Jedisの紹介 - Java Redisクライアントライブラリ

Jedisの紹介– Java Redisクライアントライブラリ

1. 概要

この記事は、Redis用のJavaのクライアントライブラリであるan introduction to Jedisです。これは、ディスク上にも保持できる人気のあるインメモリデータ構造ストアです。 データを保持するためにキーストアベースのデータ構造によって駆動され、データベース、キャッシュ、メッセージブローカーなどとして使用できます。

最初に、Jedisがどのような状況で役立つか、そしてそれが何であるかを説明します。

後続のセクションでは、さまざまなデータ構造について詳しく説明し、トランザクション、パイプライン処理、パブリッシュ/サブスクライブ機能について説明します。 接続プーリングとRedisクラスターで締めくくります。

2. なぜジェダイ?

Redisは、最もよく知られているクライアントライブラリをofficial siteに一覧表示します。 ジェダイには複数の選択肢がありますが、現在、推奨スターに値するのは、lettuceRedissonの2つだけです。

これら2つのクライアントには、スレッドセーフ、透過的な再接続処理、非同期APIなど、Jedisにはないすべての独自の機能があります。

ただし、それは小さく、他の2つよりもかなり高速です。 また、Spring Framework開発者が選択したクライアントライブラリであり、3つすべてのコミュニティの中で最大のコミュニティを持っています。

3. Mavenの依存関係

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


    redis.clients
    jedis
    2.8.1

ライブラリの最新バージョンをお探しの場合は、this pageをご覧ください。

4. Redisのインストール

Redisの最新バージョンの1つをインストールして起動する必要があります。 現在、最新の安定バージョン(3.2.1)を実行していますが、3.x以降のバージョンであれば問題ありません。

LinuxおよびMacintosh用のRedisに関するhereの詳細情報を検索してください。これらには、非常によく似た基本的なインストール手順があります。 Windowsは公式にはサポートされていませんが、このportは適切に維持されています。

その後、Javaコードから直接飛び込んで接続できます。

Jedis jedis = new Jedis();

デフォルトコンストラクターは、デフォルト以外のポートまたはリモートマシンでサービスを開始していない限り正常に動作します。その場合、コンストラクターにパラメーターとして正しい値を渡すことで正しく構成できます。

5. Redisデータ構造

ネイティブ操作コマンドのほとんどがサポートされており、便利なことに、通常は同じメソッド名を共有しています。

5.1. 文字列

文字列は、Redis値の最も基本的な種類であり、単純なキー値データ型を永続化する必要がある場合に役立ちます。

jedis.set("events/city/rome", "32,15,223,828");
String cachedResponse = jedis.get("events/city/rome");

変数cachedResponseは、値32,15,223,828を保持します。 後で説明する有効期限のサポートと組み合わせて、Webアプリケーションおよびその他のキャッシュ要件で受信したHTTP要求に対して、高速で簡単に使用できるキャッシュレイヤーとして機能できます。

5.2. リスト

Redisリストは、単純に文字列のリストであり、挿入順序でソートされており、たとえばメッセージキューを実装するのに理想的なツールです。

jedis.lpush("queue#tasks", "firstTask");
jedis.lpush("queue#tasks", "secondTask");

String task = jedis.rpop("queue#tasks");

変数taskは、値firstTaskを保持します。 任意のオブジェクトをシリアル化して文字列として保持できるため、キュー内のメッセージは必要に応じてより複雑なデータを運ぶことができることに注意してください。

5.3. Sets

Redisセットは、繰り返されるメンバーを除外するときに便利な文字列の順序付けられていないコレクションです。

jedis.sadd("nicknames", "nickname#1");
jedis.sadd("nicknames", "nickname#2");
jedis.sadd("nicknames", "nickname#1");

Set nicknames = jedis.smembers("nicknames");
boolean exists = jedis.sismember("nicknames", "nickname#1");

Javaセットnicknamesのサイズは2になり、nickname#1の2番目の追加は無視されました。 また、exists変数の値はtrueになります。メソッドsismemberを使用すると、特定のメンバーの存在をすばやく確認できます。

