ホームページ  >  記事  >  Java  >  Java は Redis に基づいて分散ロックを実装します

Java は Redis に基づいて分散ロックを実装します

(*-*)浩
(*-*)浩転載
2019-08-28 16:12:442711ブラウズ

分散ロックは、Zookeeper、Redis など、さまざまな方法で実装できます。いずれの場合でも、基本原則は同じです。つまり、ロックを表すために状態値が使用され、ロックの占有と解放は状態値によって識別されます。

Java は Redis に基づいて分散ロックを実装します

1. Redis が分散ロックを簡単に実装できる理由

1. Redis は単一プロセス、単一スレッド モードであり、キュー このモードは同時アクセスをシリアル アクセスに変換し、複数のクライアントの Redis への接続間に競合はありません。

2. Redis の SETNX コマンドを使用すると、簡単に分散ロックを実装できます。

setNX(存在しない場合はSET)

構文: SETNXキー値

戻り値: 設定が成功した場合は1が返され、設定が失敗した場合は0が返されます。戻ってきた。

キーが存在しない場合にのみ、キーの値を value に設定し、1 を返します。指定されたキーがすでに存在する場合、SETNX は何も実行せず、0 を返します。

要約すると、setnx の戻り値を使用してロックが取得されているかどうかを判断できます。Redis はシングルスレッドであるため、同時アクセスについて心配する必要はありません。1 を返した場合、ロックが取得され、0 が返されます。取得されませんでした。業務が終了したらロックを解除する必要がありますが、ロック解除のロジックは非常に単純で、前回設定したキーを削除し、次回からキーを設定することでロックを取得できるようになります。

2. 分散ロックの実装

分散ロックは Redis 独自の関数 setNX を通じて実装できることはすでにわかっています。具体的な実装手順は次のとおりです。

CentOS7 Linux 仮想マシンに Redis サービスをインストールしました。IP アドレスは 192.168.246.130、サービス ポートは 6379 です。

次は、redis を介して分散ロックを実装する Java の例です:

import redis.clients.jedis.Jedis;
public class RedisLock {
	//锁的key
	private static final String key = "DistributedRedisLock";
	private static Integer count = 0;
	public static void main(String[] args) {
		for(int i=0;i<1000;i++){
			new Thread(new Runnable() {
				@Override
				public void run() {
					//获取Redis连接
					Jedis jedis = new Jedis("192.168.246.130", 6379);
					try{
						while(true){
							//获取锁
							if(jedis.setnx(key, Thread.currentThread().getName()) == 1){
								try{
									System.out.println("线程("+Thread.currentThread().getName()+")获取到锁,开始执行操作");
									count++;
									System.out.println(count);
									break;
								}finally{
									System.out.println("操作执行完成,释放锁");
									//操作执行完一定要释放锁,所以在finally块中执行
									jedis.del(key);
								}
							}else{
								//返回的不是1,说明已经有某个线程获取到了锁
								try {
									//等待100毫秒之后重试
									Thread.sleep(100l);
								} catch (InterruptedException e) {
									e.printStackTrace();
								}
							}
						}
					}catch(Exception e){
						e.printStackTrace();
					}finally{
						//释放Redis连接
						jedis.disconnect();
					}
				}
			}).start();
		}
	}
}

上記のコードの出力は次のとおりです:

线程(Thread-320)获取到锁,开始执行操作
1
操作执行完成,释放锁
线程(Thread-463)获取到锁,开始执行操作
2
操作执行完成,释放锁
线程(Thread-997)获取到锁,开始执行操作
3
操作执行完成,释放锁
...
线程(Thread-409)获取到锁,开始执行操作
998
操作执行完成,释放锁
线程(Thread-742)获取到锁,开始执行操作
999
操作执行完成,释放锁
线程(Thread-286)获取到锁,开始执行操作
1000
操作执行完成,释放锁

上記のコードは単一のアプリケーション内にありますが、複数のスレッドでテストされましたが、分散環境でロックを取得するために複数のアプリケーションと複数のスレッドが使用された場合でも、結果は依然として正しいです。

3. デッドロック問題を解決する

前のコード例は、原理を説明するための単なるテスト コードです。例自体は非常に単純なので、次のようなものがあります。いくつかの考慮されていない側面があります。例えば、ロック取得後、業務実行中に環境問題が発生してRedisとの接続が切断され、finallyブロックでロックが解除できず、ロック取得を待っていた他のスレッドが無限に待機してしまう、といった場合です。デッドロック現象が起こるのです。

解決策:

Redis でロックの有効期限を設定できるため、ロックを解放できない場合でも、一定の時間が経過すると自動的にロックが解放されます。期間。

コードに関しては、ロックを取得した後に次のコードを try ステートメント ブロックに追加するだけです:

jedis.expire(key, 10); //这里给锁设置10秒的过期时间

より良い解決策:

1 つの解決策はあまり良くありません。ビジネス オペレーションの処理時間が非常に長く、設定された有効期限を超えると、ロックが自動的に解放され、その後、finally ブロックでロックを解放する操作が実行されると、ロックが解除されてしまいます。スレッドが保持しているロックにより、他のスレッドが保持しているロックが解放されるため、同時実行性の問題が発生します。したがって、ロックを解除するときにロックの有効期限が切れているかどうかを判断し、期限が切れている場合は再度ロックを解除する必要はありません。

コード内で、ロックを取得した後の操作を次のコードに変更します:

long start = System.currentTimeMillis(); //获取起始时间毫秒数
try{
  jedis.expire(key, 10);
  ...
}finally{
  ...
  if(System.currentTimeMillis() < start+10*1000){
     //如果之前设置的锁还未过期,则释放掉
     jedis.del(key);
  }
}

以上がJava は Redis に基づいて分散ロックを実装しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。