バックグラウンド
redisの高性能データ構造の2つのセクションを書きました。クリックして表示します。今日は自分の好みを変えましょう。今日は、分散システムでのredisのアプリケーションを見てみましょう。分散ロックにredisを使用することはよくある質問と言えます。
redis分散ロック
分散ロックによって解決される問題
ロックに関しては、最初の応答はスレッドブロッキングです。ここで注意する必要があるのは、ここでのディメンションが、サービス(プロセス)のスレッド間だけでなく、複数のサービス間でもレベルに上がることです。複数のプロセス間の同時実行性の問題についても言えます(2つのプロセスはそれぞれ2つのサービス上にあります)。したがって、ここでのスレッド間ロックの使用は、これらのJUCパッケージやJDKが提供するロックメカニズム(reentrantLock、Sychronized、CyclicBarrier、CountDownlotch、Semaphore、volatile)などの問題を解決できません。それらは、同じプロセスの異なるスレッド間の並行性の問題のみを処理できます。したがって、さまざまなプロセスとさまざまなサーバー時間の同時実行性のセキュリティ問題を解決するために、redis分散ロックが作成されます。ここで言えることは、分散ロックとスレッドロックを区別することです(それらを呼び出すのが適切であるか、オブジェクトロックであるかはわかりません)。
図に従って理解してください。2つのクライアントがデータベース内の同じデータを操作して変更します。
成し遂げる
- 分散ロックの本質は、異なるサービス間または同じサービス間でスレッドがredisの穴をめぐって競合することです。スレッドが穴を占有し、ドアがロックされると、他のスレッドはあきらめるか、再度待機する必要があります。スレッドがピットを占有し終えたら、del命令を使用してピットの位置を解放します。つまり、トイレのドアが開いています。実際の使用方法は、ピットの名前を設定することであり、この値は、
ピットが占有されていることを示します。ピットは通常、setnx(存在しない場合は設定)コマンドによって占有され、1つのクライアントのみがピットを占有できます。まず、先に使い切って
から、delコマンドを呼び出してピットを解放します。
// 这里的冒号:就是一个普通的字符,没特别含义,它可以是任意其它字符,不要误解
> setnx lock:codehole true
OK
... do something critical ...
> del lock:codehole
(integer) 1
- しかし、上記の方法には問題があります。つまり、このスレッドが異常に実行され、del releaseコマンドが有効にならない場合、問題があります。しかし、一部の人々は私がJavaのfinallyブロックを使用してピットを解放すると言っています。これは確かに方法ですが、使用されているサービスが直接切断した場合はどうなりますか?
//伪代码
try{
}catch (Exception e){
e.printStackTrace();
}finally {
//就算异常了也会执行,删除操作
delKey;
}
}
- つまり、このロック(ピットの位置)に制限時間を与えるために、時間になると自動的にそれを解放する必要があります。次に、実装は最初にロックを取得し、次にタイミング時間を設定します。
> setnx lock:codehole true
OK
> expire lock:codehole 5
... do something critical ...
> del lock:codehole
(integer) 1
-
上記の方法は、操作プロセスがアトミックではない(すべて実行される、またはまったく実行されない。実行の半分がない)ことではないため、ロックの取得後にも表示されます。次に、異常なロックタイムアウト時間またはサービスハングの問題があります。これは元の問題に戻ります。
-
次に、これに対処するための良い方法を考えますか?原子性といえば、これは4つの特徴の1つではないでしょうか。次に、redisを使用してこの問題に対処します。setnxとexpireの命令を同じものに入れて実行します。この問題はredis 2.8で対処されました。つまり、これらの2つのコマンドは一緒に実行できます。これが、分散ロックを使用する理由です。
> set lock:codehole true ex 5 nx OK ... do something critical ...
> del lock:codehole
- スレッドの実行時間が長すぎるためにロックタイムアウトが発生した場合はどうすればよいですか?
分散ロックの期限が切れました
- Redis分散ロックではタイムアウトの問題を解決できません。ロックとロックの解放の間のロジックの実行時間が長すぎ
、ロックのタイムアウト制限を超えると、問題が発生します。この時点でロックが期限切れになるため、2番目のスレッドはロックを再度保持します
が、最初のスレッドがビジネスロジックを実行した直後にロックが解放され、3番目のスレッドは2番目のスレッドロジックになります。
編集が実行された後、ロックを取得しました。 - まず、この種の問題を回避する必要があります。理由は、ビジネスロジックの実行時間が長すぎるため、それを使用するときは短時間で使用し、ビジネスの誇張されたサービス呼び出しを回避して、有効期限をできるだけ合理的に設定する必要がありますビジネスの実行時間をできるだけ長く保つようにしてください。
- 別のより安全な解決策は、各ロックに値を設定することです。delを実行するとき、最初に値が以前に設定されたかどうかを比較できます。これは少し楽観的なロックです。比較し、比較後に置換を交換します。しかし、マッチングとキーの削除はアトミック操作ではないので、心配してください。さいわい、redisはスクリプト実行メソッドを提供し、スクリプトはアトミックであり、luaスクリプトはアトミック性の目標を達成するために使用されます。(Alibaba Cloudのredisシャーディングモードに注意してください。それらのシャーディングモードは、luaスクリプトのサポートに問題があります。私が踏んだピット。redissionのRmapCacheを使用すると、ソースコードにluaスクリプトが含まれ、最終的な実行が失敗する)
# delifequals
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
再入可能
- 再入可能性とは、ロックを保持しているときに再度ロックを要求した場合でも、再度ロックを要求でき、その後、ロックは再入可能であることを意味します。たとえば、JavaでreentrantLockおよびsynchrizedは再入可能ロックです。
- それらの実装の一般的な原則は、楽観的ロックによってロックステータスを変更することです。同じスレッドIDである場合、正常に変更され、ロックを取得できます。同じスレッドIDでない場合、ロックはアップグレードされます。redisはどのように使用しますか?同じ推論。このスレッドIDまたはこのタスクIDを参照できます。これは、スレッドディメンションまたはタスクディメンションにすることができます。再入可能にします。
ミドルウェアの使用
- jedis(使用した場合、接続プールのリサイクルに問題があります。それは私のコードに問題があるはずですが、最終的には解決されませんでした)
- redission(redssionを置き換えた後に接続プールを再利用する問題は解決されましたが、元のredisスタンドアロンバージョンをシャーディングモードにアップグレードすると、luaスクリプトの問題が発生します)
上記の2つのredisミドルウェアは非常に快適に使用できます。ブラケットの内側には、踏んだピットがあります。
実際のケースの共有
- 要件
マイクロサービス間での呼び出し、インターフェースを外部に公開することにより、インターフェースが同じタスクIDによって短時間で繰り返し呼び出されることを防止します(前のタスク実行の完了を保証するため)。データの同時操作を防止します。 - 解決策:ロックとしてredis分散ロックとtaskIdを使用します。有効期限を2秒に、待機時間を2秒に設定します。
- 2秒は、ビジネス実行の最長時間が2秒であることを意味します。終了していない場合は離します。
- 3sは、ロックタイムアウトの期限が切れると、待機しているタスクが実行されることを意味します。
使用:パッケージ化されたredssion:
@Override
public Response discernCommon(DiscernCallBackResultTo discernCallBackResultTo) {
//防止并发,通过同一个TSKID做锁 重复新增发票,锁过期时间3秒,等待时间为2秒
boolean lock = RedissonLockUtils.tryLock(discernCallBackResultTo.getTaskId(), 2, 3);
if (lock) {
try {
dealDiscernResult(discernCallBackResultTo);
} catch (Exception e) {
LOGGER.error(e.getMessage(), Coder.of(ErrorCode.DISCERN_DEAL_EXCEPTION), e);
} finally {
RedissonLockUtils.unlock(discernCallBackResultTo.getTaskId());
}
} else {
EventMessage confilct = new EventMessage(EventEnum.EVENT_CONFILC_TASKID.getEventId())
.addExtValue("taskId", discernCallBackResultTo.getTaskId());
Tracker.getInstance().record(eventMessage);
}
return Response.ok("回调成功!");
}
総括する
- Redis分散ロックの実装と進化
- redisロックが期限切れになり、ロックが誤って解放されます。タスクを追加することにより、最初に比較して削除します
- redis分散ロックの再入可能ロックの実現は、楽観的ロック方式により、スレッドiDまたはタスクIDが再入可能になるように設計されています。
参照
- 「Redis Extreme Cold」
- http://ifeve.com/?x=34&y=9&s=%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81