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

1概要

この記事はhttp://redis.io[Redis]Javaクライアントhttps://lettuce.io[Lettuce]の紹介です。

Redisは、データベース、キャッシュ、またはメッセージブローカーとして使用できるメモリ内のKey-Valueストアです。データは、Redisのインメモリデータ構造のキーを操作するhttps://redis.io/commands[commands]を使用して追加、照会、変更、および削除されます。

Lettuceは、データ構造、パブ/サブメッセージング、高可用性サーバー接続など、完全なRedis APIの同期および非同期通信使用をサポートしています。

2なぜレタスですか?

私たちはJedisのリンクをカバーしました:/jedis-java-redis-client-library[前の記事の1つで]。レタスの違いは何ですか?

最も大きな違いは、Java 8の CompletionStage インターフェースによる非同期サポートとReactive Streamsのサポートです。以下に示すように、LettuceはRedisデータベースサーバーからの非同期リクエストを作成し、ストリームを作成するための自然なインターフェースを提供します。

サーバーとの通信にもNettyを使用します。これは「より重い」APIになりますが、複数のスレッドとの接続の共有にも適しています。

3セットアップ

3.1. 依存

pom.xml で必要な唯一の依存関係を宣言することから始めましょう。

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>5.0.1.RELEASE</version>
</dependency>

最新版のライブラリはhttps://github.com/lettuce-io/lettuce-core[Github repository]またはhttps://search.maven.org/classic/#search%7Cgav%7C1で確認できます。 %7Cg%3A%22io.lettuce%22%20AND%20a%3A%22lettuce-core%22[Maven Central。]

3.2. Redisのインストール

クラスタリングまたはセンチネルモードをテストする場合は、少なくとも1つのRedisインスタンスをインストールして実行する必要があります(ただしセンチネルモードでは正しく機能するために3つのサーバーが必要です)。現時点で最新の安定版。

Redisを始めるためのより多くの情報はLinuxとMacOSのためのダウンロードを含むhttps://redis.io/topics/quickstart[ここ]で見つけることができます。

RedisはWindowsを正式にはサポートしていませんが、サーバーのポートはhttps://github.com/MicrosoftArchive/redis[ここ]です。 Docker でRedisを実行することもできます。これは、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<String, String> connection
 = redisClient.connect();

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

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

4.2. Redis URI

静的ファクトリメソッドにURIを渡して RedisClient を作成します。

レタスはRedis URIのカスタム構文を利用します。これはスキーマです。

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

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

**  __redis__  - スタンドアロンRedisサーバー

**  __rediss__  -  SSL接続経由のスタンドアロンRedisサーバー

**  __redis-socket__  -  Unixドメインソケット経由のスタンドアロンRedisサーバー

**  __redis-sentinel__  -  Redis Sentinelサーバ

Redisデータベースインスタンスは、URLパスの一部として、または追加のパラメータとして指定できます。両方を指定した場合は、パラメータの方が優先されます。

上記の例では、__String__表現を使用しています。レタスには、接続を構築するための__RedisURI__クラスもあります。それは__Builder__パターンを提供します。

[source,java,gutter:,true]

RedisURI.Builder .redis("localhost", 6379).auth("password") .database(1).build();

そしてコンストラクタ:

[source,java,gutter:,true]

new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);

[[synchronous__commands]]

====  **  4.3. 同期コマンド**

Jedisと同様に、Lettuceは完全なRedisコマンドセットをメソッドの形で提供します。

ただし、Lettuceは同期バージョンと非同期バージョンの両方を実装しています。

同期バージョンについて簡単に見てから、チュートリアルの残りの部分では非同期実装を使用します。

接続を作成したら、それを使用してコマンドセットを作成します。

[source,java,gutter:,true]

RedisCommands<String, String> syncCommands = connection.sync();

これでRedisと通信するための直感的なインターフェースができました。

__String値を設定および取得できます。

[source,java,gutter:,true]

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

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

ハッシュを扱うことができます。

[source,java,gutter:,true]

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

この記事の後半で、より多くのRedisについて説明します。

レタス同期APIは非同期APIを使用します。ブロックはコマンドレベルで行われます。これは、複数のクライアントが同期接続を共有できることを意味します。

[[asynchronous__commands]]

====  **  4.4. 非同期コマンド**

非同期コマンドを見てみましょう。

[source,java,gutter:,true]

RedisAsyncCommands<String, String> asyncCommands = connection.async();

同期セットの取得方法と同様に、接続から__RedisAsyncCommands__のセットを取得します。これらのコマンドは__RedisFuture__(内部的には__CompletableFuture__です)__を返します。

[source,java,gutter:,true]

RedisFuture<String> result = asyncCommands.get("key");

__CompletableFuture__を扱うためのガイドはlink:/java-completablefuture[ここ]にあります。

[[reactive__commands]]

====  **  4.5. リアクティブAPI **

