← На главную

Amazon ElastiCache и работа с ним на Scala

ElastiCache – один из множества сервисов в Amazon, который дает облачные Memcached и Redis. Дает не просто так, а с автоматической заменой упавших нод, а также со специальным клиентом, который определяет состав созданного кластера и ходит на различные его узлы в зависимости от значения ключа. То есть, получается что-то вроде EC2 инстансов с поднятым Memcached или Redis на них в автоскейлинг группе за ELB, только с шардированием и уже готовое. В этой заметке мы научимся настраивать Amazon ElastiCache и ходить в него из кода на языке Scala.

Создадим Memcached кластер из двух узлов. Это делается очень просто, поэтому не будем останавливаться на этом шаге подробно. Проверьте только, что в security group, указанной при создании кластера, разрешено хождение на порт 11211. После того, как кластер будет создан (это займет какое-то время, порядка нескольких минут), проверьте, что в него можно ходить с ваших EC2 инстансов и нельзя с машин, находящихся вне облака Амазона. Последнее, как я понимаю, всегда имеет место быть, независимо от настроек вашей security group.

Выше говорилось про автодискавери узлов, входящих в кластер. Это работает так. Вы сообщаете клиенту configuration endpoint кластера. При подключении клиент делает примерно следующее:

get AmazonElastiCache:cluster 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’ом к одному из узлов и проверить, что все работает:

set mykey 0 60 5 abcde STORED get mykey VALUE mykey 0 5 abcde END delete mykey DELETED quit

Для хождения в Memcached в мире Java есть неплохой клиент spymemcached, можно воспользоваться им:

package me.eax.spymemcached_example 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 был сделан пропатченный клиент с автодискавери, который можно подключить к проекту без изменения основного кода программы:

name := "spymemcached-example" 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-кластер в Амазоне