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 кластера. При подключении клиент делает примерно следующее:

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

Метки: , , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.