Java development 2.0: Вторая волна разработки Java-приложений: Redis для реального мира

Как Redis победил memcached в приложениях с интенсивным чтением

Redis имеет много общего с memcached, но при этом обладает более богатым набором функций. В этом выпуске Java development 2.0 Эндрю экспериментирует с Redis, добавив его в свое приложение для регистрации местоположения мобильных устройств (с помощью его Java-варианта Jedis). Узнайте, как использовать Redis в качестве простого хранилища данных, и затем попробуйте переделать его в сверхбыстрый легковесный кэш.

Эндрю Гловер, президент компании, Stelligent Incorporated

Эндрю ГловерЭндрю Гловер является президентом компании Stelligent Incorporated , которая помогает другим фирмам решать проблемы качества программного обеспечения. Для этого используются эффективные стратегии тестирования и технологии непрерывной интеграции, которые позволяют коллективам разработчиков постоянно контролировать качество кода, начиная с ранних стадий разработки. Просмотрите блог Энди , там можно найти список его публикаций.



16.08.2012

Об этой серии

С момента первого появления технологии Java™ принципы разработки ПО на этом языке претерпели значительные изменения. Благодаря появлению мощных систем с открытым исходным кодом и надежных инфраструктур для предоставления средств разработки в аренду стало возможным быстро и дешево транслировать, тестировать, исполнять и поддерживать приложения Java. В данной серии статей Эндрю Гловер описывает широкий спектр средств и технологий, благодаря которым стала возможной эта новая парадигма разработки на языке Java.

Ранее в этой серии я уже обсуждал концепцию NoSQL и представил различные хранилища данных NoSQL, совместимые с платформой Java, включая Google Bigtable и Amazon SimpleDB. Я также обсуждал традиционные серверные хранилища, такие как MongoDB и CouchDB. У каждого хранилища свои сильные и слабые стороны, особенно если говорить о конкретных областях применения.

В этом месяце серия Java development 2.0 посвящена Redis, легковесному хранилищу, организованного по принципу «ключ-значение» (key-value). В сущности большинство реализаций NoSQL являются хранилищами типа «ключ-значение», но Redis поддерживает необычайно широкий набор значений, включая строки, списки, множества и хэши. В связи с этим Redis часто называют сервером структурированных данных. Кроме того, Redis славится своей исключительной скоростью, что делает его оптимальным выбором для определенного класса сценариев.

Для понимания нового бывает полезно сравнить его с чем-то уже известным, поэтому мы начнем изучение Redis с рассмотрения его черт, общих с memcached. Затем я продемонстрирую ключевые возможности Redis, которые дают ему преимущества в некоторых прикладных сценариях. И в заключение я покажу, как использовать Redis в качестве традиционного хранилища объектов моделей.

Redis и memcached

Memcached — хорошо известная система кэширования объектов в памяти, работа которой основана на кэшировании целевых ключей и значений в оперативной памяти. Таким образом, memcached снижает нагрузку ввода/вывода при интенсивном чтении диска. Вставка memcached между веб-приложением и базой данных может улучшить производительность чтения. Поэтому memcached является хорошим выбором для приложений, требующих быстрого просмотра данных. Примером такого приложения может быть служба просмотра сведений об акциях, которая в противном случае просматривала бы базу в поиске практически неизменных данных, таких как данные о соответствии тикеров названиям компаний или даже информация о цене.

MemcacheDB

Сравнение Redis с memcached не вполне корректно; куда более оправданно сравнивать его с MemcacheDB — распределенной системой хранения «ключ-значение», применяемой для реализации персистентности данных. MemcacheDB очень похожа на Redis и имеет дополнительное преимущество в виде простого взаимодействия с реализациями клиентов memcached.

Но memcached имеет некоторые ограничения, включая тот факт, что его значения являются простыми строками. Redis, в отличие от memcached, поддерживает более богатый набор функций. Некоторые сравнительные замеры показывают также, что Redis значительно быстрее, чем memcached. Богатый набор типов данных Redis позволяет сохранять в памяти более сложные данные, чем в memcached. Кроме того, в отличие от memcached, Redis может обеспечивать персистентность данных.

