Автор оригинала: Eric Goebelbecker.
1. Обзор
Эта статья представляет собой введение в Lettuce , а Red-это Java-клиент.
Redis-это хранилище значений ключей в памяти, которое можно использовать в качестве базы данных, кэша или брокера сообщений. Данные добавляются, запрашиваются, изменяются и удаляются с помощью команд |, которые работают с ключами в структуре данных Redis в памяти.
Lettuce поддерживает как синхронную, так и асинхронную связь с использованием полного API Redis, включая его структуры данных, обмен сообщениями pub/sub и соединения с сервером высокой доступности.
2. Почему Салат?
Мы рассказывали о джедаях в одном из предыдущих постов. Чем отличается салат-латук?
Наиболее существенным отличием является его асинхронная поддержка через интерфейс Java 8 Completion Stage и поддержка реактивных потоков. Как мы увидим ниже, Lettuce предлагает естественный интерфейс для выполнения асинхронных запросов с сервера базы данных Redis и для создания потоков.
Он также использует Netty для связи с сервером. Это делает API “более тяжелым”, но также делает его более подходящим для совместного использования соединения с несколькими потоками.
3. Настройка
3.1. Зависимость
Давайте начнем с объявления единственной зависимости, которая нам понадобится в pom.xml :
io.lettuce lettuce-core 5.0.1.RELEASE
Последнюю версию библиотеки можно проверить в репозитории Github или в Maven Central.
3.2. Установка Redis
Нам нужно будет установить и запустить по крайней мере один экземпляр Redis, два, если мы хотим протестировать кластеризацию или режим sentinel (хотя режим sentinel требует трех серверов для правильной работы.) Для этой статьи мы используем 4.0.x – последнюю стабильную версию на данный момент.
Более подробную информацию о начале работы с Redis можно найти здесь , включая загрузки для Linux и macOS.
Redis официально не поддерживает Windows, но есть порт сервера здесь . Мы также можем запустить Redis в Docker , что является лучшей альтернативой для Windows 10 и быстрым способом запуска.
4. Соединения
4.1. Подключение к серверу
Подключение к Redis состоит из четырех этапов:
- Создание URI Redis
- Использование URI для подключения к redisClient
- Открытие соединения Redis
- Создание набора команд Redis
Давайте посмотрим на реализацию:
RedisClient redisClient = RedisClient .create("redis://[email protected]:6379/"); StatefulRedisConnectionconnection = redisClient.connect();
Соединение Redis с сохранением состояния -это то, на что это похоже; потокобезопасное соединение с сервером Redis, которое будет поддерживать соединение с сервером и при необходимости повторно подключаться. Как только у нас есть соединение, мы можем использовать его для выполнения команд Redis синхронно или асинхронно.
redisClient использует значительные системные ресурсы, так как он содержит ресурсы Netty для связи с сервером Redis. Приложения, требующие нескольких подключений, должны использовать один redisClient.
4.2. Redis URIs
Мы создаем Переоткрытие передав URI статическому заводскому методу.
Салат использует пользовательский синтаксис для URI Redis. Это схема:
redis :// [[email protected]] host [: port] [/ database] [? [timeout=timeout[d|h|m|s|ms|us|ns]] [&_database=database_]]
Существует четыре схемы URI:
- redis – автономный сервер Redis
- rediss – автономный сервер Redis через SSL-соединение
- redis-socket – автономный сервер Redis через доменный сокет Unix
- redis-sentinel – сервер Redis Sentinel
Экземпляр базы данных Redis может быть указан как часть пути URL-адреса или в качестве дополнительного параметра. Если указаны оба параметра, параметр имеет более высокий приоритет.
В приведенном выше примере мы используем представление String . Салат также имеет класс RedisURI для построения соединений. Он предлагает шаблон Builder :
RedisURI.Builder .redis("localhost", 6379).auth("password") .database(1).build();
И конструктор:
new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);
4.3. Синхронные команды
Подобно Jedis, Lettuce предоставляет полный набор команд Redis в виде методов.
Однако Lettuce реализует как синхронные, так и асинхронные версии. Мы кратко рассмотрим синхронную версию, а затем используем асинхронную реализацию для остальной части учебника.
После создания соединения мы используем его для создания набора команд:
RedisCommandssyncCommands = connection.sync();
Теперь у нас есть интуитивно понятный интерфейс для общения с Redis.
Мы можем установить и получить Строковые значения:
syncCommands.set("key", "Hello, Redis!"); String value = syncommands.get("key");
Мы можем работать с хэшами:
syncCommands.hset("recordName", "FirstName", "John"); syncCommands.hset("recordName", "LastName", "Smith"); Maprecord = syncCommands.hgetall("recordName");
Мы расскажем о других поездках позже в этой статье.
Синхронный API салата использует асинхронный API. Блокировка выполняется для нас на командном уровне. Это означает, что несколько клиентов могут совместно использовать синхронное соединение.
4.4. Асинхронные команды
Давайте взглянем на асинхронные команды:
RedisAsyncCommandsasyncCommands = connection.async();
Мы извлекаем набор Redisasynccommand из соединения, аналогично тому, как мы извлекли синхронный набор. Эти команды возвращают RedisFuture (который является CompletableFuture внутренне) :
RedisFutureresult = asyncCommands.get("key");
Руководство по работе с CompletableFuture можно найти здесь.
4.5. Реактивный API
Наконец, давайте посмотрим, как работать с неблокирующим реактивным API:
RedisStringReactiveCommandsreactiveCommands = connection.reactive();
Эти команды возвращают результаты, завернутые в Моно или Поток от Проектный реактор .
Руководство по работе с реактором проекта можно найти здесь.
5. Структуры Данных Redis
Мы кратко рассмотрели строки и хэши выше, давайте посмотрим, как Lettuce реализует остальные структуры данных Redis. Как и следовало ожидать, каждая команда Redis имеет метод с аналогичным именем.
5.1. Списки
Списки-это списки Строк с сохранением порядка вставки. Значения вставляются или извлекаются с любого конца:
asyncCommands.lpush("tasks", "firstTask"); asyncCommands.lpush("tasks", "secondTask"); RedisFutureredisFuture = asyncCommands.rpop("tasks"); String nextTask = redisFuture.get();
В этом примере следующая задача равна ” первая задача “. Push помещает значения в начало списка, а затем pop выводит значения из конца списка.
Мы также можем добавлять элементы с другого конца:
asyncCommands.del("tasks"); asyncCommands.lpush("tasks", "firstTask"); asyncCommands.lpush("tasks", "secondTask"); redisFuture = asyncCommands.lpop("tasks"); String nextTask = redisFuture.get();
Мы начинаем второй пример, удаляя список с помощью del . Затем мы снова вставляем те же значения, но мы используем pop для извлечения значений из начала списка, поэтому следующая задача содержит текст ” secondTask “.
5.2. Наборы
Наборы Redis-это неупорядоченные коллекции Строки похоже на Java Наборы ; нет повторяющихся элементов:
asyncCommands.sadd("pets", "dog"); asyncCommands.sadd("pets", "cat"); asyncCommands.sadd("pets", "cat"); RedisFuture> pets = asyncCommands.smembers("nicknames"); RedisFuture exists = asyncCommands.sismember("pets", "dog");
Когда мы извлекаем набор Redis как Set , размер равен двум, так как дубликат “cat” был проигнорирован. Когда мы запрашиваем Redis о существовании “собаки” с расчленением, ответ истинен.
5.3. Хэши
Ранее мы кратко рассмотрели пример хэшей. Они заслуживают краткого объяснения.
Хэши Redis-это записи с полями и значениями String . Каждая запись также имеет ключ в первичном индексе:
asyncCommands.hset("recordName", "FirstName", "John"); asyncCommands.hset("recordName", "LastName", "Smith"); RedisFuturelastName = syncCommands.hget("recordName", "LastName"); RedisFuture
Мы используем set для добавления полей в хэш, передавая имя хэша, имя поля и значение.
Затем мы извлекаем отдельное значение с get, именем записи и полем. Наконец, мы извлекаем всю запись в виде хэша с помощью hgetall.
5.4. Сортированные наборы
Сортированные наборы содержат значения и ранг, по которому они сортируются. Ранг-это 64-разрядное значение с плавающей запятой.
Элементы добавляются с рангом и извлекаются в диапазоне:
asyncCommands.zadd("sortedset", 1, "one"); asyncCommands.zadd("sortedset", 4, "zero"); asyncCommands.zadd("sortedset", 2, "two"); RedisFuture> valuesForward = asyncCommands.zrange(key, 0, 3); RedisFuture
> valuesReverse = asyncCommands.zrevrange(key, 0, 3);
Второй аргумент для add – это ранг. Мы получаем диапазон по рангу с z диапазоном для возрастающего порядка и zrevrange для нисходящего.
Мы добавили ” ноль ” с рангом 4, поэтому он будет отображаться в конце значений Вперед и в начале значений назад.
6. Сделки
Транзакции позволяют выполнять набор команд за один атомарный шаг. Эти команды гарантированно будут выполняться по порядку и исключительно. Команды от другого пользователя не будут выполняться до завершения транзакции.
Либо все команды выполняются, либо ни одна из них не выполняется. Redis не будет выполнять откат, если один из них завершится неудачей. После вызова exec() все команды выполняются в указанном порядке.
Давайте рассмотрим пример:
asyncCommands.multi(); RedisFutureresult1 = asyncCommands.set("key1", "value1"); RedisFuture result2 = asyncCommands.set("key2", "value2"); RedisFuture result3 = asyncCommands.set("key3", "value3"); RedisFuture execResult = asyncCommands.exec(); TransactionResult transactionResult = execResult.get(); String firstResult = transactionResult.get(0); String secondResult = transactionResult.get(0); String thirdResult = transactionResult.get(0);
Вызов multi запускает транзакцию. При запуске транзакции последующие команды не выполняются до тех пор, пока не будет вызван exec () .
В синхронном режиме команды возвращают null. В асинхронном режиме команды возвращают RedisFuture . Exec возвращает Результат транзакции , содержащий список ответов.
С тех пор как RedisFutures также получая свои результаты, асинхронные клиенты API получают результат транзакции в двух местах.
7. Дозирование
В нормальных условиях Lettuce выполняет команды, как только они вызываются клиентом API.
Это то, что нужно большинству обычных приложений, особенно если они полагаются на получение результатов команд последовательно.
Однако такое поведение неэффективно, если приложениям не нужны результаты немедленно или если большие объемы данных загружаются массово.
Асинхронные приложения могут переопределить это поведение:
commands.setAutoFlushCommands(false); List> futures = new ArrayList<>(); for (int i = 0; i < iterations; i++) { futures.add(commands.set("key-" + i, "value-" + i); } commands.flushCommands(); boolean result = LettuceFutures.awaitAll(5, TimeUnit.SECONDS, futures.toArray(new RedisFuture[0]));
Если для команд setAutoFlush установлено значение false , приложение должно вызывать команды flush вручную. В этом примере мы поставили в очередь несколько команд set , а затем очистили канал. Waitall ожидает завершения всех функций Redis .
Это состояние устанавливается для каждого соединения и влияет на все потоки, использующие соединение. Эта функция неприменима к синхронным командам.
8. Публикация/Подписка
Redis предлагает простую систему обмена сообщениями для публикации/подписки. Подписчики получают сообщения от каналов с помощью команды subscribe . Сообщения не сохраняются; они доставляются пользователям только тогда, когда они подписаны на канал.
Redis использует систему pub/sub для уведомлений о наборе данных Redis, предоставляя клиентам возможность получать события о том, что ключи установлены, удалены, истекли и т. Д.
Более подробную информацию см. в документации здесь .
8.1. Подписчик
A RedisPubSubListener получает сообщения pub/sub. Этот интерфейс определяет несколько методов, но мы просто покажем метод получения сообщений здесь:
public class Listener implements RedisPubSubListener{ @Override public void message(String channel, String message) { log.debug("Got {} on channel {}", message, channel); message = new String(s2); } }
Мы используем redisClient для подключения канала pub/sub и установки прослушивателя:
StatefulRedisPubSubConnectionconnection = client.connectPubSub(); connection.addListener(new Listener()) RedisPubSubAsyncCommands async = connection.async(); async.subscribe("channel");
С установленным прослушивателем мы получаем набор асинхронных команд Redis PubSub и подписываемся на канал.
8.2. Издатель
Публикация-это всего лишь вопрос подключения канала Pub/Sub и получения команд:
StatefulRedisPubSubConnectionconnection = client.connectPubSub(); RedisPubSubAsyncCommands async = connection.async(); async.publish("channel", "Hello, Redis!");
Для публикации требуется канал и сообщение.
8.3. Реактивные подписки
Салат также предлагает реактивный интерфейс для подписки на сообщения pub/sub:
StatefulRedisPubSubConnectionconnection = client .connectPubSub(); RedisPubSubAsyncCommands reactive = connection .reactive(); reactive.observeChannels().subscribe(message -> { log.debug("Got {} on channel {}", message, channel); message = new String(s2); }); reactive.subscribe("channel").subscribe();
Flux , возвращаемый observeChannels , получает сообщения для всех каналов, но поскольку это поток, фильтрацию легко выполнить.
9. Высокая Доступность
Redis предлагает несколько вариантов обеспечения высокой доступности и масштабируемости. Полное понимание требует знания конфигураций серверов Redis, но мы рассмотрим краткий обзор того, как Lettuce поддерживает их.
9.1. Ведущий/Ведомый
Серверы Redis реплицируют себя в конфигурации master/slave. Главный сервер отправляет ведомому устройству поток команд, которые реплицируют главный кэш на ведомое устройство. Redis не поддерживает двунаправленную репликацию, поэтому ведомые устройства доступны только для чтения.
Салат может подключаться к ведущим/ведомым системам, запрашивать у них топологию, а затем выбирать ведомые устройства для операций чтения, что может повысить пропускную способность:
RedisClient redisClient = RedisClient.create(); StatefulRedisMasterSlaveConnectionconnection = MasterSlave.connect(redisClient, new Utf8StringCodec(), RedisURI.create("redis://localhost")); connection.setReadFrom(ReadFrom.SLAVE);
9.2. Страж
Redis Sentinel отслеживает ведущие и ведомые экземпляры и организует отказоустойчивость подчиненных устройств в случае отказа ведущего устройства.
Салат может подключиться к Sentinel, использовать его, чтобы узнать адрес текущего мастера, а затем вернуть ему соединение.
Для этого мы создаем другой RedisURI и подключаем к нему наш redisClient :
RedisURI redisUri = RedisURI.Builder .sentinel("sentinelhost1", "clustername") .withSentinel("sentinelhost2").build(); RedisClient client = new RedisClient(redisUri); RedisConnectionconnection = client.connect();
Мы построили URI с именем хоста (или адресом) первого стража и именем кластера, за которым следует второй адрес стража. Когда мы подключаемся к Sentinel, Lettuce запрашивает у него топологию и возвращает нам соединение с текущим главным сервером.
Полная документация доступна здесь.
9.3. Кластеры
Кластер Redis использует распределенную конфигурацию для обеспечения высокой доступности и высокой пропускной способности.
Кластеры разделяют ключи на 1000 узлов, поэтому транзакции в кластере недоступны:
RedisURI redisUri = RedisURI.Builder.redis("localhost") .withPassword("authentication").build(); RedisClusterClient clusterClient = RedisClusterClient .create(rediUri); StatefulRedisClusterConnectionconnection = clusterClient.connect(); RedisAdvancedClusterCommands syncCommands = connection .sync();
Redis Advanced Cluster Commands содержит набор команд Redis, поддерживаемых кластером, и направляет их к экземпляру, содержащему ключ.
Полная спецификация доступна здесь .
10. Заключение
В этом уроке мы рассмотрели, как использовать салат для подключения и запроса сервера Redis из нашего приложения.
Lettuce поддерживает полный набор функций Redis, с бонусом полностью потокобезопасного асинхронного интерфейса. Он также широко использует интерфейс Java 8 Completion Stage , чтобы предоставить приложениям точный контроль над тем, как они получают данные.
Примеры кода, как всегда, можно найти на GitHub .