Статьи

Redis Client Lettuce 5 GA выпущен

После 13-месячной фазы разработки и 208 решенных заявок, я с удовольствием сообщаю о доступности Lettuce 5.0. Это крупный релиз, в котором появилось несколько важных изменений, новых интересных функций и совместимости с Java 9.

Получить релиз от Maven Central

1
2
3
4
5
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>5.0.0.RELEASE</version>
</dependency>

или загрузите пакет выпуска с GitHub .

Салат 5 представляет динамический API команд Redis. Эта модель программирования позволяет вам объявлять методы команд и вызывать команды в соответствии с вашими потребностями и поддерживать модули Redis, не дожидаясь, пока салат будет поддерживать новые команды.

По умолчанию салат использует собственный транспорт (epoll, kqueue) в системах MacOS, соответствующих Linux, если имеется собственная зависимость.
Салат 5 идет с серьезными изменениями; он удаляет устаревшие интерфейсы RedisConnection и RedisAsyncConnection и их отдельные интерфейсы в пользу StatefulRedisConnection и RedisCommands и др.

Основные переломные изменения:

  1. Мы переместили координаты артефакта из biz.paluch.redis: салат в io.lettuce: салат-ядро
  2. Мы переместили пакеты из biz.paluch.redis в io.lettuce.core. Путь миграции прост, заменив старое имя пакета в ваших импортах новым именем пакета.
  3. Документация переместилась с http://redis.paluch.biz на https://lettuce.io .
  4. Убрал гуаву.
  5. Мы удалили некоторые устаревшие методы, подробности см. Ниже.

Для салата требуется только netty 4.1 (netty 4.0 больше не поддерживается) и Project Reactor 3.1, что приводит нас к следующему изменению:

Реактивный API основан на Реактивных потоках, используя Project Reactor типа Mono и Flux вместо RxJava 1 и Observable .
Если вам требуется RxJava Single и Observable в вашем коде, то используйте адаптеры издателя в rxjava-reactive-streams для адаптации Mono и Flux .

Этот выпуск представляет новое справочное руководство, которое поставляется вместе с обычными артефактами.
Справочное руководство привязано к конкретной версии и не изменяется со временем, например, вики.

1
2
3
4
5
<dependency>
  <groupId>io.lettuce</groupId>
  <artifactId>lettuce-core</artifactId>
  <version>5.0.0.RELEASE</version>
</dependency>

Вы можете найти полный журнал изменений, содержащий все изменения, начиная с первого выпуска 5.0 вехи,
на GitHub . Остерегайтесь переломных изменений.

Спасибо всем авторам, сделавшим Салат 5 возможным. Любая обратная связь приветствуется или подать проблему на GitHub .

API динамических команд Redis

Абстракция интерфейса команд Redis обеспечивает динамический способ безопасного вызова типов команд Redis. Это позволяет вам объявить интерфейс с методами команд, чтобы значительно сократить стандартный код, необходимый для вызова команды Redis.

Redis — это хранилище данных, поддерживающее более 190 документированных команд и более 450 командных перестановок. Развитие команд и отслеживание с помощью будущих модулей является сложной задачей для разработчиков клиентов и пользователей Redis, поскольку нет полного покрытия команд для каждого модуля в одном клиенте Redis.

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

1
2
3
4
5
6
7
RedisCodec<String, String> codec = StringCodec.UTF8;
RedisCommands<String, String> commands = ...
 
String response = redis.dispatch(CommandType.SET, new StatusOutput<>(codec),
                new CommandArgs<>(codec)
                       .addKey(key)
                       .addValue(value));

Центральный интерфейс в абстракции интерфейса Commands Lettuce — это Commands .

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

