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-кластер в Амазоне

EnglishRussianUkrainian