1. Обзор
Эта статья представляет собой введение в Jedis , клиентскую библиотеку на Java для Redis – популярного хранилища структур данных в памяти, которое также может сохраняться на диске. Он управляется структурой данных на основе хранилища ключей для сохранения данных и может использоваться в качестве базы данных, кэша, брокера сообщений и т. Д.
Во-первых, мы собираемся объяснить, в каких ситуациях джедаи полезны и о чем идет речь.
В последующих разделах мы подробно рассмотрим различные структуры данных и объясним транзакции, конвейерную обработку и функцию публикации/подписки. Мы завершаем работу с пулом соединений и кластером Redis.
2. Почему джедаи?
Redis перечисляет наиболее известные клиентские библиотеки на своем официальном сайте . Существует множество альтернатив джедаям, но только еще две в настоящее время достойны их звезды рекомендации, салат и Редиссон .
У этих двух клиентов есть некоторые уникальные функции, такие как потокобезопасность, прозрачная обработка повторного подключения и асинхронный API, всех функций которого не хватает Jedis.
Однако он невелик и значительно быстрее, чем два других. Кроме того, это клиентская библиотека по выбору разработчиков Spring Framework, и у нее самое большое сообщество из всех трех.
3. Зависимости Maven
Давайте начнем с объявления единственной зависимости, которая нам понадобится в pom.xml :
redis.clients jedis 2.8.1
Если вы ищете последнюю версию библиотеки, проверьте эту страницу .
4. Установка Redis
Вам нужно будет установить и запустить одну из последних версий Redis. На данный момент мы запускаем последнюю стабильную версию (3.2.1), но любая версия post 3.x должна быть в порядке.
Найдите здесь дополнительную информацию о Redis для Linux и Macintosh, у них очень похожие основные шаги установки. Windows официально не поддерживается, но этот порт хорошо поддерживается.
После этого мы можем напрямую погрузиться в него и подключиться к нему из нашего Java-кода:
Jedis jedis = new Jedis();
Конструктор по умолчанию будет работать нормально, если вы не запустили службу на порту, отличном от порта по умолчанию, или на удаленной машине, и в этом случае вы можете правильно настроить ее, передав правильные значения в качестве параметров в конструктор.
5. Структуры Данных Redis
Большинство собственных команд операций поддерживаются, и, что достаточно удобно, они обычно имеют одно и то же имя метода.
5.1. Строки
Строки-это самый простой вид Redisvalue, полезный для тех случаев, когда вам нужно сохранить простые типы данных ключ-значение:
jedis.set("events/city/rome", "32,15,223,828"); String cachedResponse = jedis.get("events/city/rome");
Переменная кэшированный ответ будет содержать значение 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 будет содержать значение first Task . Помните, что вы можете сериализовать любой объект и сохранить его в виде строки, чтобы сообщения в очереди при необходимости могли содержать более сложные данные.
5.3. Наборы
Наборы Redis-это неупорядоченная коллекция строк, которая пригодится, если вы хотите исключить повторяющиеся элементы:
jedis.sadd("nicknames", "nickname#1"); jedis.sadd("nicknames", "nickname#2"); jedis.sadd("nicknames", "nickname#1"); Setnicknames = jedis.smembers("nicknames"); boolean exists = jedis.sismember("nicknames", "nickname#1");
Набор Java nicknames будет иметь размер 2, второе добавление nickname#1 было проигнорировано. Кроме того, переменная exists будет иметь значение true , метод dismember позволяет быстро проверить наличие определенного элемента.
5.4. Хэши
Хэши Redis сопоставляются между полями String и значениями String :
jedis.hset("user#1", "name", "Peter"); jedis.hset("user#1", "job", "politician"); String name = jedis.hget("user#1", "name"); Mapfields = jedis.hgetAll("user#1"); String job = fields.get("job");
Как вы можете видеть, хэши-это очень удобный тип данных, когда вы хотите получить доступ к свойствам объекта по отдельности, поскольку вам не нужно извлекать весь объект целиком.
5.5. Сортированные наборы
Сортированные наборы подобны набору, в котором каждый член имеет связанный рейтинг, который используется для их сортировки:
Mapscores = 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, потому что Игрок Один является вторым в рейтинге, а рейтинг основан на нуле.
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); ResponsepipeExists = p.sismember("searched#" + userOneId, "paris"); Response > pipeRanking = p.zrange("ranking", 0, -1); p.sync(); String exists = pipeExists.get(); Set 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. Объединение Пулов Соединений
Важно знать, что то, как мы обращаемся с нашим примером джедаев, наивно. В реальном сценарии вы не хотите использовать один экземпляр в многопоточной среде, поскольку один экземпляр не является потокобезопасным.
К счастью, мы можем легко создать пул подключений к 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 вы не можете выполнять транзакции или использовать конвейеры-две важные функции, на которые полагаются многие приложения для обеспечения целостности данных.
Транзакции отключены, поскольку в кластеризованной среде ключи будут сохраняться в нескольких экземплярах. Атомарность операций и безопасность потоков не могут быть гарантированы для операций, связанных с выполнением команд в разных экземплярах.
Некоторые продвинутые стратегии создания ключей гарантируют, что данные, которые вам интересны, будут сохранены в одном и том же экземпляре, будут сохранены таким образом. Теоретически это должно позволить вам успешно выполнять транзакции, используя один из базовых экземпляров Jedis кластера Redis.
К сожалению, в настоящее время вы не можете узнать, в каком экземпляре Redis конкретный ключ сохраняется с помощью Jedis (который фактически поддерживается изначально Redis), поэтому вы не знаете, в каком из экземпляров вы должны выполнить операцию транзакции. Если вы заинтересованы в этом, вы можете найти более подробную информацию здесь .
11. Заключение
Подавляющее большинство функций Redis уже доступны в Jedis, и его развитие продвигается хорошими темпами.
Это дает вам возможность интегрировать мощный механизм хранения данных в памяти в ваше приложение без особых проблем, просто не забудьте настроить пул соединений, чтобы избежать проблем с безопасностью потоков.
Примеры кода можно найти в проекте GitHub .