5.4. ハッシュ

Redisハッシュは、StringフィールドとString値の間のマッピングです。

jedis.hset("user#1", "name", "Peter");
jedis.hset("user#1", "job", "politician");

String name = jedis.hget("user#1", "name");

Map fields = jedis.hgetAll("user#1");
String job = fields.get("job");

ご覧のとおり、ハッシュは、オブジェクト全体を取得する必要がないため、オブジェクトのプロパティに個別にアクセスする場合に非常に便利なデータ型です。

5.5. ソートセット

並べ替えられたセットは、各メンバーに関連付けられたランキングがあるセットのようなもので、並べ替えに使用されます。

Map scores = new HashMap<>();

scores.put("PlayerOne", 3000.0);
scores.put("PlayerTwo", 1500.0);
scores.put("PlayerThree", 8200.0);

scores.entrySet().forEach(playerScore -> {
    jedis.zadd(key, playerScore.getValue(), playerScore.getKey());
});

String player = jedis.zrevrange("ranking", 0, 1).iterator().next();
long rank = jedis.zrevrank("ranking", "PlayerOne");

変数playerは、値PlayerThreeを保持します。これは、上位1人のプレーヤーを取得しており、彼が最高スコアのプレーヤーであるためです。 PlayerOneはランキングの2番目であり、ランキングはゼロベースであるため、rank変数の値は1になります。

6. トランザクション

トランザクションは原子性とスレッドセーフ操作を保証します。つまり、Redisトランザクション中に他のクライアントからのリクエストが同時に処理されることはありません。

String friendsPrefix = "friends#";
String userOneId = "4352523";
String userTwoId = "5552321";

Transaction t = jedis.multi();
t.sadd(friendsPrefix + userOneId, userTwoId);
t.sadd(friendsPrefix + userTwoId, userOneId);
t.exec();

Transactionをインスタンス化する直前にキーを「監視」することで、特定のキーに依存してトランザクションを成功させることもできます。

jedis.watch("friends#deleted#" + userOneId);

トランザクションが実行される前にそのキーの値が変更された場合、トランザクションは正常に完了しません。

7. パイプライン処理

複数のコマンドを送信する必要がある場合、それらを1つのリクエストにまとめて、パイプラインを使用することで接続オーバーヘッドを節約できます。これは基本的にネットワークの最適化です。 操作が相互に独立している限り、この手法を利用できます。

String userOneId = "4352523";
String userTwoId = "4849888";

Pipeline p = jedis.pipelined();
p.sadd("searched#" + userOneId, "paris");
p.zadd("ranking", 126, userOneId);
p.zadd("ranking", 325, userTwoId);
Response pipeExists = p.sismember("searched#" + userOneId, "paris");
Response> pipeRanking = p.zrange("ranking", 0, -1);
p.sync();

String exists = pipeExists.get();
Set ranking = pipeRanking.get();

コマンド応答に直接アクセスできないことに注意してください。代わりに、パイプラインが同期された後、基になる応答を要求できるResponseインスタンスが与えられます。

8. Publish/Subscribe

Redisメッセージングブローカー機能を使用して、システムのさまざまなコンポーネント間でメッセージを送信できます。 サブスクライバーとパブリッシャーのスレッドが同じJedis接続を共有しないようにしてください。

8.1. 加入者

チャンネルに送信されたメッセージを購読して聞く:

Jedis jSubscriber = new Jedis();
jSubscriber.subscribe(new JedisPubSub() {
    @Override
    public void onMessage(String channel, String message) {
        // handle message
    }
}, "channel");

サブスクライブはブロック方法です。JedisPubSubから明示的にサブスクライブを解除する必要があります。 onMessageメソッドをオーバーライドしましたが、オーバーライドできるuseful methodsは他にもたくさんあります。

8.2. 出版社

次に、発行者のスレッドから同じチャネルにメッセージを送信するだけです。

