Введение в салат - Java Redis Client

Введение в салат - клиент Java Redis

1. обзор

Эта статья представляет собой введение вLettuce, клиент JavaRedis.

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

Салат-латук поддерживает как синхронную, так и асинхронную связь с использованием полного API Redis, включая его структуры данных, обмен сообщениями pub / sub и соединения с сервером высокой доступности.

2. Почему салат?

Мы рассмотрели джедаевin one of the previous posts.. Что отличает салат-латук от других?

Наиболее существенным отличием является асинхронная поддержка через интерфейс Java 8CompletionStage и поддержка реактивных потоков. Как мы увидим ниже, Lettuce предлагает естественный интерфейс для выполнения асинхронных запросов с сервера базы данных Redis и для создания потоков.

Он также использует Netty для связи с сервером. Это делает API более «тяжелым», но также лучше подходит для совместного использования соединения с несколькими потоками.

3. Настроить

3.1. зависимость

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


    io.lettuce
    lettuce-core
    5.0.1.RELEASE

Последнюю версию библиотеки можно проверить наGithub repository или наMaven Central.

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

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

Дополнительную информацию о начале работы с Redis можно найти наhere, включая загрузки для Linux и MacOS.

Redis официально не поддерживает Windows, но есть порт для сервераhere. Мы также можем запустить Redis вDocker, что является лучшей альтернативой для Windows 10 и быстрым способом начать работу.

4. связи

4.1. Подключение к серверу

Подключение к Redis состоит из четырех шагов:

  1. Создание Redis URI

  2. Использование URI для подключения кRedisClient

  3. Открытие соединения Redis

  4. Создание набораRedisCommands

Посмотрим на реализацию:

RedisClient redisClient = RedisClient
  .create("redis://[email protected]:6379/");
StatefulRedisConnection connection
 = redisClient.connect();

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

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

4.2. Redis URI

Мы создаемRedisClient, передав 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 реализует как синхронные, так и асинхронные версии. Мы кратко рассмотрим синхронную версию, а затем используем асинхронную реализацию для остальной части учебника.

После того, как мы создаем соединение, мы используем его для создания набора команд:

RedisCommands syncCommands = connection.sync();

Теперь у нас есть интуитивно понятный интерфейс для общения с Redis.

Мы можем установить и получитьString values:

syncCommands.set("key", "Hello, Redis!");

String value = syncommands.get(“key”);

Мы можем работать с хешами:

syncCommands.hset("recordName", "FirstName", "John");
syncCommands.hset("recordName", "LastName", "Smith");
Map record = syncCommands.hgetall("recordName");

Мы расскажем больше о Redis позже в этой статье.

В синхронном API для салата используется асинхронный API. Блокировка делается для нас на командном уровне. This means that more than one client can share a synchronous connection.с

4.4. Асинхронные команды

Давайте посмотрим на асинхронные команды:

RedisAsyncCommands asyncCommands = connection.async();

Мы получаем наборRedisAsyncCommands из соединения, аналогично тому, как мы получали синхронный набор. Эти команды возвращаютRedisFuture (который внутренне являетсяCompletableFuture):

RedisFuture result = asyncCommands.get("key");

Руководство по работе сCompletableFuture можно найти вhere.

4.5. Реактивный API

Наконец, давайте посмотрим, как работать с неблокирующим реактивным API:

RedisStringReactiveCommands reactiveCommands = connection.reactive();

Эти команды возвращают результаты, заключенные вMono илиFlux изProject Reactor.

Руководство по работе с Project Reactor можно найтиhere.

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

Мы кратко рассмотрели строки и хеши выше, давайте посмотрим, как Lettuce реализует остальные структуры данных Redis. Как и следовало ожидать, каждая команда Redis имеет метод с одинаковым названием.

5.1. Списки

Lists are lists of Strings with the order of insertion preserved. Значения вставляются или извлекаются с любого конца:

asyncCommands.lpush("tasks", "firstTask");
asyncCommands.lpush("tasks", "secondTask");
RedisFuture redisFuture = asyncCommands.rpop("tasks");

String nextTask = redisFuture.get();

В этом примереnextTask равно «firstTask». Lpush помещает значения в начало списка, а затемrpop выталкивает значения из конца списка.

Мы также можем вытолкнуть элементы с другого конца:

asyncCommands.del("tasks");
asyncCommands.lpush("tasks", "firstTask");
asyncCommands.lpush("tasks", "secondTask");
redisFuture = asyncCommands.lpop("tasks");

String nextTask = redisFuture.get();

Второй пример мы начинаем с удаления списка сdel. Затем мы снова вставляем те же значения, но мы используемlpop для извлечения значений из заголовка списка, поэтомуnextTask содержит текст «secondTask».

5.2. Sets

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

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 о существовании“dog” с помощьюsismember,, ответ будетtrue.

