Содержание


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

Redis для реального мира

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

Comments

Серия контента:

Этот контент является частью # из серии # статей: Java development 2.0: Вторая волна разработки Java-приложений

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Java development 2.0: Вторая волна разработки 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 является хорошим выбором для приложений, требующих быстрого просмотра данных. Примером такого приложения может быть служба просмотра сведений об акциях, которая в противном случае просматривала бы базу в поиске практически неизменных данных, таких как данные о соответствии тикеров названиям компаний или даже информация о цене.

Но 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.


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи: Java development 2.0: Redis for the real world.
  • Серия статей Java development 2.0 посвящена технологиям, меняющим принципы разработки на языке Java. Среди рассматриваемых тем —NoSQL (май 2010 г.), MongoDB (сентябрь 2010 г.) и Gretty (август 2011 г.).
  • Скачать Redis и Jedis: Redis представляет собой хранилище типа «ключ-значение» и сервер структурированных данных с открытым исходным кодом; Jedis является рекомендованным в настоящее время клиентом для разработок на основе Java.
  • Получить Nohm: реализация объектно-реляционного отображения Redis Ohm для Node.js.
  • Учебник по Redis (Саймон Уилисон (Simon Wilison), 22 апреля 2010 г.): слайды и заметки с трехчасовой учебной сессии по Redis на европейской конференции по NoSQL. См. также комментарии лектора.
  • "Можно ли считать memcached устаревшим по сравнению с Redis?" (Stackoverflow.com, май 2010 г.): советы по сравнению и оценке Redis и memcached для конкретных сценариев применения.
  • "Применение memcached для повышения производительности сайта" (Мартин Браун (Martin Brown), developerWorks, август 2010 г.): узнайте больше о механизмах хранения данных в памяти с помощью memcached и его преимуществах с точки зрения производительности.
  • "Джеймс Филипс (James Phillips) обсуждает постреляционный мир" (раздел технологий Java, серия технических подкастов, developerWorks, май 2011 г.): Couchbase представляет собой NoSQL-решение, сочетающее гибкость и высокую производительность CouchDB с традиционными функциями, такими как очереди и индексация. Присоединяйтесь к Эндрю Гловеру и соучредителю Couchbase Джеймсу Филлипсу в обсуждении прекрасного нового мира быстрых и безопасных веб-приложений.

Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

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