通常、分散ロックを実装するには3つの方法があります。1。楽観的なデータベースロック。 2。Redisに基づく分散ロック。 3。Zookeeperに基づいた分散ロック。このブログでは、Redisに基づいて分散ロックを実装する2番目の方法を紹介します。 Redis分散ロックの実装を導入するさまざまなブログがインターネットにありますが、その実装にはさまざまな問題があります。誤解を招く子どもたちを避けるために、このブログでは、Redis分散ロックを正しく実装する方法を詳細に紹介します。
信頼性
まず、分散ロックが利用可能であることを確認するために、少なくともロックの実装が同時に次の4つの条件を満たしていることを確認する必要があります。
相互除外。いつでも、ロックを保持できるクライアントは1つだけです。
デッドロックは発生しません。クライアントがロック保持期間中に積極的にロック解除することなくクラッシュしたとしても、他のクライアントがロックを追加できるようにすることができます。
フォールトトレラント。ほとんどのRedisノードが正常に実行されている限り、クライアントはロックしてロック解除できます。
鐘を縛った人は解かれなければなりません。ロックとロック解除は同じクライアントでなければならず、クライアントは他の人から追加されたロックを解くことはできません。
コード実装
コンポーネント依存関係
まず、Mavenを介してJedisのオープンソースコンポーネントを導入し、POM.xmlファイルに次のコードを追加する必要があります。
<Dependency> groupId> redis.clients </groupId> <artifactid> jedis </artifactid> <バージョン> 2.9.0 </version> </dependency>
ロックコード
正しい姿勢
話は安いです、コードを見せてください。最初にコードを表示し、次にこれが実装される理由を説明します。
public class redistool {private static final string lock_success = "ok"; private static final string set_if_not_exist = "nx"; private static final string set_with_expire_time = "px";/***分配ロックを取得しようとするそれは正常に取得されます*/public static boolean trygetdistributedlock(jedis jedis、string lockkey、string requestid、int expiretime){string result = jedis.set(lockkey、requestid、set_if_not_exist、set_with_expire_time、expiretime);ご覧のとおり、コードの単一行を追加するだけです:jedis.set(stringkey、stringvalue、stringnxxx、stringexpx、inttime)。このセット()メソッドには、合計5つの正式なパラメーターがあります。
最初のものはキーです。キーは一意であるため、キーをロックとして使用します。
2つ目は値です。私たちが伝えているのはリクエストです。多くの子供の靴は理解できないかもしれません。ロックとして鍵を用意するだけでは十分ではありませんか?なぜ私たちはまだ価値を使用する必要があるのですか?その理由は、上記の信頼性について話したとき、分散ロックはベルを解凍するために4番目の条件を満たさなければならないためです。 RequestIDに値を割り当てることにより、どのリクエストがロックに追加されたかがわかり、ロック解除の基礎があります。 RequestIDは、uuid.randomuuid()。toString()メソッドを使用して生成できます。
3つ目はnxxxです。このパラメーターNXを記入します。これは、setifnotexistを意味します。つまり、キーが存在しない場合、セット操作を実行します。キーが既に存在する場合、操作は行われません。
4番目はexpxです。このパラメーターPXを渡すことができます。つまり、このキーに期限切れの設定を追加することを意味します。特定の時間は、5番目のパラメーターによって決定されます。
5番目は時間です。これは、4番目のパラメーターをエコーし、キーの有効期限を表します。
一般に、上記のset()メソッドを実行すると、2つの結果のみがつながります。1。現在、ロックはありません(キーは存在しません)。ロック操作が実行され、ロックがロックに対して有効になるように設定され、値はロックされたクライアントを表します。 2.ロックが既に存在しており、操作は行われません。
注意している場合は、ロックコードが信頼性に記載されている3つの条件を満たしていることがわかります。まず、set()はNXパラメーターを追加します。これにより、キーが既に存在する場合、関数が正常に呼び出されないこと、つまり1つのクライアントのみがロックを保持してMutexを満たすことができます。第二に、ロックの有効期限を設定してから、ロックホルダーがその後のクラッシュでクラッシュしてロック解除されない場合でも、有効期限に達したため(つまり、キーが削除されます)、ロックは自動的にロック解除され、デッドロックはありません。最後に、ロックされたクライアントリクエストIDを表すRequestIDに値を割り当てるため、クライアントはロック解除時に同じクライアントであるかどうかを確認できます。 Redis Stand-Aloneの展開シナリオのみを検討するため、当面はフォールトトレランスを考慮しません。
エラー例1
一般的なエラーの例は、jedis.setnx()とjedis.expire()の組み合わせを使用してロックを達成することです。コードは次のとおりです。
public static void gurnegetlock1(jedis jedis、string lockkey、string lockkey、int expiretime){long result = jedis.setnx(lockkey、requestid); if(result == 1){//プログラムが突然クラッシュした場合、有効期限が設定できない場合、jedis.expire、expiretime);setnx()メソッドの関数はsetifnotexistであり、expire()メソッドはロックに有効期限を追加することです。一見すると、以前のset()メソッドと同じようです。ただし、これらは2つのRedisコマンドであるため、Atomicではありません。 setnx()を実行した後にプログラムが突然クラッシュした場合、ロックは有効期限を設定しません。その後、デッドロックが発生します。一部の人々がインターネット上でこれを実装する理由は、Jedisの低いバージョンがMulti-Parameter Set()メソッドをサポートしていないためです。
エラー例2
このエラーの例は、問題を見つけるのがより困難であり、実装もより複雑です。実装のアイデア:jedis.setnx()コマンドを使用してロックを実装します。キーはロックであり、値はロックの有効期限です。実行プロセス:1。setnx()メソッドからロックを追加してみてください。現在のロックが存在しない場合、ロックは正常に返されます。 2.ロックが既に存在する場合は、ロックの有効期限を取得し、現在の時間と比較してください。ロックの有効期限が切れた場合は、新しい有効期限を設定し、ロックが正常に追加されます。コードは次のとおりです。
public static boolean murnegetlock2(jedis jedis、string lockkey、int expiretime){long expires = system.currenttimemillis() + expiretime; string expiresstr = string.valueof(expires);ロックが存在し、ロックの有効期限が取得されますstring currentvaluestr = jedis.get(lockkey); if(currentValuester!= null && long.parselong(currentValuesestr)<System.CurrentTimemillis()){//ロックの有効期限が切れ、前のロックの有効期限を取得し、現在のロック= jedis.getの時刻を設定します。 expiresstr); if(oldvaluester!= null && oldvaluestr.equals(currentValuestr)){//マルチスレッドの同時性の場合を考慮して、1つのスレッドの設定値が現在の値と同じ場合、それはロックリターンをロックする権利がある場合、}} //では、このコードの問題は何ですか? 1.クライアントは有効期限時間自体を生成するため、各クライアントの時間を分散アプローチで同期させる必要があります。 2。ロックが期限切れになると、複数のクライアントがjedis.getset()メソッドを同時に実行すると、最終的にロックできますが、このクライアントのロックの有効期限は他のクライアントによって上書きされる場合があります。 3。ロックには所有者のロゴがありません。つまり、クライアントはロックを解除できます。
コードのロックを解除します
正しい姿勢
最初にコードを表示し、次にこれが実装される理由をゆっくりと説明しましょう。
public class redistool {private static final long release_success = 1l;/***分散ロックをリリース* @param jedis redis client* @param lockkey lock* @param requestid requestid* @returnリリースが成功したかどうか*/public static boolean release -reliectedlock(jedis jedis、string requirtid) keys [1])== argv [1] redis.call( 'del'、keys [1])else return 0 end "; object result = jedis.eval(collections.singletonlist(lockkey)、collections.singletonlist(requestId));ご覧のとおり、ロックを解除するには2行のコードだけが必要です!コードの最初の行で、単純なLUAスクリプトコードを書きました。このプログラミング言語を最後に見たのは「ハッカーとペインター」でしたが、今回は使用されるとは思っていませんでした。コードの2行目では、LUAコードをjedis.eval()メソッドに渡し、パラメーターキー[1]をロックキーとargv [1]にrequestIDに割り当てます。 eval()メソッドは、実行のためにLUAコードをRedisサーバーに引き渡すことです。
では、このLUAコードの機能は何ですか?実際、それは非常に簡単です。まず、ロックに対応する値を取得し、requestIDに等しいかどうかを確認し、等しい場合はロックを削除します(ロック解除)。では、なぜLUA言語を使用してそれを実装するのでしょうか?上記の操作がアトミックであることを確認する必要があるためです。 Atomicityがもたらす問題については、[コードのロック解除 - エラー例2]を読むことができます。では、Redisの特性に由来するAtomicityを確実に実行できるのはなぜですか。公式ウェブサイトの評価コマンドの部分的な説明は次のとおりです。
簡単に言えば、evalコマンドがLUAコードを実行すると、LUAコードがコマンドとして実行され、Redisはevalコマンドが実行されるまで他のコマンドを実行しません。
エラー例1
最も一般的なロック解除コードは、jedis.del()メソッドを使用してロックを削除することです。ロックの所有者を最初に審査することなく直接ロック解除するこの方法により、ロックがそうでない場合でも、クライアントはいつでもロックを解除します。
public static void誤解jedis、string lockkey){jedis.del(lockkey); }エラー例2
一見、このロック解除コードは問題ありません。私は以前にこのようにそれをほとんど実装しましたが、これは正しい姿勢に似ています。唯一の違いは、実行する2つのコマンドに分割されていることです。コードは次のとおりです。
public static void誤解jedis、jedis jedis、string lockkey、string requestid){//ロックと解除が同じクライアントであるかどうかを判断します(requestid.equals(jedis.get(lockkey)))){//コードのコメントに関しては、問題は、jedis.del()メソッドが呼び出された場合、現在のクライアントに属していない場合にロックがロック解除されることです。それで、本当にそのようなシナリオはありますか?答えはイエスです。たとえば、クライアントAロック、そして一定期間後、クライアントはロック解除されます。 jedis.del()を実行する前に、ロックが突然有効になります。この時点で、クライアントBは正常にロックしようとし、クライアントAはDel()メソッドを実行し、クライアントBのロックがロック解除されます。
要約します
この記事では、主にJavaコードを使用してRedis分散ロックを正しく実装する方法を紹介します。ロックとロックを解除するために、2つの古典的なエラー例が与えられます。実際、信頼性の4つの条件を満たすことが保証されている限り、Redisを介して分散ロックを実装することは難しくありません。
どのシナリオで分散ロックが主に使用されていますか?たとえば、同期が必要な場合、データを挿入するには、データベースに同様のデータがあるかどうかを事前にチェックする必要があります。複数のリクエストが同時に挿入されると、データベースには同様のデータがなく、すべてを追加できると判断される場合があります。現時点では、同期処理が必要ですが、直接データベースロックテーブルは時間がかかりすぎるため、Redis分散ロックが使用されます。同時に、データの挿入を実行できるスレッドは1つだけで、他のスレッドが待機しています。
上記は、Redis分散ロックの正しい実装を説明するJava言語に関するこの記事のすべての内容です。私はそれが誰にでも役立つことを願っています。興味のある友人は、このサイトの他の関連トピックを引き続き参照できます。欠点がある場合は、それを指摘するためにメッセージを残してください。このサイトへのご支援をありがとうございました!