ElastiCache — один из множества сервисов в Amazon , который дает облачные Memcached и Redis . Дает не просто так, а с автоматической заменой упавших нод, а также со специальным клиентом, который определяет состав созданного кластера и ходит на различные его узлы в зависимости от значения ключа. То есть, получается что-то вроде EC2 инстансов с поднятым Memcached или Redis на них в автоскейлинг группе за ELB , только с шардированием и уже готовое. В этой заметке мы научимся настраивать Amazon ElastiCache и ходить в него из кода на языке Scala.
Создадим Memcached кластер из двух узлов. Это делается очень просто, поэтому не будем останавливаться на этом шаге подробно. Проверьте только, что в security group, указанной при создании кластера, разрешено хождение на порт 11211. После того, как кластер будет создан (это займет какое-то время, порядка нескольких минут), проверьте, что в него можно ходить с ваших EC2 инстансов и нельзя с машин, находящихся вне облака Амазона. Последнее, как я понимаю, всегда имеет место быть, независимо от настроек вашей security group.
Выше говорилось про автодискавери узлов, входящих в кластер. Это работает так. Вы сообщаете клиенту configuration endpoint кластера. При подключении клиент делает примерно следующее:
VALUE AmazonElastiCache:cluster 0 123
1
domain1.amazon.com|172.1.2.3|11211 domain2.amazon.com|172.4.5.6|11211
END
quit
То есть, список узлов хранится в configuration endpoint по ключу с волшебным именем AmazonElastiCache:cluster
. На самом деле, похоже, этот ключ есть на всех узлах кластера, а configuration endpoint представляет собой просто доменное имя, которое резолвится в случайно выбранный узел из кластера.
Можно подключиться telnet’ом к одному из узлов и проверить, что все работает:
abcde
STORED
get mykey
VALUE mykey 0 5
abcde
END
delete mykey
DELETED
quit
Для хождения в Memcached в мире Java есть неплохой клиент spymemcached , можно воспользоваться им:
import java. net . InetSocketAddress
import java. util . concurrent . TimeUnit
import net. spy . memcached . MemcachedClient
import scala. compat . Platform
object SpyMemcachedExample extends App {
val host = «memcache-cluster.ololo.cache.amazonaws.com»
val port = 11211
val client = new MemcachedClient ( new InetSocketAddress ( host, port ) )
val startTime = Platform. currentTime
println ( s «Start time: $startTime» )
for ( i < — 1 to 32 ) {
try {
val key = s «test-key-$i»
client. set ( key, 3600 , s «test-value-$i-$startTime» )
. get ( 100 , TimeUnit. MILLISECONDS )
val value = client. asyncGet ( key )
. get ( 100 , TimeUnit. MILLISECONDS )
. asInstanceOf [ String ]
println ( s «key $i — OK: $value» )
} catch {
case e : Exception =>
println ( s «key $i — FAILED: ${e.getMessage}» )
}
}
// клиент создает трэды, для завершения которых нужно
// явно завершить приложение
System. exit ( 0 )
}
Но тут, очевидно, нет никакого автодискавери. Легко убедиться при помощи того же tcpdump, или зайдя на ноду telnet’ом и посмотрев ключи, что программа ходит только на одну случайно выбранную ноду в кластере. Это совершенно точно не то поведение, которое нам нужно. К счастью, ребятами из Amazon был сделан пропатченный клиент с автодискавери , который можно подключить к проекту без изменения основного кода программы:
version := «0.1»
scalaVersion := «2.11.6»
libraryDependencies ++ = Seq (
// «net.spy» % «spymemcached» % «2.11.6»
( «com.amazonaws» % «elasticache-java-cluster-client» % «1.0.61.0» )
. exclude ( «jmock» , «jmock» )
. exclude ( «jmock» , «jmock-cglib» )
. exclude ( «junit» , «junit» )
. exclude ( «junit» , «junit-dep» )
. exclude ( «org.springframework» , «spring-beans» )
. exclude ( «org.springframework» , «spring-asm» )
. exclude ( «cglib» , «cglib-full» )
. exclude ( «cglib» , «cglib-nodep» )
)
Как видите, пакет тащит за собой много мусора, который, впрочем, мы здесь выпиливаем средствами SBT .
Теперь несложно убедиться, что программа ходит на разные узлы кластера и шардирует данные. Примите однако во внимание, что ваше приложение может считать старые данные, если размер или состав кластера поменяется! Поэтому используйте ElastiCache исключительно как кэш и храните в нем неизменяемые значения.
По поводу скорости ElastiCache. Согласно моим тестам, на одну операцию чтения или записи уходит около 1.12 мс. На практике все зависит от клиента, типа узлов в вашем кластере, нагруженности сети, а также размера ключей и значений. Но порядок примерно такой, что, как мне кажется, вполне ОК.
Если при запуске приложения вы получаете какие-то ошибки, но потом все становится ОК, не спешите паниковать. Дело в том, что для предоставления кэшей в Amazon используется какой-то там их внутренний движок. В версии 1.4 все работает, как описано выше, однако в версии 1.5 список узлов в кластере получается при помощи команды config get cluster
. Клиент сначала пытается выполнить ее и в случае ошибки (которая и попадает в лог) переподключается, после чего запрашивает ключ AmazonElastiCache:cluster
. Подробности здесь .
Надо признать, все это выглядит довольно страшно и словно было отдано на оутсорс индусам. Возможно, вам захочется воспользоваться каким-то другим клиентом для Memcached и написать поверх него самостоятельно всю эту логику с автодискавери. Возможно, даже поднять собственные Memcached в EC2, потому что за те же деньги там в 2 раза больше оперативной памяти и никакого вендорлока. Правда, вендорлок не такой уж и большой, а с учетом стоимости внутреннего ELB по деньгам, возможно, получается то же самое. Мой вердикт на момент написания этих строк заключался в том, что, несмотря на всю спорность этого сервиса, ElastiCache скорее полезен, чем нет, и стоит держать его на вооружении.
Исходники к данной заметке доступны в этом репозитории .
А используете ли вы ElastiCache в своих проектах?
Дополнение: Как я поднимал Couchbase-кластер в Амазоне