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

1概要

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

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

以降のセクションでは、さまざまなデータ構造について詳しく説明し、トランザクション、パイプライン処理、および発行/購読機能について説明します。コネクションプーリングとRedis Clusterで締めくくります。

2なぜジェディですか?

Redisは彼らのhttp://redis.io/clients#java/[公式サイト]に最も有名なクライアントライブラリをリストしています。 Jedisには複数の選択肢がありますが、現在推奨されているスター、http://redis.paluch.biz/[lettuce]、およびhttps://github.com/mrniko/redisson/[Redisson]にふさわしいのは、あと2つだけです。

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

しかし、それは小さく、他の二つよりかなり速いです。

その上、それはSpring Framework開発者の選択のクライアントライブラリです、そしてそれは3人すべての中で最大の共同体を持っています。

3 Mavenの依存関係

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

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.8.1</version>
</dependency>

最新版のライブラリを探している場合は、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22redis.clients%22%20AND%20a%3A%をご覧ください。 22jedis%22[このページ]。

** 4 Redisのインストール

**

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

LinuxおよびMacintosh用のRedisについての詳細は、http://redis.io/topics/quickstart[こちら]を参照してください。基本的なインストール手順は非常に似ています。 Windowsは正式にはサポートされていませんが、このhttps://github.com/MSOpenTech/redis[port]はよく管理されています。

その後、Javaコードから直接アクセスして接続できます。

Jedis jedis = new Jedis();

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

** 5 Redisデータ構造

**

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

5.1. 文字列

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

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

Redisセットは、繰り返しメンバーを除外したいときに便利な、順序付けされていないストリングのコレクションです。

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

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

Java Set 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<String, String> fields = jedis.hgetAll("user#1");
String job = fields.get("job");

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

5.5. ソートセット

ソートセットは、各メンバーに順位が関連付けられたセットのようなもので、ソートに使用されます。

Map<String, Double> 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");

私たちはトップ1のプレイヤーを検索しているので、変数 player は値 PlayerThree を保持し、彼は最高のスコアを持つプレイヤーです。

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<Boolean> pipeExists = p.sismember("searched#" + userOneId, "paris");
Response<Set<String>> pipeRanking = p.zrange("ranking", 0, -1);
p.sync();

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

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

8公開/購読

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 メソッドをオーバーライドしましたが、さらに多くのhttp://javadox.com/redis.clients/jedis/2.8.0/redis/clients/jedis/JedisPubSub.html[有用なメソッド]をオーバーライドすることができます。

8.2. 出版社

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

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

9接続プーリング

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

幸いなことに、私たちが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の実装は容易なスケーラビリティと高可用性を提供します、あなたがそれに精通していないならば、我々はあなたが彼らのhttp://redis.io/topics/cluster-spec[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 Clusterを使用する場合、トランザクションを実行することもパイプラインを使用することもできません。これは、多くのアプリケーションがデータの完全性を保証するために依存している2つの重要な機能です。

クラスタ化された環境では、キーは複数のインスタンスにわたって永続化されるため、トランザクションは無効になります。操作の原子性とスレッドの安全性は、異なるインスタンスでのコマンド実行を含む操作に対しては保証されません。

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

残念ながら、現時点ではJedisを使用して特定のキーがどのRedisインスタンスに保存されているか(実際にはRedisによってネイティブにサポートされている)を見つけることができません。これに興味があるならば、あなたはより多くの情報を見つけることができますhttps://groups.google.com/forum/#!topic/redis- db/4I0ELYnf3bk[ここ]。

11結論

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

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

GitHubプロジェクト にコードサンプルがあります。