最後に、ノンブロッキングリアクティブAPIの使用方法を見てみましょう。

[source,java,gutter:,true]

RedisStringReactiveCommands<String, String> reactiveCommands = connection.reactive();

** これらのコマンドはhttps://github.com/reactor/reactor-core[Project Reactor ____.____]から__Mono__または__Flux__にラップされた結果を返します。

Project Reactorを使った作業の手引きはlink:/reactor-core[ここ]にあります

[[data__structures]]

===  **  5 Redisデータ構造**

上記の文字列とハッシュについて簡単に説明しました。レタスが残りのRedisのデータ構造をどのように実装するかを見てみましょう。予想通り、各Redisコマンドには同じ名前のメソッドがあります。

====  **  5.1. リスト**

** リストは、挿入順を保持した__Strings__のリストです。** 値は、両端から挿入または取得されます。

[source,java,gutter:,true]

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

String nextTask = redisFuture.get();

この例では、__nextTask__は「__firstTask__」と同じです。 __Lpush__は値をリストの先頭にプッシュし、次に__rpop__はリストの末尾から値をポップします。

反対側から要素をポップすることもできます。

[source,java,gutter:,true]

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. セット**

**  RedisセットはJavaの__Sets__に似た__Strings__の順不同のコレクションです。重複する要素はありません。**

[source,java,gutter:,true]

asyncCommands.sadd("pets", "dog"); asyncCommands.sadd("pets", "cat"); asyncCommands.sadd("pets", "cat");

RedisFuture<Set<String>> pets = asyncCommands.smembers("nicknames"); RedisFuture<Boolean> exists = asyncCommands.sismember("pets", "dog");

Redisセットを__Set__として取得すると、重複した__“ cat” __は無視されたため、サイズは2になります。 __sismemberで__ "dog" __の存在をRedisに問い合わせると、応答は__true.__です。

====  **  5.3. ハッシュ**

先にハッシュの例を簡単に見ました。それらは簡単な説明に値します。

**  Redisハッシュは__String__フィールドと値を持つレコードです。** 各レコードは主インデックスにキーも持っています。

[source,java,gutter:,true]

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

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

__hset__を使ってハッシュにフィールドを追加し、ハッシュの名前、フィールドの名前、そして値を渡します。

次に、__hget、__レコード名、およびフィールドを使用して個々の値を取得します。最後に、__hgetall.__を使用してレコード全体をハッシュとして取得します。

[[sorted__sets]]

====  **  5.4. ソートセット**

** ソートセットには、値とランクが含まれています。ランクは64ビット浮動小数点値です。

アイテムはランク付きで追加され、範囲内で取得されます。

[source,java,gutter:,true]

asyncCommands.zadd("sortedset", 1, "one"); asyncCommands.zadd("sortedset", 4, "zero"); asyncCommands.zadd("sortedset", 2, "two");

RedisFuture<List<String>> valuesForward = asyncCommands.zrange(key, 0, 3); RedisFuture<List<String>> valuesReverse = asyncCommands.zrevrange(key, 0, 3);

__zadd__の2番目の引数はランクです。昇順には__zrange__、降順には__zrevrange__を使用してランクで範囲を検索します。

ランクが4の“ __zero__”を追加したので、それは__valuesForward__の終わりと__valuesReverseの先頭に表示されます。

===  **  6. トランザクション**

トランザクションを使用すると、単一のアトミックステップで一連のコマンドを実行できます。これらのコマンドは、順番通りに排他的に実行されることが保証されています。他のユーザーからのコマンドは、トランザクションが終了するまで実行されません。

すべてのコマンドが実行されるか、または実行されません。 ** いずれかが失敗した場合、Redisはロールバックを実行しません**  __exec()__が呼ばれると、すべてのコマンドは指定された順序で実行されます。

例を見てみましょう。

[source,java,gutter:,true]

asyncCommands.multi();

RedisFuture<String> result1 = asyncCommands.set("key1", "value1"); RedisFuture<String> result2 = asyncCommands.set("key2", "value2"); RedisFuture<String> result3 = asyncCommands.set("key3", "value3");

RedisFuture<TransactionResult> 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クライアントによって呼び出されるとすぐにコマンドを実行します。

これは、最も一般的なアプリケーションが望んでいることです。特に、コマンドの結果を連続して受け取ることに依存している場合はそうです。

ただし、アプリケーションがすぐに結果を必要としない場合、または大量のデータがまとめてアップロードされている場合、この動作は効率的ではありません。

非同期アプリケーションはこの動作をオーバーライドできます。

[source,java,gutter:,true]

commands.setAutoFlushCommands(false);

List<RedisFuture<?>> 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__が完了するのを待ちます。

** この状態は接続ごとに設定され、接続を使用するすべてのスレッドに影響します。この機能は同期コマンドには適用されません。**

[[pubsub]]

===  **  8公開/購読**

