ローカル ロックは、それが存在する仮想マシン内のスレッドの同期実行のみを制御できます。分散環境内のすべての仮想マシンでスレッドの同期実行を実現するには、複数の仮想マシンがロックを共有する必要があります。次の図に示すように、分散方式で導入することもできます。
すべての仮想マシンが同じロックを取得します。ロックは、ロックとロック解除のサービスを提供する別個のプログラムです。ロックを取得した人はデータベースにクエリを実行します。
ロックは特定の仮想マシンに属さず、複数の仮想マシンに分散して共有されます。このようなロックを分散ロックと呼びます。
分散ロック実装スキーム
分散ロックを実装するには多くのスキームがあり、一般的に使用されるスキームは次のとおりです。
1. データベースに基づいた分散ロックを実現
データベースの主キーの一意性の特性、またはデータベースの一意のインデックスの特性を使用して、複数のスレッドが同じレコードを同時に挿入し、挿入に成功した人がロックを取得します。
2. Redis に基づいてロックを実装する
Redis は、SETNX、set nx、redisson などの分散ロックの実装を提供します。
SETNX を例にとります。SETNX コマンドの動作プロセスは、存在しないキーを設定することです。複数のスレッドが同じキーを設定した場合、1 つのスレッドだけがキーの設定に成功し、設定されたスレッドがロックを取得します。
3. Zookeeperを使用して実装
Zookeeper は、主に分散プログラム間の同期の問題を解決する分散調整サービスです。Zookeeper の構造はファイル ディレクトリに似ています。複数のスレッドが Zookeeper のサブディレクトリ (ノード) を作成する場合、正常に作成されるのは 1 つだけです。この機能を使用すると、分散ロックを実現できます。ノードの作成に成功した人がロックを取得します。
Redis NX は分散ロックを実装します
分散ロックを実装する Redis のスキームは、 http://redis.cn Web サイトで見つけることができます。アドレスはhttp://www.redis.cn/commands/set.htmlです。
SET resource-name anystring NX EX max-lock-time を達成するには、次のコマンドを使用します。
NX: キーが存在しない場合にのみキーが正常に設定されることを示します。
例: 有効期限を設定する
ここで 3 つの SSH クライアントを起動し、redis に接続します: docker exec -it redis redis-cli
最初の認証: auth redis
次のように、テスト コマンドを 3 つのクライアントに同時に送信します。
lock001 ロックの設定を示します。値は 001、有効期限は 30 秒です。
Plain Text
SET lock001 001 NX EX 30
コマンドは正常に送信されました。3 つの SSH クライアントを観察したところ、設定のうち 1 つだけが成功し、他の 2 つの設定は失敗したことがわかりました。成功したリクエストは、lock001 ロックが取得されたことを示しています。
Set nx を使用してコードに分散ロックを実装するにはどうすればよいですか?
Set nxはspring-boot-starter-data-redisが提供するAPIを利用することで実現できます。依存関係を追加します。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.2</version>
</dependency>
依存関係を追加した後、Bean にrestTemplate を挿入します。まず、次のように疑似コードの一部を分析してみましょう。
if(缓存中有){
返回缓存中的数据
}else{
获取分布式锁
if(获取锁成功){
try{
查询数据库
}finally{
释放锁
}
}
}
1. 分散ロックを取得する
redisTemplate.opsForValue().setIfAbsent(key,vaue) を使用してロックを取得します。
ここで質問を考えてみましょう。set nx a key/value が 1 に成功した場合、このキー (つまり、ロック) には有効期限を設定する必要がありますか?
有効期限が設定されていない場合、ロックが取得されても最終的に実行されない場合、ロックは常に存在し、他のスレッドはロックを取得できません。したがって、set nx を実行するときに有効期限を指定する必要があります。つまり、次のコマンドが使用されます。
SET resource-name anystring NX EX max-lock-time
具体的な呼び出しメソッドは次のとおりです: redisTemplate.opsForValue().setIfAbsent(K var1, V var2, long var3, TimeUnit var5)
2. ロックの解除方法
ロックを解除するには、キーの有効期限が切れたときの自動解除と手動削除の 2 つの状況があります。
1) キーの有効期限が切れたら自動的に解放する方法
ロックには有効期限が設定されているため、期限が切れるとキーは自動的に解放されますが、データベースへのクエリなどの操作が完了する前にキーが期限切れになり、この時点で他のスレッドがロックを取得してしまうという問題が発生します。 、最後にクエリデータベースの実行を繰り返します。 業務操作を繰り返します。
この問題をどうやって解決すればいいでしょうか?
キーの有効期限は長く設定でき、データベースのクエリやキャッシュの設定などの関連操作を実行するには十分です。
そうなると効率が低下し、時間値の制御が難しくなります。
2) ロックを手動で削除します
ロックを手動で削除すると、有効期限が切れたときにキーの自動削除と競合し、他の人のロックが削除される可能性があります。
例: クエリ データベースやその他のビジネス オペレーションが実行されていない場合、キーは期限切れになります。このとき、他のスレッドがロックを占有します。前のスレッドがクエリ データベースやその他のビジネス オペレーションを実行しているときに、ロックを手動で削除すると、ロックも削除されます。他のスレッドの。
この問題を解決するには、ロックを削除する前に、設定したロックかどうかを判断できます。疑似コードは次のとおりです。
if(缓存中有){
返回缓存中的数据
}else{
获取分布式锁: set lock 01 NX
if(获取锁成功){
try{
查询数据库
}finally{
if(redis.call("get","lock")=="01"){
释放锁: redis.call("del","lock")
}
}
}
}
上記のコードの行 11 から 13 は非アトミックであるため、他のスレッドのロックも削除されます。ドキュメントの手順を参照してください: http://www.redis.cn/commands/set.html
上記の最適化方法により、次のシナリオが回避されます。クライアント a が取得したロック (キー) は有効期限切れのため Redis サーバーによって削除されましたが、この時点でもクライアント a はまだ DEL コマンドを実行しています。そして、クライアント a によって有効期限が設定された後、クライアント b が同じキーのロックを再取得した場合、a によって DEL を実行すると、クライアント b によって追加されたロックが解放されます。
ロック解除スクリプトの例は次のようになります。
if redis.cal1("get",KEYS[1]) == ARGV[1]
then
return redis. call("del",KEYS[1])
else
return 0
end
setnx コマンドを呼び出してキー/値を設定する場合、各スレッドは異なる値を設定します。これにより、スレッドがロックを削除するときに、最初にキーをチェックして、その時点で設定した値であるかどうかを判断し、そうである場合はその値を確認できます。 、 消して。
この操作全体はアトミックであり、これを達成する方法は、上記の lua スクリプトを実行することです。
Lua は小さなスクリプト言語で、Redis はバージョン 2.6 で Lua スクリプトを実行することにより、複数のコマンドのアトミック性をサポートしています。
原子性とは何ですか?
これらのコマンドはすべて成功するか、すべて失敗します。
上記は Redis Nx を使用して分散ロックを実装しています。他のスレッドによって設定されたロックが削除されないようにするには、Redis を使用して Lua スクリプトを実行する必要があります。これはアトミックですが、有効期限の値の設定は不正確ではありません. .