Перейти к основному содержимому

Введение в Jedis — клиентскую библиотеку Java Redis

· 9 мин. чтения

1. Обзор

Эта статья представляет собой введение в Jedis , клиентскую библиотеку на Java для Redis — популярное хранилище структур данных в памяти, которое также может сохраняться на диске. Он управляется структурой данных на основе хранилища ключей для сохранения данных и может использоваться в качестве базы данных, кэша, брокера сообщений и т. д.

Во-первых, мы собираемся объяснить, в каких ситуациях полезны джедисы и о чем они.

В последующих разделах мы подробно рассмотрим различные структуры данных и объясним транзакции, конвейерную обработку и функцию публикации/подписки. Мы заканчиваем с пулом соединений и Redis Cluster.

2. Почему джедаи?

Redis перечисляет самые известные клиентские библиотеки на своем официальном сайте . Существует множество альтернатив Jedis, но только две из них в настоящее время достойны своей рекомендательной звезды: салат и Redisson .

У этих двух клиентов есть некоторые уникальные функции, такие как безопасность потоков, прозрачная обработка повторного подключения и асинхронный API — все функции, которых нет в Jedis.

Однако он небольшой и значительно быстрее, чем два других. Кроме того, это клиентская библиотека, которую выбирают разработчики Spring Framework, и у нее самое большое сообщество из всех трех.

3. Зависимости Maven

Начнем с объявления единственной зависимости, которая нам понадобится в pom.xml :

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>

Если вы ищете последнюю версию библиотеки, посетите эту страницу .

4. Установка Redis

Вам нужно будет установить и запустить одну из последних версий Redis. На данный момент мы используем последнюю стабильную версию (3.2.1), но подойдет любая версия после 3.x.

Найдите здесь дополнительную информацию о Redis для Linux и Macintosh, у них очень похожие основные этапы установки. Windows официально не поддерживается, но этот порт хорошо поддерживается.

После этого мы можем напрямую погрузиться и подключиться к нему из нашего Java-кода:

Jedis jedis = new Jedis();

Конструктор по умолчанию будет работать нормально, если вы не запустили службу на порту, отличном от порта по умолчанию, или на удаленной машине, и в этом случае вы можете правильно настроить ее, передав правильные значения в качестве параметров в конструктор.

5. Структуры данных Redis

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

5.1. Струны

Строки — это самый простой тип значения Redis, полезный, когда вам нужно сохранить простые типы данных ключ-значение:

jedis.set("events/city/rome", "32,15,223,828");
String cachedResponse = jedis.get("events/city/rome");

Переменная cachedResponse будет содержать значение 32,15,223,828 . В сочетании с поддержкой истечения срока действия, о которой будет сказано ниже, он может работать как молниеносно быстрый и простой в использовании уровень кэширования для HTTP-запросов, полученных в вашем веб-приложении, и других требований к кэшированию.

5.2. Списки

Списки Redis — это просто списки строк, отсортированные по порядку вставки, что делает их идеальным инструментом для реализации, например, очередей сообщений:

jedis.lpush("queue#tasks", "firstTask");
jedis.lpush("queue#tasks", "secondTask");

String task = jedis.rpop("queue#tasks");

Переменная task будет содержать значение firstTask . Помните, что вы можете сериализовать любой объект и сохранить его как строку, поэтому сообщения в очереди могут содержать более сложные данные, когда это необходимо.

5.3. Наборы

Наборы Redis — это неупорядоченная коллекция строк, которые пригодятся, когда вы хотите исключить повторяющиеся элементы:

jedis.sadd("nicknames", "nickname#1");
jedis.sadd("nicknames", "nickname#2");
jedis.sadd("nicknames", "nickname#1");

Set<String> nicknames = jedis.smembers("nicknames");
boolean exists = jedis.sismember("nicknames", "nickname#1");

Псевдонимы Java Set будут иметь размер 2, второе добавление псевдонима #1 было проигнорировано. Кроме того, существующая переменная будет иметь значение true , метод sismember позволяет быстро проверить существование определенного члена.

5.4. Хэши

Хэши Redis сопоставляются между строковыми полями и строковыми значениями:

jedis.hset("user#1", "name", "Peter");
jedis.hset("user#1", "job", "politician");

String name = jedis.hget("user#1", "name");

Map<String, String> fields = jedis.hgetAll("user#1");
String job = fields.get("job");

Как видите, хэши — очень удобный тип данных, когда вы хотите получить доступ к свойствам объекта по отдельности, поскольку вам не нужно извлекать весь объект.

5.5. Сортированные наборы

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

Map<String, Double> scores = new HashMap<>();

scores.put("PlayerOne", 3000.0);
scores.put("PlayerTwo", 1500.0);
scores.put("PlayerThree", 8200.0);

scores.entrySet().forEach(playerScore -> {
jedis.zadd(key, playerScore.getValue(), playerScore.getKey());
});