Команды выполняются синхронно, асинхронно или с моделью реактивного выполнения, в зависимости от объявления метода.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface MyRedisCommands extends Commands {
 
    String get(String key); // Synchronous Execution of GET
 
    @Command("GET")
    byte[] getAsBytes(String key); // Synchronous Execution of GET returning data as byte array
 
    @Command("SET") // synchronous execution applying a Timeout
    String setSync(String key, String value, Timeout timeout);
 
    Future<String> set(String key, String value); // asynchronous SET execution
 
    @Command("SET")
    Mono<String> setReactive(String key, String value); // reactive SET execution using SetArgs
 
    @CommandNaming(split = DOT) // support for Redis Module command notation -> NR.RUN
    double nrRun(String key, int... indexes);
}
 
RedisCommandFactory factory = new RedisCommandFactory(connection);
 
MyRedisCommands commands = factory.getCommands(MyRedisCommands.class);
 
String value = commands.get("key");

Вы получаете совершенно новые возможности с Redis Command Interfaces. Одним из них является прозрачное принятие реактивного типа. Реактивный API Lettuce основан на Reactive Streams, однако с помощью командных интерфейсов вы можете объявить тип возвращаемого значения RxJava 1 или RxJava 2, и Lettuce будет обрабатывать принятие за вас. Пользователи RxJava 1 имеют путь миграции, который позволяет использовать нативные типы без
дальнейшее преобразование.

Смотрите также: https://lettuce.io/core/5.0.0.RELEASE/reference/#redis-command-interfaces

Пакетный интерфейс команд

Интерфейсы команд поддерживают пакетирование команд для сбора нескольких команд в очереди пакетов и очистки пакета за одну запись в транспорт. Пакетирование команд выполняет команды с отложенным характером. Это означает, что на момент вызова результат не доступен. Пакетирование можно использовать только с синхронными методами без возвращаемого значения (void) или асинхронными методами, возвращающими RedisFuture.

Пакетирование команд можно включить на двух уровнях:

  • На уровне класса, аннотируя командный интерфейс с @BatchSize . Все методы участвуют в командном пакетировании.
  • На уровне метода, добавив CommandBatching к аргументам. Метод выборочно участвует в групповом пакетировании.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@BatchSize(50)
interface StringCommands extends Commands {
 
    void set(String key, String value);
 
    RedisFuture<String> get(String key);
 
    RedisFuture<String> get(String key, CommandBatching batching);
}
 
StringCommands commands = …
 
commands.set("key", "value"); // queued until 50 command invocations reached.
                              // The 50th invocation flushes the queue.
 
commands.get("key", CommandBatching.queue()); // invocation-level queueing control
commands.get("key", CommandBatching.flush()); // invocation-level queueing control,
                                              // flushes all queued commands

Подробнее: https://lettuce.io/core/5.0.0.RELEASE/reference/#command-interfaces.batch

Миграция в реактивные потоки

В салате 4.0 появился реактивный API, основанный на RxJava 1 и Observable . Это было началом активной поддержки Redis. Салат использовал Observable повсеместно, так как другие реактивные типы, такие как Single и Completable были еще бета или находились в разработке.

С тех пор многое изменилось в реактивном пространстве. RxJava 2 является преемником RxJava 1, который в настоящее время достиг конца жизни. RxJava 2 не полностью основан на Reactive Streams и базовых версиях Java 6, в то время как другие библиотеки композиций могут извлечь выгоду из Java 8.

Это также означает отсутствие null значений и использование выделенных типов значений для выражения множественности значений ( 0|1 и 0|1|N ) в API.

В Lettuce 5.0 реактивный API использует Project Reactor с его типами Mono и Flux .

Салат 4

1
2
3
4
5
Observable<Long> del(K... keys);
 
Observable<K> keys(K pattern);
 
Observable<V> mget(K... keys);

Салат 5

1
2
3
4
5
Mono<Long> del(K... keys);
 
Flux<K> keys(K pattern);
 
Flux<KeyValue<K, V>> mget(K... keys);

Переключение с RxJava 1 на использование Project Reactor требует переключения библиотеки. Большинство операторов используют похожие или даже одинаковые имена. Если вам необходимо придерживаться RxJava 1, используйте rxjava-reactive-streams для принятия реактивных типов (RxJava 1 <-> Реактивные потоки).