Redis представляет собой превосходное решение для кэширования, но его богатый набор функций позволяет найти ему и другое применение. Поскольку Redis может сохранять данные на диск и реплицировать их по узлам, его можно использовать в качестве хранилища для традиционных моделей данных (т.е. Redis можно использовать практически так же, как СУРБД). Кроме того, Redis часто используется в качестве системы очередей; при этом Redis применяется для надежного персистентного хранения рабочих очередей с использованием поддерживаемого Redis типа «список». В качестве примера крупномасштабной инфраструктуры, использующей Redis именно таким способом, можно привести GitHub.


Берем Redis и вперед!

Чтобы начать работу с Redis, нужно получить к нему доступ, что можно сделать, установив его локально или обратившись к хостинг-провайдеру. Если у вас Mac, установка выполняется проще простого. Если у вас Windows®, у вас должен быть установлен Cygwin. Если вы собираетесь работать через провайдера, то Redis4You может предложить бесплатный план. Независимо от того, каким образом вы получите доступ к Redis, вы сможете выполнять примеры, приведенные в последующей части статьи. Впрочем, следует отметить, что использование провайдера Redis для кэширования вряд ли будет удачным решением, поскольку сетевые задержки могут свести на нет все преимущества производительности.

Взаимодействие с Redis осуществляется через команды, другими словами, здесь нет SQL-подобного языка запросов. Работа с Redis очень напоминает работу с традиционной структурой данных map— все объекты имеют ключ и значение, и каждое значение имеет связанный с ним обширный набор типов данных. Кроме того, каждый тип данных имеет собственный набор команд. Например, если вы планируете использовать простые типы данных, скажем, в некоторой схеме кэширования, вы можете использовать команды set и get.

С экземпляром Redis можно взаимодействовать через оболочку командной строки. Кроме того, существует множество клиентов для программной работы с Redis. В листинге 1 показан пример несложного взаимодействия с Redis из командной строки с помощью простейших команд:

Листинг 1. Листинг 1. Применение простых команд Redis
redis 127.0.0.1:6379> set page registration
OK
redis 127.0.0.1:6379> keys *
1) "foo"
2) "page"
redis 127.0.0.1:6379> get page
"registration"

В этом примере я привязал ключ "page" (страница) к значению "registration" (регистрация) командой set. Затем я использовал команду keys (последующая звездочка * означает, что я хочу видеть все имеющиеся ключи экземпляра). Команда keys показала, что существует ключ page, а также ключ foo. Связанные с ключом значения можно извлечь с помощью команды get. Учтите, что значение, полученное с помощью команды get, может быть только строкой. Если, например, значение ключа является списком, то для извлечения элементов списка нужно использовать специальные команды для работы со списками. (Заметьте, что существуют команды, запрашивающие тип значения.)


Интеграция Java с Jedis

Программистам, желающим интегрировать Redis в приложения Java, группа Redis рекомендует использовать проект, получивший название Jedis. Jedis представляет собой легковесную библиотеку, которая привязывает собственные команды Redis к простым методам Java. Например, Jedis позволяет получать и устанавливать простые значения, как показано в листинге 2:

Листинг 2. Простые команды Redis в коде на языке Java
JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
Jedis jedis = pool.getResource();

jedis.set("foo", "bar");
String foobar = jedis.get("foo");
assert foobar.equals("bar");

pool.returnResource(jedis);
pool.destroy();

В листинге 2 я настроил группу соединений и выбрал соединение (примерно так же, как и в типичном сценарии JDBC), которое возвратил в нижней части листинга. В промежутке между логическими командами группы соединений я связал значение "bar" с ключом "foo", который я получил с помощью команды get.

Подобно memcached, Redis позволяет привязывать к значению срок его действия. Таким образом, я могу создать значение (например, временную цену товара), которое со временем будет удалено из кэша Redis. Если я хочу определить срок действия в Jedis, это нужно делать после вызова set, назначая срок действия, как показано в листинге 3:

