分散ロック・実装
ロックは、すべての問題を解決するために使用されます。
- いつ、どのようにスレッドの安全性の問題を解決するためのプロセスで複数のスレッド、複数のスレッドが同時に同じリソースにアクセスします。
- 両方のモジュールは、ファイルを読み書きするために、ファイルにアクセスするための分散型アーキテクチャー・システム
- データのセキュリティを確保するためにどのようにデータの変更同じことを行うために、複数のアプリケーション、
単一のプロセスでは、我々は、同期を使用することができる解決するために同期動作のようなロックが、クロスプロセスロックを行うために、分散アーキテクチャ内の複数のプロセスの場合のために、方法。彼らは、完了するために、いくつかのサードパーティ製のツールの助けが必要
分散設計上の問題は解決すべき
分散ロック・ソリューションズ
- ロックを取得する方法
データベース、唯一の制約により、
ロック(
IDはint(11)
methodNameののVARCHAR(100)、
メモVARCHAR(1000)
modifyTimeタイムスタンプ
一意のキーMN(メソッド) - 唯一の制約
)
ロック疑似コードを取得
{試します
ロックにEXECインサート(methodNameの、メモ)の値( 'メソッド'、 'DESC')。方法
trueを返します。
}キャッチ(DuplicateException電子){
falseを返します。
}
ロックを解除
methodNameの=「」ロックから削除します。
問題は考える必要があります
- ロック解除操作が失敗したら、それはデータベースに記録されているロックにつながる、何の有効期限をロックしていない、他のスレッドは再ロックを取得することはできません
- これは、直接挿入しますエラーが失敗したら、データ操作を挿入し、ロックを非ブロックされます。スレッドがロックを取得しないと再ロック動作を得るためにロックトリガー再び軌道に乗るために、キューキューに入りません。
- 非リエントラントロックロックが解除されていない前に、同じスレッドが再びロックを取得することはできません
飼育係は、分散ロックを達成します
固有のノードのZooKeeperを使用すると、最も低いノードロックとしての一時的な特性または特性取得されたノードを命じた。達成する飼育係は、比較的単純な、学芸員のクライアントにより、ロック操作がパッケージ化されたため、次のように動作します
飼育係の利点
1.高い信頼性、簡単な
2.飼育係他のクライアントのために異常と飼育係の接続が中断されるようならば、一時的なノードの特性は、そのノードは、ロックが自動的に解除されることを意味し、削除されますので、
3.飼育係自体は安定した、優れたクラスタソリューションを提供します
4.つまり、サーバはロックが解除されたことをクライアントにメッセージを送信するためのイニシアチブをとるだろう、通知メカニズムを見ます、ロック操作をリリース
分散キャッシュロックに基づいて、
RedisのsetNxは、キーが存在しない場合にキー値を設定するコマンドを有します。だから、分散ロック動作を実現するために、この機能を使用することができます
特定の実装コード
- 依存関係を追加します。
- Redisの接続コードを書きます
ロックコードをリリース
- 分散ロックの実現
- ロックを解除する方法
Redisの多重化メカニズム
Linuxカーネルは、システムコマンドの操作を、ファイルとして動作読み書きするすべての外部デバイスは、ファイルを提供するために、カーネルを呼び出して、ファイルディスクリプタ(ファイルディスクリプタ)を返します。ソケット記述子のために読んでsocketfd(ソケット記述子)と呼ばれる応答を、書き込みしているだろう。IO多重化はプロセス一度カーネルに通知するためにIOのプロセス条件は、後に発見する準備が指定された1つまたは複数のファイル記述子を意味し、
また、イベント駆動型のオペレーティングシステムとして知られるIO多重化は、ソケット読み取り、書き込みの時間が、それは通知を与える際に、機能を提供します。システムが読めるある記述をお知らせし、私は読み取り操作を実行するためだったときにのみ使用し、非ブロッキングソケットで使用する場合、あなたはそれぞれの読み取りが有効なデータを読み取ることができるようにすることができます。使用される選択/プール/ファイルディスクリプタ/ kqueueのようなシステム機能により、オペレーティングシステム関数呼び出し、これらの機能は、複数の記述子を監視することができるI / O動作の記述子が複数であることができるように、同時に準備を読んI / Oマルチプレクサと呼ばれる完了の同時交互スレッドは、多重化は、本明細書で同じでスレッドを指し
多重ユーザを同時にスレッドで要求ioのソケットの複数を処理することができる利点。複数のIO要求を処理するのと同じスレッドを実現。同期ブロッキングモデルでは、自分の目標を達成するために、マルチスレッドモードでなければなりません
Redisのは、LUAスクリプトを使用します
LUAスクリプト
Luaは、標準的なC言語のソースコード形式で記述された効率的な軽量のスクリプト言語であり、それによってアプリケーションの柔軟な拡張やカスタマイズを提供し、組込みアプリケーションのために設計され、開放されています
スクリプトを使用するメリット
- ネットワークのオーバーヘッドを削減し、Luaのスクリプトで同じスクリプトの実行中に複数のコマンドを置くことができます
- アトミック操作、Redisのは全体として実行するスクリプト全体は、他のコマンドの途中に挿入することはできません。言い換えれば、スクリプトの競合状態を書き込む処理を心配する必要がなくなります
- 再利用性、スクリプトは常に他のクライアントが同じロジックを達成するために、このスクリプトを再利用できることを意味し、クライアントから送信されたのRedisに保存されます
LuaはLinuxでのインストール
ソースパッケージのluaをダウンロードする公式サイトへのtar.gz
連絡先-5.3.0.tar.gzタール-zxvf
抽出されたディレクトリに:
CD-月5.2.0
Linuxの(Linux環境コンパイラ)を作ります
make installを
エラーが報告された場合、readlineの/ readline.hを見つけることができない、のyumコマンドでインストールすることができます
yumをreadlineの-develのncursesの-develのインストール-y
そして、Linuxは/インストール後にmake installを行い、
最後に、あなたは直接のlua Luaのコンソールにコマンドを入力することができます
Luaの構文
RedisのとLuaの
Redisのは、Luaのスクリプトで呼び出すコマンドを、あなたはredis.call関数呼び出しを使用することができます。例えば、我々は、コマンドの文字列型を呼び出します
redis.call( '設定'、 'こんにちは'、 '世界')
redis.call関数の戻り値は、Redisのコマンドの実装の結果です。以前、我々はRedisの返されるデータの種類に値5のタイプを導入しは同じではありません。この関数はLuaのタイプに対応する変換データの5種類の値を返すredis.call
Luaのスクリプトから取得した戻り値
多くの場合、当方は一切のリターン実行が存在しない場合は、return文によって行われるRedisのクライアントに値を返すようにスクリプト内でreturnステートメントを使用することができ、スクリプトが値を返すことができる必要がある、デフォルトの戻り値はnilです。
Redisの中のluaスクリプトを実行する方法
Redisのは、EVALコマンドは組み込みコマンド呼び出し元のスクリプトとしてRedisのような他の開発者に電話をかけることができています。
[EVAL] [スクリプトの内容] [キーパラメータ数] [キー...] [引数...]
データはキーでスクリプトに渡すことができ、これら2つのパラメータのArg、それらの値は、それぞれ、スクリプトで使用することができKEYSとARGVアクセスこれらの2つのタイプのグローバル変数を。例えば、我々はRedisのクライアントの呼び出しによって、設定されたスクリプトコマンドを達成し、その文が実行されます。
コンテンツのluaスクリプトは次のとおりです。リターンredis.call( '設定'、KEYS [1]、ARGV [1])// KEYSとARGVは大文字でなければなりません。
evalの"リターンredis.call( '設定'、KEYS [1]、ARGV [1])" 1つのハローワールド
すべてのパラメータがスクリプト後KEYS ARGV、グローバル変数テーブルの二つのタイプに格納されているために、上記の例では1である - EVALコマンドキーパラメータは数です。パラメータなしのスクリプトは、このパラメータを省略することができないとき。引数または0が存在しない場合
evalの"リターンredis.call( '取得'、 'こんにちは')" 0
EVALSHAコマンド
私たちは長いスクリプトの下に、LUAスクリプトのevalを実行状況、各コールスクリプトを考慮すると、スクリプト全体の帯域幅を比較し、Redisのを渡す必要があります。この問題を解決するために、RedisのはEVALSHAコマンドがSHA1に、開発者がスクリプトを実行するスクリプトの内容を消化することができます提供します。コマンドとEVALの使用法、それはスクリプトSHA1ダイジェストの内容を置き換えるスクリプトの内容であるという点で
- Redisのは、スクリプトの実行EVALコマンドSHA1ダイジェストの時に計算され、スクリプトのキャッシュに記録されます
- Redisのスクリプトを、対応するキャッシュの内容によって提供要約によると、スクリプトからEVALSHAコマンドを実行するときを探します、スクリプトが見つかった場合、それ以外の場合は「NOSCRIPT一致するスクリプトは、EVALを使用しないでください」が実行されます
EVALSHA次の場合の効果を実証するために
スクリプトのロード「リターンredis.call( 『こんにちは』、 『取得』)は、」キャッシュに追加し、スクリプトコマンドSHA1を生成します
evalsha "a5a402e90df3eaeca2ff03d56d99982e05cf6574" 0
我々はevalのコマンドを呼び出す前に、スクリプトが存在しない場合、その後のevalコマンドを呼び出すevalshaコマンドプロンプトを実行します
LUAスクリプト戦闘
回の電話番号にアクセスするための周波数を達成するため、以下はLuaのスクリプトで、phone_limit.luaとして保存
ローカルNUM = redis.call( 'INCR'、KEYS [1])
もしTONUMBER(NUM)== 1、次いで
redis.call( '期限切れ'、KEYS [1]、ARGV [1])
1を返します
ELSEIF TONUMBER(NUM)> TONUMBER(ARGV [2])を
0を返します
他
1を返します
終わり
次のコマンドを呼び出します
./redis-cli --eval phone_limit.luaのrate.limiting:137億、10 3
構文は次のとおりです./redis-cli -eval [LUAスクリプト] [キー...]スペース、スペース[引数...]
アトミックスクリプト
Redisのスクリプトの実行は、スクリプトの実行中にRedisのは、他のコマンドを実行しないこと、アトミックです。すべてのコマンドは実行するために実行した後、スクリプトを待たなければなりません。Redisのにつながるプロセスのスクリプトの実行時間を防ぐためにサービスを提供することはできません。Redisのは、LUA-期限パラメータが最大スクリプト実行時間を制限提供します。デフォルトは5秒です。
スクリプトはこのタイムリミットより長く実行すると、Redisのは、他のコマンドを受け入れるために開始されますが、実行しません(アトミック性をスクリプトことを確実にするため)が、BUSYエラーを返します
ハンズオン
オープン2つのクライアントウィンドウ
無限ループの最初のウィンドウ内のLuaスクリプトの実行
evalの0を「真のエンドんが」
第二のウィンドウで実行するようにハローを取得
ウィンドウのセカンドランの最終的な結果は、スクリプトがスクリプトのkillコマンドによって実行され終了することができ、忙しいです。LUAスクリプトは現在、(セット)動作として、変更されたデータRedisの上で実行される場合アトミックLUAスクリプトを確保するため、スクリプトのkillコマンドは、スクリプトの動作を終了することができません。もしこの原則に反するの終端の実行部、
この場合、唯一通じシャットダウンNOSAVE強制終了コマンド
Javaコード
RedisManager.java
輸入redis.clients.jedis.Jedis。 輸入redis.clients.jedis.JedisPool。 輸入redis.clients.jedis.JedisPoolConfig。 パブリッククラスRedisManager { プライベート静的JedisPool jedisPool。 静的{ JedisPoolConfig jedisPoolConfig =新しいJedisPoolConfig()。 jedisPoolConfig.setMaxTotal(20)。 jedisPoolConfig.setMaxIdle(10)。 jedisPool =新しいJedisPool(jedisPoolConfig、 "120.79.174.118"、6379); } パブリック静的Jedis getJedisは(){例外をスロー するif(!= NULL jedisPool)は{ jedisPool.getResourceを返します()。 } (「Jedispoolが初期化できなかった」)は、新しい例外を投げます。 } }
分散ロックのRedisLock.java単純な実装
java.util.Listのインポート、 インポートjava.util.UUID; インポートredis.clients.jedis.Jedis; インポートredis.clients.jedis.Transaction; パブリッククラスRedisLock { 公共の文字列GETLOCK(文字キー、int型のタイムアウト){ 試み{ Jedis = RedisManager.getJedisのjedis(); 文字列値= UUID.randomUUID()のtoString();. 長い方の端=のSystem.currentTimeMillis()+タイムアウト; 一方(のSystem.currentTimeMillis()<完){ (jedis.setnx(主IF 。、値)== 1){ //成功したロックを設定し、操作が成功したのRedisのある ; jedis.expire(キー、タイムアウト) ;戻り値 } (jedis.ttl(主IF)== -1){ //検出時間が経過します既に設定されていない場合 jedis.expireを(キー、タイムアウト); } のThread.sleep(1000)。 } }キャッチ(例外E){ e.printStackTrace(); } 戻りNULL; } パブリックブールRELEASELOCK(文字キー、文字列値){ 試み{ Jedis jedis RedisManager.getJedis =(); 一方、(真の){ jedis.watch(キー); //時計 IF(value.equals(jedis.get)(キー){//ロックスレッドが決定され、Redisの電流に格納され得る)同じロックで トランザクション取引jedis.multi =(); transaction.del (KEY); リスト<OBJECT> = transaction.execリスト(); IF(リスト== NULL){ 続行; } trueに戻り; } jedis.unwatch(); BREAK; } キャッチ}(例外E){ IF(RET){ e.printStackTrace(); } 偽に戻り; } パブリック静的無効メイン(文字列[] args){ = "AAA"文字キー、 RedisLock redisLock新しい新しいRedisLock =(); 文字列LOCKID = redisLock.getLock(キー、10000); IF (!LOCKID = NULL){ System.out.printlnは( "成功入手ロック"); }他{ System.out.printlnは( "ロックを取得するのに失敗"); } 文字列lockId2 = redisLock.getLock(キー、10000); IF (!lockId2 = NULL){ System.out.printlnは( "成功入手ロック"); }他{ System.out.printlnは( "ロックを取得するのに失敗"); } ブールRET = redisLock.releaseLock(キー、LOCKID); }他{ System.out.println( "成功リリースロック"); System.out.printlnは( "ロック解除に失敗しました"); } 文字列lockId3 = redisLock.getLock(キー、10000); IF(ヌル= lockId3!){ のSystem.out .println( "成功得ロック"); }他{ するSystem.out.println( "ロックを取得するのに失敗"); } ブールRET2 = redisLock.releaseLock(キー、lockId3); IF(RET2){ System.out.printlnは( ) "正常ロックを解除"; {}他を するSystem.out.println( "ロック解除の失敗"); } } }
LuaDemo.javaのluaスクリプトの実行
輸入はjava.util.ArrayList; 輸入はjava.util.List; 輸入redis.clients.jedis.Jedis。 パブリッククラスLuaDemo { 公共の静的な無効メイン(文字列[] argsが){例外をスロー Jedis jedis = RedisManager.getJedis()。 ストリングLUA = "ローカルNUM = redis.call( 'INCR'、KEYS [1])\ n" + + "TONUMBER次いで(NUM)== 1 \ nもし" "redis.call( '期限切れ'、KEYS [1 ]、ARGV [1])\ n "+ "リターン1 \ n "+ "ELSEIF TONUMBER(NUM)> TONUMBER(ARGV [2])を\ n" + "リターン0 \ n "+ "他の\ n" + + "1 \ nは返す" "終わり"; 一覧<文字列>キー=新しいArrayListを<>(); keys.add(」 arggs.add( "6000"); オブジェクトOBJ = jedis.eval(LUA、キー、arggs)。 System.out.println(OBJ)。 } }
SHA要約キャッシュのluaスクリプトによってLuaDemo2.java
輸入はjava.util.ArrayList; 輸入はjava.util.List; 輸入redis.clients.jedis.Jedis。 パブリッククラスLuaDemo2 { 公共の静的な無効メイン(文字列[] argsが){例外をスロー Jedis jedis = RedisManager.getJedis()。 ストリングLUA = "ローカルNUM = redis.call( 'INCR'、KEYS [1])\ n" + + "TONUMBER次いで(NUM)== 1 \ nもし" "redis.call( '期限切れ'、KEYS [1 ]、ARGV [1])\ n "+ "リターン1 \ n "+ "ELSEIF TONUMBER(NUM)> TONUMBER(ARGV [2])を\ n" + "リターン0 \ n "+ "他の\ n" + + "1 \ nは返す" "終わり"; 一覧<文字列>キー=新しいArrayListを<>(); keys.add(「IP: // SHA要約のluaスクリプトによってキャッシュ、ネットワークトラフィックを削減し、パフォーマンスを向上させます。(キャッシュが失われる再起動するRedisのSHA要約) 文字列SHA = jedis.scriptLoad(LUA) のSystem.out.println(SHA); オブジェクトOBJ = jedis.evalsha(SHA、キー、arggs) のSystem.out.println(OBJ ); } }
設定のmaven
<依存性> <のgroupId> redis.clients </のgroupId> <たartifactId> jedis </たartifactId> <バージョン> 2.9.0 </バージョン> </依存> <依存性> <のgroupId> org.apache.commons </のgroupId> <たartifactId>コモンズ-POOL2 </たartifactId> <バージョン> 2.4.3 </バージョン> </依存関係>