Миграция в реактивные потоки требует переноса значений, чтобы указать на отсутствие значений. Вы найдете различия по сравнению с предыдущим API и API синхронизации / асинхронности в тех случаях, когда команды могут возвращать null значения. Салат 5.0 поставляется с новыми типами Value которые представляют собой монады, инкапсулирующие значение (или их отсутствие).

Смотрите также: https://lettuce.io/core/5.0.0.RELEASE/reference/#reactive-api

Значение, KeyValue и другие типы значений

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

Типы значений основаны на Value и ScoredValue / ScoredValue расширяются оттуда. Значение — это тип оболочки, инкапсулирующий значение или его отсутствие. Value может быть создано разными способами:

1
2
3
4
5
6
7
8
9
Value<String> value = Value.from(Optional.of("hello"));
 
Value<String> value = Value.fromNullable(null);
 
Value<String> value = Value.just("hello");
 
KeyValue<Long, String> value = KeyValue.from(1L, Optional.of("hello"));
  
KeyValue<String, String> value = KeyValue.just("key", "hello");

Он преобразуется в Optional и Stream для интеграции с другими функциональными применениями и позволяет отображать значения.

1
2
3
4
5
6
7
Value.just("hello").stream().filter(…).count();
 
KeyValue.just("hello").optional().isPresent();
 
Value.from(Optional.of("hello")).map(s -> s + "-world").getValue();
 
ScoredValue.just(42, "hello").mapScore(number -> number.doubleValue() * 3.14d).getScore();

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

Стратегии отсрочки / задержки

Благодаря @jongyeol

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

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

Салат идет с различными реализациями отсрочки:

  • Равный Джиттер
  • Полный джиттер
  • Декоррелированный джиттер

Они настроены в ClientResources :

1
2
3
4
5
6
7
DefaultClientResources.builder()
        .reconnectDelay(Delay.decorrelatedJitter())
        .build();
 
DefaultClientResources.builder()
        .reconnectDelay(Delay.equalJitter())
        .build();

Смотрите также: https://www.awsarchitectureblog.com/2015/03/backoff.html и
https://lettuce.io/core/5.0.0.RELEASE/reference/#clientresources.advanced-settings

Новый API для команд Z… RANGE

Команды диапазона Sorted Sets поставляются с оптимизированным API в отношении перегрузок методов. Такие команды, как ZRANGEBYSCORE , ZRANGEBYLEX , ZREMRANGEBYLEX и некоторые другие, теперь объявляют методы, принимающие объекты Range и Limit вместо растущего списка параметров. Новый Range позволяет использовать типы оценок и значений, применяя правильное двоичное кодирование.

4.2 и ранее

1
2
3
4
5
6
7
commands.zcount(key, 1.0, 3.0)
 
commands.zrangebyscore(key, "-inf", "+inf")
 
commands.zrangebyscoreWithScores(key, "[1.0", "(4.0")
 
commands.zrangebyscoreWithScores(key, "-inf", "+inf", 2, 2)

С 5.0

1
2
3
4
5
6
7
commands.zcount(key, Range.create(1.0, 3.0));
 
commands.zrangebyscore(key, Range.unbounded());
 
