[[347022]] Redis 分散ロック 分散ロックはなぜ必要なのでしょうか? JDK はロックする方法を提供します: (1)同期キーワード (2)揮発性+CASによる楽観的ロックの実装 (3)リードライトロック (4) ReenTrantLock 再入可能ロック 待ってください、これらのロックは非常に便利であり、マルチスレッドの状況でスレッドの安全性を保証します。 しかし、分散システムでは、上記のロックは役に立ちません。 分散システムにおける同時実行の問題を解決したい場合は、分散ロックの概念を導入する必要があります。 Javaコードの実装モチベーションまず第一に、ロック実装の原則の実現です。理論は実践を導き、実践は理論を向上させます。 夕方にはRedis分散ロックに関する記事がたくさんありますが、その質はさまざまです。 ミドルウェア チームが Redis 分散ロック ツールを提供していない場合があり、提供されている場合でも頻繁にメンテナンスされない場合があります。自分で実装して原理を理解し、変更しやすくする方がよいでしょう。 インターフェース定義JDK での再利用を容易にするために、インターフェースは JDK の Lock インターフェースから継承します。 - パッケージcom.github.houbb.lock.api.core;
-
- java.util.concurrent.TimeUnitをインポートします。
- java.util.concurrent.locks.Lockをインポートします。
-
-
-
- 公共 インターフェースILockはLockを拡張します{
-
-
-
- boolean tryLock( long時間, TimeUnit 単位,
- 文字列キー) はInterruptedExceptionをスローします。
-
-
-
- ブール型tryLock(文字列キー);
-
-
-
- void unlock(文字列キー);
-
- }
メソッド シンプルさを保つために、最初のバージョンではよく使用される 3 つのコア メソッドのみを追加しました。 後でさらに追加することもできます。 抽象実装後でさらに実装を追加しやすくするために、共通の抽象親クラスが最初にここで実装されます。 - パッケージcom.github.houbb.lock.redis.core;
-
- com.github.houbb.lock.api.core.ILockをインポートします。
- com.github.houbb.lock.redis.constant.LockRedisConstをインポートします。
- com.github.houbb.wait.api.IWaitをインポートします。
-
- java.util.concurrent.TimeUnitをインポートします。
- java.util.concurrent.locks.Conditionをインポートします。
-
-
-
- 公共 抽象的な クラスAbstractLockRedisはILockを実装します{
-
-
-
- プライベート 最終的なIWait待機;
-
- 保護されたAbstractLockRedis(IWait wait) {
- this .wait = wait;
- }
-
- @オーバーライド
- 公共 voidロック() {
- 投げる 新しいUnsupportedOperationException();
- }
-
- @オーバーライド
- 公共 void lockInterruptibly()はInterruptedExceptionをスローします{
- 投げる 新しいUnsupportedOperationException();
- }
-
- @オーバーライド
- 公共 ブール型tryLock() {
- tryLock(LockRedisConst.DEFAULT_KEY)を返します。
- }
-
- @オーバーライド
- 公共 voidロック解除() {
- LockRedisConst.DEFAULT_KEY のロックを解除します。
- }
-
- @オーバーライド
- 公共 boolean tryLock( long time, TimeUnit unit, String key)はInterruptedExceptionをスローします{
- 長いstartTimeMills = System.currentTimeMillis();
-
-
- ブール結果 = this .tryLock(key);
- if (結果) {
- 戻る 真実;
- }
-
-
- 時間 <= 0の場合
- 戻る 間違い;
- }
- 長い期間Mills = unit.toMillis(時間);
- 長いendMills = startTimeMills +durationMills;
-
-
- (System.currentTimeMillis() < endMills)の間{
- 結果 = tryLock(キー);
- if (結果) {
- 戻る 真実;
- }
-
-
- 待機します。待機します(TimeUnit.MILLISECONDS、 10 );
- }
- 戻る 間違い;
- }
-
- @オーバーライド
- 公共 同期した boolean tryLock( long time, TimeUnit unit)はInterruptedExceptionをスローします{
- tryLock(time, unit, LockRedisConst.DEFAULT_KEY) を返します。
- }
-
- @オーバーライド
- public条件 newCondition() {
- 投げる 新しいUnsupportedOperationException();
- }
-
- }
実際のコアは、public boolean tryLock(long time, TimeUnit unit, String key) throws InterruptedException メソッドです。 このメソッドは、ロックを取得するために this.tryLock(key) を呼び出します。成功した場合は直接戻ります。失敗した場合はループで待機します。 ここでタイムアウトを設定します。タイムアウトした場合は、直接 true が返されます。 Redis ロックの実装私たちが実装した Redis 分散ロックは、上記の抽象クラスから継承されます。 - パッケージcom.github.houbb.lock.redis.core;
-
- com.github.houbb.heaven.util.lang.StringUtilをインポートします。
- com.github.houbb.id.api.Idをインポートします。
- com.github.houbb.id.core.util.IdThreadLocalHelperをインポートします。
- com.github.houbb.lock.redis.constant.LockRedisConstをインポートします。
- com.github.houbb.lock.redis.exception.LockRedisExceptionをインポートします。
- com.github.houbb.lock.redis.support.operator.IOperatorをインポートします。
- com.github.houbb.wait.api.IWaitをインポートします。
-
-
-
- 公共 LockRedisクラスはAbstractLockRedisを拡張します。
-
-
-
- プライベート 最終的なIOperator redisOperator;
-
-
-
- プライベート 最終ID ID;
-
- パブリックLockRedis(IWait wait、IOperator redisOperator、Id id) {
- スーパー(待つ)
- これは.redisOperator = redisOperator です。
- この.id = id;
- }
-
- @オーバーライド
- 公共 ブール型tryLock(文字列キー) {
- 最終的な文字列 requestId = id.id();
- IdThreadLocalHelper.put(リクエストId);
-
- redisOperator.lock(キー、requestId、LockRedisConst.DEFAULT_EXPIRE_MILLS)を返します。
- }
-
- @オーバーライド
- 公共 void unlock(文字列キー) {
- 最終的な文字列 requestId = IdThreadLocalHelper.get();
- StringUtil.isEmpty(リクエストID)の場合{
- 文字列 threadName = Thread.currentThread().getName();
- 投げる 新しいLockRedisException( "スレッド " + threadName + " に requestId が含まれていません" );
- }
-
- ブール値のロック解除 = redisOperator.unlock(キー、リクエスト ID);
- ロック解除の場合
- 投げる new LockRedisException( "キー " + キー + " のロック解除結果が失敗しました!" );
- }
- }
- }
これが redis lock のコア実装です。理解できない場合は、原則を確認することをお勧めします。 Redis 分散ロック原理の詳細な説明
ロックロック部分では、現在のオペレータを区別するための ID が生成されます。 安全のため、デフォルトのタイムアウトも設定されています。 もちろん、これは発信者の使用コストを簡素化するためです。これを使用する場合、開発者はロックしたいキーのみに注意する必要があります。 もちろん、分散ロックを実装するメソッドに @DistributedLock をカプセル化するなど、ロック キーをさらに抽象化することもできます。これは後で時間があるときに拡張することができ、原理は難しくありません。 ロック解除ロックを解除すると、現在のプロセスの保持フラグが取得されます。 現在のスレッドが保持している ID を使用してロックを解除します。 I演算子Redis の操作を抽象化しました。なぜ抽象化したのでしょうか? Redis サービスには多くの種類があるため、Redis シングル ポイント、クラスター、マスター スレーブ、センチネルなどがあります。 jedis、spring redisTemplate、codis、redisson など、接続されるクライアントは多数ある可能性があります。 後の拡張を容易にするために、ここでは操作を抽象化します。 インタフェースインターフェースは次のように定義されます。 - パッケージcom.github.houbb.lock.redis.support.operator;
-
-
-
- 公共 インターフェースIOperator {
-
-
-
- ブール型ロック(文字列 lockKey、文字列 requestId、 int expireTimeMills);
-
-
-
- ブール値のロック解除(文字列 lockKey、文字列 requestId);
-
- }
ジェダイの実装Jedis のシングルポイント バージョンを実装します。 - パッケージcom.github.houbb.lock.redis.support.operator.impl;
-
- com.github.houbb.lock.redis.constant.LockRedisConstをインポートします。
- com.github.houbb.lock.redis.support.operator.IOperatorをインポートします。
- redis.clients.jedis.Jedis をインポートします。
-
- java.util.Collectionsをインポートします。
-
-
-
- 公共 JedisOperatorクラスはIOperatorを実装します{
-
-
-
- プライベート 最後のジェダイ、ジェダイ。
-
- パブリックJedisOperator(Jedis jedis) {
- this .jedis = jedis;
- }
-
-
-
- @オーバーライド
- 公共 ブール型ロック(文字列ロックキー、文字列リクエストID、 int有効期限ミル) {
- 文字列結果 = jedis.set(lockKey、requestId、LockRedisConst.SET_IF_NOT_EXIST、LockRedisConst.SET_WITH_EXPIRE_TIME、expireTimeMills);
- LockRedisConst.LOCK_SUCCESS.equals(結果)を返します。
- }
-
-
-
- @オーバーライド
- 公共 ブール値のロック解除(文字列ロックキー、文字列リクエストID) {
- 文字列スクリプト = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" ;
- オブジェクト結果 = jedis.eval(script、Collections.singletonList(lockKey)、Collections.singletonList(requestId));
- LockRedisConst.RELEASE_SUCCESS.equals(結果)を返します。
- }
-
- }
これが最も重要な部分です。 これらの数行のコードを過小評価しないでください。まだ注意すべき点はたくさんあります。 ロックロックを追加するときは、自分自身をロック保持者として識別するための requestId が含まれます。 SETNX はキーが存在しない場合にのみロックします。 ロックの解除に失敗するなどの異常な理由により、長期間のロック占有を回避するために、ロックの有効期限を設定します。 ロック解除操作のアトミック性を保証するには、Lua スクリプトを使用します。 ロックの所有者であることを証明するには、requestId を渡します。 テスト検証Mavenの紹介 - <依存関係>
- <groupId>com.github.houbb</groupId>
- <artifactId>ロックコア</artifactId>
- <バージョン> 0.0.1 </バージョン>
- </依存関係>
テストコード - ジェディス jedis = new Jedis( "127.0.0.1" , 6379 );
- IOperator 演算子 =新しいJedisOperator(jedis);
-
-
- ILock ロック = LockRedisBs.newInstance().operator(operator).lock();
-
- 試す{
- ブール型lockResult = lock.tryLock();
- System.out.println(lockResult);
-
- }キャッチ(例外 e) {
- e.printStackTrace();
- 最後に
- ロックを解除します。
- }
まとめこの時点で、Redis 分散ロックのシンプルなバージョンが実装されました。 もちろん、まだ改善の余地はたくさんあります。 (1)例えば、分散ロックにおけるGCによる問題を回避するために増分シーケンスを導入する (2)Redisサーバとクライアントのさらなるサポート (3)アノテーションスタイルのRedis分散ロックのサポート |