Jedis jPublisher = new Jedis();
jPublisher.publish("channel", "test message");

9. 接続プーリング

私たちがジェダイのインスタンスを扱ってきた方法が素朴であることを知ることは重要です。 実際のシナリオでは、単一インスタンスはスレッドセーフではないため、マルチスレッド環境で単一インスタンスを使用することは望ましくありません。

幸いなことに、必要に応じて再利用できるようにRedisへの接続のプールを簡単に作成できます。これは、リソースをプールに戻す限り、スレッドセーフで信頼性の高いプールです。

JedisPoolを作成しましょう:

final JedisPoolConfig poolConfig = buildPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "localhost");

private JedisPoolConfig buildPoolConfig() {
    final JedisPoolConfig poolConfig = new JedisPoolConfig();
    poolConfig.setMaxTotal(128);
    poolConfig.setMaxIdle(128);
    poolConfig.setMinIdle(16);
    poolConfig.setTestOnBorrow(true);
    poolConfig.setTestOnReturn(true);
    poolConfig.setTestWhileIdle(true);
    poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis());
    poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis());
    poolConfig.setNumTestsPerEvictionRun(3);
    poolConfig.setBlockWhenExhausted(true);
    return poolConfig;
}

プールインスタンスはスレッドセーフであるため、静的にどこかに保存できますが、アプリケーションのシャットダウン時にリークを避けるためにプールを破棄するように注意する必要があります。

これで、必要なときにアプリケーション内のどこからでもプールを使用できます。

try (Jedis jedis = jedisPool.getResource()) {
    // do operations with jedis resource
}

Jedisリソースを手動で閉じる必要がないように、Javaのtry-with-resourcesステートメントを使用しましたが、このステートメントを使用できない場合は、finally句でリソースを手動で閉じることもできます。

厄介なマルチスレッドの問題に直面したくない場合は、アプリケーションで説明したようにプールを使用してください。 プール構成パラメーターを使用して、システムの最適なセットアップに適応させることができます。

10. Redisクラスター

このRedis実装は、簡単なスケーラビリティと高可用性を提供します。慣れていない場合は、official specificationを読むことをお勧めします。 Redisクラスターのセットアップは、この記事の範囲外であるため、説明しませんが、ドキュメントの作成が完了したら問題なく実行できます。

準備ができたら、アプリケーションから使用を開始できます。

try (JedisCluster jedisCluster = new JedisCluster(new HostAndPort("localhost", 6379))) {
    // use the jedisCluster resource as if it was a normal Jedis resource
} catch (IOException e) {}

マスターインスタンスの1つからホストとポートの詳細を提供するだけで、クラスター内の残りのインスタンスが自動検出されます。

これは確かに非常に強力な機能ですが、特効薬ではありません。 Redisクラスターを使用する場合、トランザクションを実行したり、パイプラインを使用したりすることはできません。パイプラインは、データの整合性を確保するために多くのアプリケーションが依存する2つの重要な機能です。

クラスター化された環境では、キーが複数のインスタンスにわたって保持されるため、トランザクションは無効になります。 異なるインスタンスでのコマンド実行を伴う操作については、操作の原子性とスレッドセーフを保証できません。

いくつかの高度なキー作成戦略により、同じインスタンスに永続化される興味深いデータがそのように永続化されることが保証されます。 理論的には、これにより、Redisクラスターの基盤となるJedisインスタンスの1つを使用してトランザクションを正常に実行できるはずです。

残念ながら、現在、どのRedisインスタンスで特定のキーがJedis(実際にはRedisでネイティブにサポートされている)を使用して保存されているかを知ることができないため、トランザクション操作を実行する必要があるインスタンスはわかりません。 これに興味がある場合は、hereの詳細情報を見つけることができます。

11. 結論

Redisの機能の大半はすでにJedisで利用可能であり、その開発は順調に進んでいます。

スレッドセーフの問題を回避するために接続プーリングを設定することを忘れないでください。

コードサンプルはGitHub projectにあります。