Amazon ElastiCache и работа с ним на Scala
22 апреля 2015
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-кластер в Амазоне
Метки: Scala, Облака, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.