Redisはシンプルなパブリッシュ/サブスクライブメッセージングシステムを提供しています。加入者は__subscribe__コマンドを使用してチャネルからのメッセージを消費します。メッセージは持続しません。彼らがチャンネルを購読しているときだけ、それらはユーザーに届けられます。

RedisはRedisデータセットに関する通知にpub/subシステムを使用して、クライアントにキーの設定、削除、期限切れなどに関するイベントを受信する機能を提供します。

詳細はhttps://redis.io/topics/notifications[ここ]を参照してください。

====  **  8.1. 加入者**

__RedisPubSubListener__は、pub/subメッセージを受け取ります。このインターフェースはいくつかのメソッドを定義していますが、ここでメッセージを受信するためのメソッドを示すだけです

[source,java,gutter:,true]

public class Listener implements RedisPubSubListener<String, String> {

    @Override
    public void message(String channel, String message) {
        log.debug("Got {} on channel {}",  message, channel);
        message = new String(s2);
    }
}
パブ/サブチャンネルを接続してリスナーをインストールするのに__RedisClient__を使います:

[source,java,gutter:,true]

StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub(); connection.addListener(new Listener())

RedisPubSubAsyncCommands<String, String> async = connection.async(); async.subscribe("channel");

リスナーをインストールした状態で、__RedisPubSubAsyncCommands__のセットを取得し、チャンネルを購読します。

====  **  8.2. 出版社**

公開は、Pub/Subチャンネルを接続してコマンドを取得するだけの問題です。

[source,java,gutter:,true]

StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub();

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

公開にはチャンネルとメッセージが必要です。

[[reactive__pubsub]]

====  **  8.3. リアクティブサブスクリプション**

Lettuceはpub/subメッセージを購読するためのリアクティブインタフェースも提供します。

[source,java,gutter:,true]

StatefulRedisPubSubConnection<String, String> connection = client .connectPubSub();

RedisPubSubAsyncCommands<String, String> reactive = connection .reactive();

reactive.observeChannels().subscribe(message → { log.debug("Got {} on channel {}", message, channel); message = new String(s2); }); reactive.subscribe("channel").subscribe();

__observeChannels__から返された__Flux__はすべてのチャンネルのメッセージを受信しますが、これはストリームなので、フィルタリングは簡単です。

[[clustering]]

===  **  9高可用性

Redisは、高可用性とスケーラビリティのためにいくつかのオプションを提供しています。

完全な理解にはRedisサーバーの設定に関する知識が必要ですが、Lettuceがそれらをどのようにサポートしているかについての簡単な概要を説明します。

[[master__slave]]

====  **  9.1. マスター/スレーブ**

Redisサーバーはマスター/スレーブ構成で自分自身を複製します。マスターサーバーは、マスターキャッシュをスレーブに複製する一連のコマンドをスレーブに送信します。 Redisは双方向レプリケーションをサポートしていないため、スレーブは読み取り専用です。

レタスはマスター/スレーブシステムに接続し、それらにトポロジーを問い合わせ、そして読み込み操作のためにスレーブを選択することができ、それはスループットを改善することができます

[source,java,gutter:,true]

RedisClient redisClient = RedisClient.create();

StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), RedisURI.create("redis://localhost"));

connection.setReadFrom(ReadFrom.SLAVE);

====  **  9.2. センチネル**

Redis Sentinelは、マスターインスタンスとスレーブインスタンスを監視し、マスターフェールオーバーの発生時にスレーブへのフェールオーバーを調整します。

LettuceはSentinelに接続し、それを使って現在のマスタのアドレスを見つけ、それから接続を返すことができます。

これを行うには、異なる__RedisURI__を構築し、それに__RedisClient__を接続します。

[source,java,gutter:,true]

RedisURI redisUri = RedisURI.Builder .sentinel("sentinelhost1", "clustername") .withSentinel("sentinelhost2").build(); RedisClient client = new RedisClient(redisUri);

RedisConnection<String, String> connection = client.connect();

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

完全なドキュメントはhttps://redis.io/topics/sentinel[ここにあります。]

[[clustering]]

====  **  9.3. クラスター**

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

** クラスタは最大1000個のノードにまたがって鍵を分割するため、クラスタ内でトランザクションは利用できません。

[source,java,gutter:,true]

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

__RedisAdvancedClusterCommands__は、クラスターでサポートされているRedisコマンドのセットを保持し、それらをキーを保持するインスタンスにルーティングします。

完全な仕様はhttps://redis.io/topics/cluster-spec[ここ]にあります。

[[clustering]]

===  **  10結論**

このチュートリアルでは、レタスを使用してアプリケーション内からRedisサーバーに接続して照会する方法を調べました。

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

いつものように、コードサンプルはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/redis[GitHubに追加]を見つけることができます。
前の投稿:Java String.intern()
次の投稿:@Componentと@Repository、およびSpringの@Service