ElastiCacheとELBとtwemproxy
redis / memcachedをスケールする方法として、アプリケーションで分散アルゴリズムを実装する方法や、ライブラリを使う方法などありますが、
Twitterが作っているtwemproxy(https://github.com/twitter/twemproxy)というものがあります。
これは、redis / memachedの前段に置くことでキャッシュクラスタを構成することが出来ます。様々な分散アルゴリズムや、故障ノードの切り離しなどの機能もあり、
キャッシュノードが不具合で接続できなくなったとしても自動でサービスアウトしてくれます。
開発も盛んに進んでいて、今、ノード追加時にプロセスの再起動が必要ですが、gracefulの実装も見えて来ました。
詳しくは以前書いたこちらの記事を参照して下さい。http://d.conma.me/entry/20121227/1356596553
twemproxyは現状だとノード追加時にプロセスの再起動が必要なので、その間通信が切れてしまいます。
また、冗長構成をとるためにELBの配下に置くことで、切り離しつつ設定ファイルを書き換えていったり、twemproxy自体の障害にも対応出来るようになります。
そこで、ElasatiCacheだけ・twemproxy+ElastiCacahe・twemproxy+ElastiCacahe+Internal ELB の3種類の構成でパフォーマンステストを行いました。
ElastiCacheはcache.m1.large、twemproxyはm1.largeを使用しました。
10万キーのget / setを行う時間を計測しcmd/secを出しています。並列度は100ノード、キーサイズは40bytes / valueは400 bytesです。
横軸はElastiCacheノード数です。
ElastiCacheだけ
twemproxy+ElastiCacahe
twemproxy+ElastiCacahe+ELB
結果
ELBを挟むことで桁が1つ下がる結果となりました。そして、各ノード数に関係なく粗一定の性能になりました。
twemproxyを使用した事による性能の劣化は殆どなく高速に動作しています。しかし、CPU使用率がcmd/secが上がるにつれて多くなる点に注意が必要です。
分散アルゴリズムはtwemproxyが対応しているものを全て試したのですが、殆ど差は見受けられない結果となりました。
以前、DBの前段にInternal ELBを配置してパフォーマンスを計測したのですが、ここまでの性能劣化は起こらなかったので、さらに詳しく検証をしています。
その時は、接続したらセッションはそのままになっていたのですが、今回はset / getの度にセッションを切断していたので、それが原因かと思っています。
ElastiCacheも1つのendpointに接続して、config get cluster を発行すると、有効なノードの接続情報を返してくれますが、クライアントの対応が必要です。
ElastiCache側でkeyによる分散が実装されるとさらにElastiCacheの使用が快適になりますが、今のところそのような話は出ていないっぽいので、このようなproxyを使うといいのではないかなと思います。
twemproxyが2台であれば、HeartBeatを使用して、ENIの付け替えでactive-standby構成が作れます。
おまけ
ELBでtwemproxyのヘルスチェックを行う際には、管理ポートがデフォルトでは22222番ポートになっているので、こちらを見るか、twemproxyの各ノードグループのlisten portでもいいと思いますが、ノードグループのノード数が基準値以下になったら切り離すというような場合は以下のような簡単なスクリプトを配置してxinetdでアクセスを受け付ける事で実現出来ます。
require 'json' require 'net/telnet' SEVER_CONFIG_NAME = 'elastic_cache' # 監視を行うノードグループ名 SERVER_REGEX = /^twemproxy-test\.9jpctq\..*/ # ElastiCacacheのサーバ名の正規表現 MIN_SPEAR_SERVER_NUM = 2 # # 最低限組み込んでおくサーバ数 SERVICE_IN_SERVER_NUM = 3 # サービスインしているcache node数 TWEMPROXY_IPADDR = '127.0.0.1' # twemproxyの動いてるノードのIPアドレス TWEMPROXY_ADMIN_PORT = 22222 # 管理ポート def is_healthy? begin info = '' telnet = Net::Telnet.new("Host" => TWEMPROXY_IPADDR, "Port" => TWEMPROXY_ADMIN_PORT) telnet.cmd('') { |c| info = c } telnet.close ejected_server_count = JSON.parse(info)[SEVER_CONFIG_NAME]['server_ejects'].to_i rescue Exception => e return false end return false if ejected_server_count.nil? (SERVICE_IN_SERVER_NUM - ejected_server_count) >= MIN_SPEAR_SERVER_NUM end gets if is_healthy? and not File.exists?('/tmp/out') code_msg = "200 OK" else code_msg = "503 Service Unavailable" end print "HTTP/1.0 #{code_msg}\r\n" print "Content-type: text/html\r\n" print "Content
これを適当な名前で保存して、/etc/xinet.d/twemproxyとかに以下の記述をします
service twemproxy { flags = REUSE socket_type = stream port = 2525 wait = no user = nagios group = nagios server = /usr/bin/twemproxy-check log_on_failure += USERID disable = no only_from = 127.0.0.1 10.0.0.0/16 per_source = UNLIMITED instances = UNLIMITED }
これで、Internal ELBのヘルスチェックは、twemproxyが動いているサーバに
Protocol: http Port: 2525 Path: /
で監視が出来るようになります。
また、/tmp/out でファイルを作ると任意のタイミングでELBから切り離す事ができます。
更に高負荷環境で検証をしているので、まとめたいと思います。
twemproxyの設定
elastic_cache: listen: 0.0.0.0:22121 hash: md5 distribution: ketama auto_eject_hosts: true redis: false hash_tag: "{}" backlog: 4096 preconnect: true timeout: 400 server_retry_timeout: 5000 server_failure_limit: 1 servers: - twemproxy-test.9jpctq.0001.apne1.cache.amazonaws.com:11211:1 - twemproxy-test.9jpctq.0002.apne1.cache.amazonaws.com:11211:1 - twemproxy-test.9jpctq.0003.apne1.cache.amazonaws.com:11211:1 - twemproxy-test.9jpctq.0004.apne1.cache.amazonaws.com:11211:1