Листинг 3. Значению Redis можно присвоить срок действия
jedis.set("ушел", "папа, ушел");
jedis.expire("ушел", 10);
String there = jedis.get("ушел");
assert there.equals("папа, ушел");

Thread.sleep(4500);

String notThere = jedis.get("ушел");
assert notThere == null;

В листинге 3 я с помощью функции expire установил срок действия значения "ушел" равным 10 секундам. После вызова Thread.sleep, команда get для "ушел" вернет null.

Типы данных в Redis

Работа с типами данных Redis, такими как списки и хэши, требует применения специальных команд. Например, список можно создать, добавляя значения к ключу. В листинге 4 я подаю команду rpush, которая добавляет значение в правую часть (в конец) списка. (Соответствующая команда lpush вставляет значение в начало списка.)

Листинг 4. Списки Redis
jedis.rpush("люди", "Мэри");
assert jedis.lindex("люди", 0).equals("Мэри");

jedis.rpush("люди", "Марк");

assert jedis.llen("люди") == 2;
assert jedis.lindex("люди", 1).equals("Марк");

Redis поддерживает широкий диапазон команд для работы с типами данных; более того, каждый тип данных имеет собственный набор команд. Вместо того чтобы рассматривать их отдельно, я продемонстрирую некоторые из них в действии в реалистичном сценарии разработки приложения.


Redis в качестве решения для кэширования

Я уже упоминал, что Redis очень легко превращается в решение для кэширования, и так уж получилось, что как раз такое решение мне и нужно! В этом примере я собираюсь интегрировать Redis с моим веб-сервисом для регистрации местоположений мобильных устройств, который я назвал Magnus.

Если вы не следили за этой серией статей, то история такова: сначала я реализовал Magnus с помощью системы Play, а затем переработал и трансформировал его в несколько других реализаций. Magnus представляет собой простой сервис, который получает документы JSON через запросы HTTP PUT. Эти документы описывают местоположение конкретной учетной записи, то есть человека с мобильным устройством.

Теперь я хочу встроить в Magnus кэширование — другими словами, я хочу сократить трафик просмотра данных путем сохранения в памяти тех данных, которые меняются не часто.

Magnus кэширует!

Первым делом в листинге 5 я с помощью вызова get выясняю, присутствует ли имя входящей учетной записи (которое является ключом) в Redis. Вызов get вернет идентификатор учетной записи в виде значения или в виде null. Если вернется значение, я буду использовать его в качестве переменной acctId. Если вернется null (что показывает, что имя учетной записи не присутствует в Redis в качестве ключа), я найду значение учетной записи в MongoDB и добавлю его в Redis командой set.

Преимуществом здесь является скорость: в следующий раз, когда эта учетная запись передаст информацию о местоположении, я смогу получить ее идентификатор из Redis (выступающий в роли кэш-памяти) вместо того, чтобы снова идти в MongoDB и выполнять лишнее чтение.

Листинг 5. Применение Redis в роли кэш-памяти
"/location/:account" {
  put {
    def jacksonMapper = new ObjectMapper()
    def json = jacksonMapper.readValue(request.contentText, Map.class)
    def formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm")
    def dt = formatter.parse(json['timestamp'])
    def res = [:]
    
    try{

      def jedis = pool.getResource()	
      def acctId = jedis.get(request.parameters['account'])

      if(!acctId){
        def acct = Account.findByName(request.parameters['account'])
        jedis.set(request.parameters['account'], acct.id.toString())
        acctId = acct.id
      }

      pool.returnResource(jedis)
      new Location(acctId.toString(), dt, json['latitude'].doubleValue(), 
      json['longitude'].doubleValue() ).save()
      res['status'] = 'success'
    }catch(exp){
      res['status'] = "error ${exp.message}"
    }
   response.json = jacksonMapper.writeValueAsString(res)
  }
}

Заметьте, что реализация Magnus (написанная на Groovy) в листинге 5 все еще использует для сохранения модели данных реализацию NoSQL; Redis исполняет лишь роль кэша для просмотра данных. Поскольку первичные данные моей учетной записи живут в MongoDB (на самом деле они находятся в MongoHQ.com), а данные Redis сохранены локально, то во время поиска последующих идентификаторов Magnus будет работать значительно быстрее.