commands.zrangebyscoreWithScores(key, Range.from(Boundary.including(1.0), Boundary.excluding(4.0));
 
commands.zrangebyscoreWithScores(key, Range.unbounded(), Limit.create(2, 2));

Прощай гуава

Салат 5.0 больше не использует библиотеку Google Guava. Гуава был хорошим другом в те дни, совместимые с Java 6, когда синхронизацию Future и обратные вызовы было неинтересно использовать. Это изменилось с Java 8 и CompletableFuture .

Другие варианты использования, такие как HostAndPort или LoadingCache могут быть встроены или заменены средой Collection в Java 8.

Удаление устаревших интерфейсов и методов

Этот выпуск удаляет устаревшие интерфейсы RedisConnection и RedisAsyncConnection и их отдельные интерфейсы в пользу StatefulRedisConnection и RedisCommands .

Вы заметите небольшие различия при использовании этого API. Транзакционные команды и выбор базы данных больше не доступны через API Redis Cluster, поскольку старый API был получен из автономного API. RedisCommands и RedisAsyncCommands больше не являются Closeable . Пожалуйста, используйте commands.getStatefulConnection().close() чтобы закрыть соединение. Это изменение устраняет неоднозначность при закрытии интерфейса команд при закрытии соединения.

Замена пула подключений

Это заняло довольно много времени, но 4.3 устарела в уже существующей поддержке пула подключений. Это, в частности, RedisClient.pool(…) и RedisClient.asyncPool(…) . Эти методы удалены с помощью салата 5.0.

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

Возвращенные объекты соединения — это прокси, которые возвращают соединение в его пул при вызове close() . StatefulConnection реализует Closeable чтобы разрешить использование try-with-resources.

01
02
03
04
05
06
07
08
09
10
GenericObjectPool<StatefulRedisConnection<String, String>> pool = ConnectionPoolSupport
        .createGenericObjectPool(() -> client.connect(), new GenericObjectPoolConfig());
 
 
try(StatefulRedisConnection<String, String> connection = pool.borrowObject()) {
    // Work
}
 
 
pool.close();

Консенсус обновления топологии Redis Cluster

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

В этом выпуске представлены стратегии PartitionsConsensus для определения наиболее подходящего представления топологии при получении нескольких представлений. Стратегия может быть настроена путем переопределения RedisClusterClient.determinePartitions(Partitions, Map<RedisURI, Partitions>) .

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

Смотрите также: https://github.com/lettuce-io/lettuce-core/issues/355

Асинхронные соединения в Redis Cluster

RedisClusterClient теперь подключается асинхронно без промежуточной блокировки к узлам кластера. Ход соединения распределяется между
несколько потоков, которые запрашивают соединение узла кластера в первый раз. Ранее соединение было последовательно-синхронным. Каждая попытка подключения блокировала последующие попытки других потоков. Если подключение к узлу кластера истекло, потоки были оштрафованы с растущим временем ожидания. Если, скажем, 10 потоков ожидали соединения, последний поток должен был ждать до 10 раз превышения времени ожидания соединения.

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

Redis Cluster Pub / Sub по выбору узлов

RedisClusterClient.connectPubSub() теперь возвращает StatefulRedisClusterPubSubConnection который позволяет регистрировать RedisClusterPubSubListener и подписку на определенных узлах кластера.

Специфичные для кластера подписки позволяют использовать уведомления пространства ключей. Уведомления пространства ключей отличаются от пользовательских публикаций Pub / Sub, поскольку уведомления пространства ключей не транслируются на весь кластер, а публикуются только на узле, где происходит уведомление. Распространенным вариантом использования является истечение срока действия ключа в кластере.

1
2
3
4
5
6
7
8
StatefulRedisClusterPubSubConnection connection = client.connectPubSub();
 
connection.addListener(…);
 
connection.setNodeMessagePropagation(true);
 
RedisClusterPubSubCommands<String, String> sync = connection.sync();
sync.slaves().commands().psubscribe("__key*__:expire");

Родные Транспорты

Теперь салат по умолчанию использует собственные транспорты, если операционная система квалифицирована и доступны зависимости. Lettuce поддерживает epoll (в системах на основе Linux) начиная с 4.0 и начиная с этой версии kqueue (системы на основе BSD, такие как macOS).

Использование Epoll можно отключить с помощью системных свойств, установив io.lettuce.core.epoll=false . Аналогичным образом, kqueue можно отключить
с io.lettuce.core.kqueue=false .

Эполл зависимость:

1
2
3
4
5
6
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-epoll</artifactId>
    <version>${netty-version}</version>
    <classifier>linux-x86_64</classifier>
</dependency>

Kqueue зависимость:

1
2
3
4
5
6
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-kqueue</artifactId>
    <version>${netty-version}</version>
    <classifier>osx-x86_64</classifier>
</dependency>
Опубликовано на Java Code Geeks с разрешения Марка Палуха, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Redis Client Lettuce 5 GA выпущен

Мнения, высказанные участниками Java Code Geeks, являются их собственными.