Введение в Jedis - клиентская библиотека Java Redis

Введение в Jedis - клиентская библиотека Java Redis

1. обзор

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

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

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

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

Redis перечисляет наиболее известные клиентские библиотеки на ихofficial site. Есть несколько альтернатив джедаям, но только две в настоящее время достойны своей звезды рекомендаций:lettuce иRedisson.

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

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

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

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


    redis.clients
    jedis
    2.8.1

Если вы ищете последнюю версию библиотеки, посмотритеthis page.

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

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

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

После этого мы можем непосредственно погрузиться и подключиться к нему из нашего 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. Sets

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

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

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

Набор Java Setnicknames будет иметь размер 2, второе добавлениеnickname#1 было проигнорировано. Кроме того, переменнаяexists будет иметь значениеtrue, методsismember позволяет быстро проверить наличие определенного члена.

5.4. Хэш

Хэши Redis сопоставляют поляString и значенияString:

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

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

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

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

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

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

Map 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();

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

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

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

7. Pipelining

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

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 pipeExists = p.sismember("searched#" + userOneId, "paris");
Response> pipeRanking = p.zrange("ranking", 0, -1);
p.sync();

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

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

8. Publish/Subscribe

Мы можем использовать функциональность брокера обмена сообщениями 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, но есть гораздо большеuseful methods, доступных для переопределения.

8.2. издатель

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

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

9. Пул подключений

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

К счастью, мы можем легко создать пул подключений к 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 Cluster

Эта реализация Redis обеспечивает легкую масштабируемость и высокую доступность, мы рекомендуем вам прочитать ихofficial specification, если вы не знакомы с ними. Мы не будем рассматривать настройку кластера 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 изначально), поэтому вы не знаете, в каком из экземпляров вы должны выполнить операцию транзакции. Если вас это интересует, вы можете найти дополнительную информациюhere.

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

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

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

Вы можете найти образцы кода вGitHub project.