1、なぜ分散ロックなのか
効率性:分散ロックの使用により、異なるノードが同じ作業を繰り返すことによるリソースの浪費を防ぎます。例えば、ユーザーが支払いを済ませた後、異なるノードが複数のSMSを送信する可能性があります。
正しさ:追加分散ロックはまた、操作の上に同じデータの2つのノードが、そのような異なるプロセスを動作させるために同じ順序でマシンの複数のノードとして、順序のエラーの最終的な状態につながる可能性がある場合、発生の正しさを破壊するために回避することができます損失の結果。
2、分散ロックは一般的に3つの方法で実装されます。
- MySQL のロックに基づく: MySQL 自身が update キーワードに対して独自の悲観的ロックを持っているか、またはこの目的のために独自の悲観的/楽観的ロックを実装することができます;
- Zookeeperの順序付きノードに基づく: Zookeeperでは、順序付き子ノードを一時的に作成することができます。これにより、クライアントがノードのリストを取得したときに、現在の子ノードリストのシリアル番号によってロックを取得できるかどうかを判断することができます;
SETNX(set if not exists)
Redisベースのシングルスレッド:Redisはシングルスレッドなので、コマンドはシリアルに実行され、本来相互に排他的である , のような独自のディレクティブを提供します;- 自社開発の分散型ロック:GoogleのChubbyなど。
それぞれのソリューションには長所と短所があります。 例えば、MySQLは直感的に理解しやすいのですが、その実装には ロックのタイムアウトやトランザクションの追加などの 追加的な考慮が必要であり、そのパフォーマンスはデータベースに限定されるため、ここではそれについては説明せず、Redisに焦点を当てます。
Redisを分散ロックとして使う利点:Redisは実装が簡単で、ZookeeperやMysqlよりもパフォーマンスが良いです。特に複雑な要件が必要でなければsetNxで実装でき、複雑な要件が必要であればRedissionを使うか借りることができます。
Redisを分散ロックとして使用するデメリット:Redisクラスタを維持する必要があるため、RedLockを実装したい場合はより多くのクラスタを維持する必要があります。
3.分散ロック条件
分散ロックを確実に利用するためには、ロックの実装が少なくとも以下の4つの条件を同時に満たすことが重要です:
相互排他的:任意の時点で、1つのクライアントだけがロックを保持できます;
デッドロックなし:あるクライアントがロックを保持したままクラッシュし、積極的にロックを解除しなかった場合でも、他のクライアントがロックを追加できることが保証されます;
フォールトトレラント:Redisノードのほとんどが正常に機能している限り、クライアントはロックとアンロックを行うことができます;
ベルを鳴らす:ロックの追加と解除は同じクライアントでなければならず、クライアント自身が他の人が追加したロックを解除することはできません。
ロックのコード実装
setNx resourceName value
あなたがマシンのダウンタイム後にロックを追加した場合、ロックが解放されないので、有効期限を追加し、有効期限を追加し、setNxは、同じアトミック操作である必要があります、Redis2.8ではsetnxとexpireの目的を達成するためにLuaスクリプトを使用する必要があるsetnxとexpireの原子性を保証することはできませんが、Redis2.8のRedisサポートnxとexpire操作は同じです。アトミック操作です。
これは、Redis 2.8 以降で拡張された set メソッドのパラメータです:
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";
/**
* 分散ロックの取得を試みる
* @param jedis Redis
* @param lockKey
* @param requestId リクエスト識別子
* @param expireTime タイムアウト
* @return 取得が成功したかどうか
*/
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);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
実際、ロックの追加はたった1行のコードです:
jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
jedis.set(String key, String value, String nxxx, String expx, int time)
ご覧のように、ロックの追加はわずか1行のコードで済みます。また、このset()メソッドには全部で5つの正式なパラメータがあります:
- キーは一意であるため、ロックとして使用されます。
- 2番目の値は、requestIdを渡され、多くの子供たちが理解できないかもしれませんが、ロックとしてキーが十分ではありませんが、なぜまた、値を使用するのですか? 理由は、上記の信頼性では、ベルのロックを解除するために4番目の条件を満たすために配布されたロックは、requestIdに値の割り当てを介して、人に結び付けられている必要があり、我々は、ロックがどのロック解除で追加する要求であることを知っている根拠を持つことができるということです。
UUID.randomUUID().toString()
requestIdは、メソッドを使用して生成することができます。 - つまり、キーが存在しない場合は操作を設定し、キーがすでに存在する場合は操作を行いません;
- このパラメータはPXとして渡され、この鍵に有効期限設定を追加することを意味します。
- 5つ目はtimeで、これは4つ目のパラメータと同じで、鍵の有効期限を表します。
3.2.ロック解除のためのコード実装:
public class RedisTool {
private static final Long RELEASE_SUCCESS = 1L;
/**
* 分散ロックの解放
* @param jedis Redis
* @param lockKey
* @param requestId リクエスト識別子
* @return リリースが成功したかどうか
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
RedisマスタースレーブアーキテクチャはRedisの分散ロックの中で最も重要です。
アプリケーションの背景
Jedisは長い間登場し、連絡先のRedis以前の人々はJedisを使用することがありますが、マルチコアと非同期の近代的なシステムでは、継続的にスループットを向上させるために、非同期の非ブロッキングスレッドモデルは非常に人気のあるフレームワークは、Nettyは、Nettyは、その優れた設計のため、アプリケーションの広い範囲、シナリオの広い範囲の実際の使用は、多くの大規模なNettyは、Nettyは、その優れた設計、アプリケーションの広い範囲のため、シナリオの広い範囲の実際の使用、hadoop、duboなどの多くの大規模なフレームワークは、基礎となる通信の多くはNettyを介して実現されています。 RedissionはRedisはNettyに基づいてカプセル化された通信クライアントです。
最も重要なことは
<!--Maven-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.4</version>
</dependency>
// 1. Create config object
Config = ...
// 2. Create Redisson instance
RedissonClient redisson = Redisson.create(config);
// 3. Get Redis based object or service you need
RMap<MyKey, MyValue> map = redisson.getMap("myMap");
RLock lock = redisson.getLock("myLock")
lock.lock();
//ビジネスコード
lock.unlock();
メリットとデメリット
利点 Redisシングルインスタンス、Redisセンチネル、Redisクラスタ、Redisマスタースレーブおよびその他のデプロイメントアーキテクチャをサポートし、Redisに基づいているため、Redisの機能の使用のカプセル化、フル機能を備えています。多くの企業が試用後、エンタープライズレベルのプロジェクトで使用することができ、コミュニティは非常に活発です。
デメリット 最大の問題は、RedisマスターインスタンスにmyLockを書き込むと、ロックキーの値が対応するマスタースレーブインスタンスに非同期でレプリケートされることです。しかし、Redisマスタがダウンしてマスタスイッチが発生すると、RedisスレーブがRedisマスタになり、クライアント2がロックを追加しようとすると、新しいRedisマスタでロックが完了し、クライアント1はロックの追加に成功したと思ってしまうということが起こります。
この時点で、複数のクライアントが分散ロックでロックを完了することになります。この時点でシステムは間違いなくビジネスセマンティクスに問題が発生し、ダーティなデータにつながります。だから、これはRedisクラスタ、またはRedisマスタースレーブアーキテクチャマスタースレーブ非同期レプリケーションは、最大の欠陥のRedis分散ロックにつながるです:Redisマスターインスタンスがダウンしているでは、ロックを完了するために同時に複数のクライアントにつながる可能性があります。