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 .