String player = jedis.zrevrange("ranking", 0, 1).iterator().next();
long rank = jedis.zrevrank("ranking", "PlayerOne");

Переменная player будет содержать значение PlayerThree , потому что мы извлекаем первого игрока, который набрал наибольшее количество очков. Переменная rank будет иметь значение 1, потому что PlayerOne является вторым в рейтинге, а рейтинг начинается с нуля.

6. Транзакции

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

String friendsPrefix = "friends#";
String userOneId = "4352523";
String userTwoId = "5552321";

Transaction t = jedis.multi();
t.sadd(friendsPrefix + userOneId, userTwoId);
t.sadd(friendsPrefix + userTwoId, userOneId);
t.exec();

Вы даже можете сделать успех транзакции зависимым от определенного ключа, «наблюдая» за ним прямо перед созданием экземпляра транзакции :

jedis.watch("friends#deleted#" + userOneId);

Если значение этого ключа изменится до того, как транзакция будет выполнена, транзакция не будет успешно завершена.

7. Конвейерная обработка

Когда нам нужно отправить несколько команд, мы можем упаковать их вместе в один запрос и сэкономить накладные расходы на соединение с помощью конвейеров, по сути, это оптимизация сети. Пока операции взаимно независимы, мы можем воспользоваться этой техникой:

String userOneId = "4352523";
String userTwoId = "4849888";

Pipeline p = jedis.pipelined();
p.sadd("searched#" + userOneId, "paris");
p.zadd("ranking", 126, userOneId);
p.zadd("ranking", 325, userTwoId);
Response<Boolean> pipeExists = p.sismember("searched#" + userOneId, "paris");
Response<Set<String>> pipeRanking = p.zrange("ranking", 0, -1);
p.sync();

String exists = pipeExists.get();
Set<String> ranking = pipeRanking.get();

Обратите внимание, что мы не получаем прямого доступа к ответам на команды, вместо этого нам предоставляется экземпляр Response , из которого мы можем запросить базовый ответ после синхронизации конвейера.

8. Опубликовать/подписаться

Мы можем использовать функциональность брокера обмена сообщениями Redis для отправки сообщений между различными компонентами нашей системы. Убедитесь, что потоки подписчиков и издателей не используют одно и то же соединение Jedis.

8.1. Подписчик

Подпишитесь и прослушайте сообщения, отправленные на канал:

Jedis jSubscriber = new Jedis();
jSubscriber.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// handle message
}
}, "channel");

Подписка — это метод блокировки, вам нужно будет явно отказаться от подписки на JedisPubSub . Мы переопределили метод onMessage , но есть много других полезных методов, доступных для переопределения.

8.2. Издатель

Затем просто отправьте сообщения на тот же канал из темы издателя:

Jedis jPublisher = new Jedis();
jPublisher.publish("channel", "test message");

9. Пул соединений

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

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

Давайте создадим JedisPool :

final JedisPoolConfig poolConfig = buildPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "localhost");

private JedisPoolConfig buildPoolConfig() {
final JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(128);
poolConfig.setMaxIdle(128);
poolConfig.setMinIdle(16);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestWhileIdle(true);
poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis());
poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis());
poolConfig.setNumTestsPerEvictionRun(3);
poolConfig.setBlockWhenExhausted(true);
return poolConfig;
}

Поскольку экземпляр пула является потокобезопасным, вы можете хранить его где-нибудь статически, но вы должны позаботиться об уничтожении пула, чтобы избежать утечек при завершении работы приложения.

Теперь мы можем использовать наш пул из любого места приложения, когда это необходимо:

try (Jedis jedis = jedisPool.getResource()) {
// do operations with jedis resource
}

Мы использовали оператор Java try-with-resources, чтобы избежать необходимости вручную закрывать ресурс Jedis, но если вы не можете использовать этот оператор, вы также можете закрыть ресурс вручную в предложении finally .

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

10. Кластер Redis

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

Как только у нас это будет готово, мы можем начать использовать его из нашего приложения:

try (JedisCluster jedisCluster = new JedisCluster(new HostAndPort("localhost", 6379))) {
// use the jedisCluster resource as if it was a normal Jedis resource
} catch (IOException e) {}

Нам нужно только предоставить информацию о хосте и порте от одного из наших мастер-экземпляров, он автоматически обнаружит остальные экземпляры в кластере.

Это, безусловно, очень мощная функция, но это не панацея. При использовании Redis Cluster вы не можете выполнять транзакции или использовать конвейеры — две важные функции, на которые полагаются многие приложения для обеспечения целостности данных.

Транзакции отключены, поскольку в кластерной среде ключи будут сохраняться в нескольких экземплярах. Атомарность операций и безопасность потоков не могут быть гарантированы для операций, включающих выполнение команд в разных экземплярах.

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

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

11. Заключение

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

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

Примеры кода вы можете найти в проекте GitHub .