5.3. Хэш

Мы кратко рассмотрели пример хэшей ранее. Они заслуживают быстрого объяснения.

Redis Hashes are records with String fields and values. Каждая запись также имеет ключ в первичном индексе:

asyncCommands.hset("recordName", "FirstName", "John");
asyncCommands.hset("recordName", "LastName", "Smith");

RedisFuture lastName
  = syncCommands.hget("recordName", "LastName");
RedisFuture> record
  = syncCommands.hgetall("recordName");

Мы используемhset для добавления полей в хэш, передавая имя хеша, имя поля и значение.

Затем мы получаем отдельное значение сhget, именем записи и поля. Наконец, мы получаем всю запись как хэш сhgetall.

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

Sorted Sets contains values and a rank, by which they are sorted. Ранг - это 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);

Второй аргументzadd - это ранг. Мы получаем диапазон по рангу сzrange для возрастания иzrevrange для убывания.

Мы добавили «zero» с рангом 4, поэтому он появится в концеvaluesForward и в началеvaluesReverse..

6. операции

Транзакции позволяют выполнять набор команд за один атомарный шаг. Эти команды гарантированно выполняются по порядку и исключительно. Команды другого пользователя не будут выполняться до завершения транзакции.

Либо все команды выполнены, либо ни одна из них не выполнена. Redis will not perform a rollback if one of them fails. После вызоваexec() все команды выполняются в указанном порядке.

Давайте посмотрим на пример:

asyncCommands.multi();

RedisFuture result1 = 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 возвращаетTransactionResult, содержащий список ответов.

Поскольку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]));

Если для setAutoFlushCommands установлено значениеfalse, приложение должно вызыватьflushCommands вручную. В этом примере мы поставили в очередь несколько командset, а затем очистили канал. AwaitAll ожидает завершения всехRedisFutures.

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

8. Publish/Subscribe

Redis предлагает простую систему обмена сообщениями публикации / подписки. Подписчики получают сообщения из каналов с помощью командыsubscribe. Сообщения не сохраняются; они доставляются пользователям только тогда, когда они подписаны на канал.

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

Смотрите документациюhere для более подробной информации.

8.1. подписчик

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 канала и установки слушателя:

StatefulRedisPubSubConnection connection
 = client.connectPubSub();
connection.addListener(new Listener())

RedisPubSubAsyncCommands async
 = connection.async();
async.subscribe("channel");

Установив прослушиватель, мы получаем наборRedisPubSubAsyncCommands и подписываемся на канал.

8.2. издатель

Публикация - это всего лишь вопрос подключения канала Pub / Sub и получения команд:

StatefulRedisPubSubConnection connection
  = client.connectPubSub();

RedisPubSubAsyncCommands async
  = connection.async();
async.publish("channel", "Hello, Redis!");

Публикация требует канала и сообщения.

8.3. Реактивные подписки

Салат также предлагает реактивный интерфейс для подписки на сообщения pub / sub:

StatefulRedisPubSubConnection connection = 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, но мы кратко рассмотрим, как их поддерживает салат-латук.

9.1. Master/Slave

Серверы Redis реплицируются в конфигурации «главный / подчиненный». Главный сервер отправляет подчиненному поток команд, которые копируют главный кэш на подчиненное устройство. Redis doesn’t support bi-directional replication, so slaves are read-only.с

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

RedisClient redisClient = RedisClient.create();

StatefulRedisMasterSlaveConnection connection
 = 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);

RedisConnection connection = client.connect();

Мы создали URI с именем хоста (или адресом) первого Стража и именем кластера, за которым следует второй сторожевой адрес. Когда мы подключаемся к Sentinel, Lettuce запрашивает его о топологии и возвращает нам соединение с текущим главным сервером.

Доступна полная документацияhere.

9.3. Кластеры

Redis Cluster использует распределенную конфигурацию для обеспечения высокой доступности и высокой пропускной способности.

Кластеры сегментируют ключи до 1000 узлов, поэтому транзакции в кластере недоступны:

RedisURI redisUri = RedisURI.Builder.redis("localhost")
  .withPassword("authentication").build();
RedisClusterClient clusterClient = RedisClusterClient
  .create(rediUri);
StatefulRedisClusterConnection connection
 = clusterClient.connect();
RedisAdvancedClusterCommands syncCommands = connection
  .sync();

RedisAdvancedClusterCommands содержит набор команд Redis, поддерживаемых кластером, перенаправляя их на экземпляр, содержащий ключ.

Доступна полная спецификацияhere.

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

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

Салат поддерживает полный набор функций Redis с бонусом полностью поточно-ориентированного асинхронного интерфейса. Он также широко использует интерфейсCompletionStage в Java 8, чтобы дать приложениям детальный контроль над тем, как они получают данные.

Примеры кода, как всегда, можно найтиover on GitHub.

Related