Но постойте! А зачем мне нужны и MongoDB, и Redis? Нельзя ли обойтись чем-нибудь одним?

Node.js для ORM

Существует множество проектов, реализующих ORM-подобное отображение для Redis, включая весьма влиятельную, основанную на Ruby альтернативу под названием Ohm. Я изучил основанную на Java модификацию этого проекта (получившую название JOhm), но в итоге остановился на варианте, написанном для Node. Преимущество Ohm и производных от него проектов в том, что они позволяют отображать модель объекта на структуру данных, основанную на Redis. Таким образом, ваши объекты моделей, во-первых, становятся персистентными, а во-вторых (в большинстве случаев) начинают работать очень быстро в режиме чтения.

С помощью Nohm я смог быстро переписать свое приложение Magnus на JavaScript и легко обеспечить персистентность объектов Location. В листинге 6 я определил модель Location с тремя свойствами. (Обратите внимание, что для простоты примера я сделал timestamp строкой, а не истинной меткой времени.)

Листинг 6. Redis ORM в Node.js
var Location = nohm.model('Location', {
	properties: {
	    latitude: {
	      type: 'float',
	      unique: false,
	      validations: [
	        ['notEmpty']
	      ]
	    },
		longitude: {
	      type: 'float',
	      unique: false,
	      validations: [
	        ['notEmpty']
	      ]
	    },
		timestamp: {
	      type: 'string',
	      unique: false,
	      validations: [
	        ['notEmpty']
	      ]
        }
     }
});

Система Node Express сильно упрощает применение моего нового объекта Nohm Location. В использованной в моем приложении реализации PUT я беру входящие значения JSON и помещаю их в экземпляр Location с помощью вызова Nohm p. Затем я проверяю достоверность экземпляра. Если он достоверен, я его сохраняю.

Листинг 7. Применение Nohm в Node Express.js
app.put('/', function(req, res) {
  res.contentType('json');
	
  var location = new Location;
  location.p("timestamp", req.body.timestamp);
  location.p("latitude", req.body.latitude);
  location.p("longitude", req.body.longitude);
  
  if(location.valid()){	
  	location.save(function (err) {
	  	if (!err) {
		    res.send(JSON.stringify({ status: "success" }));
		  } else {		
		   res.send(JSON.stringify({ status: location.errors }));
		  }
	  });
  }else{
   res.send(JSON.stringify({ status: location.errors }));
  }
});

Как показано в листинге 7, Redis легко превращается в удивительно быстрое хранилище данных в памяти. В некоторых случаях он может быть даже лучшим кэшем, чем memcached!


Заключение

Redis полезен в широком диапазоне сценариев хранения данных и, поскольку он может сохранять данные на диск (и поддерживает обширный набор типов данных), иногда он может серьезно конкурировать с memcached. В тех случаях, когда это имеет для вас смысл, вы можете использовать Redis в качестве вспомогательного хранилища моделей данных и очередей. Реализации клиента Redis портированы практически на все существующие языки программирования.

Redis не может полностью заменить СУРБД и не является тяжеловесным хранилищем с широким набором запросов, как MongoDB. Тем не менее во многих случаях он может сосуществовать с этими технологиями. Как я показал в этой статье, Redis может оказаться хорошим автономным хранилищем данных для приложений, часто выполняющих просмотр данных, или в тех случаях, когда можно получать статистические данные в реальном времени с помощью быстрых атомарных операций Redis.

Ресурсы

Научиться

Получить продукты и технологии

  • Скачать Redis и Jedis: Redis представляет собой хранилище типа «ключ-значение» и сервер структурированных данных с открытым исходным кодом; Jedis является рекомендованным в настоящее время клиентом для разработок на основе Java.
  • Получить Nohm: реализация объектно-реляционного отображения Redis Ohm для Node.js.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=830741
ArticleTitle=Java development 2.0: Вторая волна разработки Java-приложений: Redis для реального мира
publish-date=08162012