レタスの紹介 - Java Redisクライアント

レタス入門– Java Redisクライアント

1. 概要

この記事は、Redis JavaクライアントであるLettuceの紹介です。

Redisは、データベース、キャッシュ、またはメッセージブローカーとして使用できるメモリ内のキーと値のストアです。 データは、Redisのメモリ内データ構造のキーを操作するcommandsを使用して、追加、クエリ、変更、および削除されます。

Lettuceは、データ構造、pub / subメッセージング、高可用性サーバー接続など、完全なRedisAPIの同期通信と非同期通信の両方の使用をサポートしています。

2. なぜレタス?

ジェダイin one of the previous posts.について説明しました。レタスの違いは何ですか?

最も重要な違いは、Java 8のCompletionStageインターフェースを介した非同期サポートと、リアクティブストリームのサポートです。 以下で説明するように、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のインスタンスを少なくとも1つ、2つインストールして実行する必要があります(センチネルモードが正しく機能するには3つのサーバーが必要です)。この記事では、4.0.xを使用しています。現時点での最新の安定バージョン。

LinuxおよびMacOSのダウンロードなど、Redisの使用を開始する方法の詳細については、hereを参照してください。

Redisは正式にはWindowsをサポートしていませんが、サーバーhereのポートがあります。 RedisをDockerで実行することもできます。これは、Windows 10のより良い代替手段であり、起動して実行するための高速な方法です。

4. つながり

4.1. サーバーへの接続

Redisへの接続は4つのステップで構成されています。

  1. Redis URIの作成

  2. URIを使用してRedisClientに接続する

  3. Redis接続を開く

  4. RedisCommandsのセットを生成する

実装を見てみましょう:

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

StatefulRedisConnectionはそれがどのように聞こえるかです。サーバーへの接続を維持し、必要に応じて再接続するRedisサーバーへのスレッドセーフ接続。 接続が確立されると、それを使用してRedisコマンドを同期的または非同期的に実行できます。

RedisClientは、Redisサーバーと通信するためのNettyリソースを保持しているため、かなりのシステムリソースを使用します。 複数の接続を必要とするアプリケーションは、単一のRedisClient.を使用する必要があります

4.2. URIを再利用する

静的ファクトリメソッドにURIを渡すことにより、RedisClientを作成します。

レタスは、Redis URIのカスタム構文を活用しています。 これはスキーマです:

redis :// [[email protected]] host [: port] [/ database]
  [? [timeout=timeout[d|h|m|s|ms|us|ns]]
  [&_database=database_]]

4つのURIスキームがあります。

  • redis –スタンドアロンのRedisサーバー

  • rediss –SSL接続を介したスタンドアロンのRedisサーバー

  • redis-socket –Unixドメインソケットを介したスタンドアロンRedisサーバー

  • redis-sentinel – RedisSentinelサーバー

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について説明します。

Lettuce同期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();

これらのコマンドは、Project Reactor.からMonoまたはFluxでラップされた結果を返します

Project Reactorの操作ガイドはhere.にあります。

5. Redisデータ構造

上記の文字列とハッシュについて簡単に説明しました。レタスが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();

2番目の例は、delでリストを削除することから始めます。 次に、同じ値を再度挿入しますが、lpopを使用してリストの先頭から値をポップするため、nextTaskは「secondTask」テキストを保持します。

5.2. Sets

Redisセットは、JavaSetsと同様のStringsの順序付けられていないコレクションです。重複する要素はありません:

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”が無視されたため、サイズは2になります。 sismember,“dog”の存在をRedisに照会すると、応答は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の2番目の引数はランクです。 ランクごとに範囲を取得します。昇順はzrange、降順はzrevrangeです。

ランク4の「zero」を追加したため、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クライアントは2か所でトランザクション結果を受け取ります。

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

observeChannelsによって返されるFluxは、すべてのチャネルのメッセージを受信しますが、これはストリームであるため、フィルタリングは簡単に実行できます。

9. 高可用性

Redisは、高可用性とスケーラビリティのためのいくつかのオプションを提供します。 完全に理解するには、Redisサーバー構成の知識が必要ですが、Lettuceがそれらをどのようにサポートするかについて簡単に説明します。

9.1. Master/Slave

Redisサーバーは、マスター/スレーブ構成で自身を複製します。 マスターサーバーは、マスターキャッシュをスレーブに複製するコマンドのストリームをスレーブに送信します。 Redis doesn’t support bi-directional replication, so slaves are read-only.

Lettuceは、マスター/スレーブシステムに接続し、トポロジを照会してから、読み取り操作用のスレーブを選択することができます。これにより、スループットが向上します。

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

最初のSentinelのホスト名(またはアドレス)とクラスター名、続いて2番目のセンチネルアドレスでURIを作成しました。 Sentinelに接続すると、Lettuceはトポロジについてクエリを実行し、現在のマスターサーバーへの接続を返します。

完全なドキュメントが利用可能ですhere.

9.3. クラスター

Redisクラスターは、分散構成を使用して、高可用性と高スループットを提供します。

クラスターは最大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. 結論

このチュートリアルでは、アプリケーション内からLettuceを使用してRedisサーバーに接続およびクエリする方法を検討しました。

Lettuceは、完全にスレッドセーフな非同期インターフェイスというボーナスとともに、Redis機能の完全なセットをサポートしています。 また、Java 8のCompletionStageインターフェースを広範囲に使用して、アプリケーションがデータを受信する方法をきめ細かく制御できるようにします。

いつものように、コードサンプルはover on GitHubで見つけることができます。