Redisは分散ロックとして機能する(一):SpringBoot集積Redisson分散ロック
40983 ワード
文書ディレクトリ一、プロジェクトDemo 1.1プロジェクト依存 1.2プロジェクト構成-redis 1.3 Redissonの構成クラスRedissonConfig 1.4は、分散ロックのいくつかの動作 のためのLokerインターフェースを定義する. 1.5 Redissonベースの実装クラスRedissonLocker 1.6分散ロックツールクラスLockUtil を定義 1.7ユニットテスト 1.8ソースコード 二、RedissonのRedlock 2.1 Redisコマンドに基づく分散ロック 2.2 Redlock を実現 2.3 Redlockソース 2.3.1一意ID 2.3.2取得ロック 2.3.2リリースロック Redissonは分散型と拡張性のJavaデータ構造を実現し、Jedisに比べて機能が簡単で、文字列操作をサポートせず、ソート、トランザクション、パイプ、パーティションなどのRedis特性をサポートしない.Redissonの目的は,利用者のRedisへの関心の分離を促進し,利用者がビジネスロジックの処理に集中できるようにすることである.
一、プロジェクトDemo
1.1プロジェクト依存
1.2プロジェクト構成-redis
1.3 Redissonの構成クラスRedissonConfig
1.4 Lockerインタフェースを定義し、分散ロックのいくつかの操作に使用する
主に拡張を考慮するために,本論文ではredissonで実装し,同じ
1.5 Redissonベースの実装クラスRedissonLocker
1.6分散ロックツールクラスLockUtilを定義する
1.7ユニットテスト
1.8ソース
SpringBoot統合Redisson分散ロック
二、RedissonのRedlock
激しい操作の後、その優位性を見てみましょう.
2.1 Redisコマンドに基づく分散ロック
この実現方法には3つのポイントがあります. setコマンドはset key value px milliseconds nxを使用します. valueは一意性を有しなければならない. ロックを解除するときはvalue値を検証し、誤ってロックを解除することはできません.
同時に、彼にも大きな問題があります.それはロックをかける時に1つのRedisノードにしか作用しません.Redisがsentinelを通じて高可用性を保証しても、このmasterノードが何らかの原因で主従切替が発生した場合、ロックが失われることがあります.はRedisのmasterノードにロックを取得した. しかし、このロックされたkeyはまだslaveノードに同期していない. master障害、障害移行が発生し、slaveノードがmasterノードにアップグレードされます.
そのため、Redisの著者antirezは、分散環境に基づいて、より高度な分散ロックの実装方法を提案した:Redlock
2.2 Redlock実装
Redisの分散環境では,N個のRedisマスターがあると仮定した.これらのノードは完全に互いに独立しており,主従レプリケーションや他のクラスタ協調機構は存在しない.我々は、Redis単一インスタンスの場合と同様の方法を用いて、N個のインスタンス上でロックを取得および解放することを保証する.ロックを解除するには、クライアントが次の操作を行います.現在のUnix時間をミリ秒単位で取得します. は、N個のインスタンスから順に、同じkeyおよび一意性を有するvalue(例えばUUID)を使用してロックを取得しようと試みる.Redisにロックの取得を要求する場合、クライアントは、ロックの失効時間よりも小さいネットワーク接続と応答タイムアウト時間を設定する必要があります.たとえば、ロックの自動失効時間が10秒の場合、タイムアウト時間は5~50ミリ秒です.これにより、サーバ側のRedisがすでに停止している場合に、クライアントが応答結果を必死に待っていることを回避することができる.サーバ側が所定時間以内に応答していない場合、クライアントはできるだけ早く別のRedisインスタンスにロックの取得を要求しようとするべきである. クライアントは、現在の時間からロックの取得開始時間(ステップ1で記録された時間)を減算して、ロックの使用を取得する時間を得る.ロックが成功したのは、ほとんどの(N/2+1ノード)のRedisノードからロックが取り外され、使用時間がロックの失効時間よりも小さい場合のみです. ロックが取り外された場合、keyの真の有効時間は、有効時間からロックを取得するために使用される時間を減算することに等しい. 何らかの理由でロックの取得に失敗した場合(少なくともN/2+1個のRedisインスタンスでロックが取得されなかったか、またはロックが有効時間を超えた場合)、クライアントは、すべてのRedisインスタンス上でロック解除を行う必要があります(一部のRedisインスタンスがロックに成功していなくても、一部のノードがロックを取得することを防止しますが、クライアントが応答していないため、次の時間にロックを再取得できません).
2.3 Redlockソースコード
2.3.1一意ID
分散ロックを実現する上で非常に重要な点はsetのvalueが一意性を持つことであり、
2.3.2ロックの取得
ロックを取得するコードは
ロックを取得するコマンド:
2.3.2ロック解除
ロックを解除するコードは
一、プロジェクトDemo
1.1プロジェクト依存
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-data-redis
org.redisson
redisson
3.10.6
1.2プロジェクト構成-redis
spring:
redis:
host: 47.98.178.84
port: 6379
database: 0
password: password
timeout: 60s
1.3 Redissonの構成クラスRedissonConfig
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
/**
* RedissonClient,
* @return
* @throws IOException
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
return Redisson.create(config);
}
@Bean
public RedissonLocker redissonLocker(RedissonClient redissonClient){
RedissonLocker locker = new RedissonLocker(redissonClient);
// LockUtil
LockUtil.setLocker(locker);
return locker;
}
}
1.4 Lockerインタフェースを定義し、分散ロックのいくつかの操作に使用する
主に拡張を考慮するために,本論文ではredissonで実装し,同じ
Loker
インタフェースでもredisで分散ロックを実装することができ,詳細は別の記事を参照する.public interface Locker {
/**
* , , , 。
*
* @param lockKey
*/
void lock(String lockKey);
/**
*
*
* @param lockKey
*/
void unlock(String lockKey);
/**
* :
*
* @param lockKey
* @param timeout :
*/
void lock(String lockKey, int timeout);
/**
* :
*
* @param lockKey
* @param unit
* @param timeout
*/
void lock(String lockKey, TimeUnit unit, int timeout);
/**
* , true, false
*
* @param lockKey
* @return
*/
boolean tryLock(String lockKey);
/**
* , true, false, , ,
* leaseTime
* @param lockKey
* @param waitTime
* @param leaseTime
* @param unit
* @return
*/
boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit)
throws InterruptedException;
/**
*
*
* @param lockKey
* @return
*/
boolean isLocked(String lockKey);
}
1.5 Redissonベースの実装クラスRedissonLocker
public class RedissonLocker implements Locker{
private RedissonClient redissonClient;
public RedissonLocker(RedissonClient redissonClient) {
super();
this.redissonClient = redissonClient;
}
public void lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
}
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
public void lock(String lockKey, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
}
public void lock(String lockKey, TimeUnit unit, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
}
public void setRedissonClient(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
public boolean tryLock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
return lock.tryLock();
}
public boolean tryLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit) throws InterruptedException{
RLock lock = redissonClient.getLock(lockKey);
return lock.tryLock(waitTime, leaseTime, unit);
}
public boolean isLocked(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
return lock.isLocked();
}
}
1.6分散ロックツールクラスLockUtilを定義する
public class LockUtil {
private static Locker locker;
/**
* locker
* @param locker
*/
public static void setLocker(Locker locker) {
LockUtil.locker = locker;
}
/**
*
* @param lockKey
*/
public static void lock(String lockKey) {
locker.lock(lockKey);
}
/**
*
* @param lockKey
*/
public static void unlock(String lockKey) {
locker.unlock(lockKey);
}
/**
* ,
* @param lockKey
* @param timeout
*/
public static void lock(String lockKey, int timeout) {
locker.lock(lockKey, timeout);
}
/**
* , ,
* @param lockKey
* @param unit
* @param timeout
*/
public static void lock(String lockKey, TimeUnit unit, int timeout) {
locker.lock(lockKey, unit, timeout);
}
/**
* , true, false
* @param lockKey
* @return
*/
public static boolean tryLock(String lockKey) {
return locker.tryLock(lockKey);
}
/**
* , waitTime , true, false, leaseTime
* @param lockKey
* @param waitTime
* @param leaseTime
* @param unit
* @return
* @throws InterruptedException
*/
public static boolean tryLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit) throws InterruptedException {
return locker.tryLock(lockKey, waitTime, leaseTime, unit);
}
/**
*
* @param lockKey
* @return
*/
public static boolean isLocked(String lockKey) {
return locker.isLocked(lockKey);
}
}
1.7ユニットテスト
@SpringBootTest
@RunWith(SpringRunner.class)
public class LockerTest {
private static final Logger logger = LoggerFactory.getLogger(LockerTest.class);
@Test
public void testLock() {
LockUtil.lock("hello");
logger.info(" ");
try {
//TODO
} catch (Exception e) {
//
}finally{
//
LockUtil.unlock("hello");
logger.info(" ");
}
}
}
1.8ソース
SpringBoot統合Redisson分散ロック
二、RedissonのRedlock
激しい操作の後、その優位性を見てみましょう.
2.1 Redisコマンドに基づく分散ロック
Redis
分散ロックのほとんどの人が考えていることを知っています.- (unique_value UUID )
SET resource_name unique_value NX PX 30000
- (lua , value, )
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
この実現方法には3つのポイントがあります.
同時に、彼にも大きな問題があります.それはロックをかける時に1つのRedisノードにしか作用しません.Redisがsentinelを通じて高可用性を保証しても、このmasterノードが何らかの原因で主従切替が発生した場合、ロックが失われることがあります.
そのため、Redisの著者antirezは、分散環境に基づいて、より高度な分散ロックの実装方法を提案した:Redlock
2.2 Redlock実装
Redisの分散環境では,N個のRedisマスターがあると仮定した.これらのノードは完全に互いに独立しており,主従レプリケーションや他のクラスタ協調機構は存在しない.我々は、Redis単一インスタンスの場合と同様の方法を用いて、N個のインスタンス上でロックを取得および解放することを保証する.ロックを解除するには、クライアントが次の操作を行います.
2.3 Redlockソースコード
2.3.1一意ID
分散ロックを実現する上で非常に重要な点はsetのvalueが一意性を持つことであり、
redisson
のvalueはどのようにvalueの一意性を保証するのか.答えは**UUID+threadId
**です.ソースはRedissonです.JAvaとRedissonLockJAva:protected final UUID id = UUID.randomUUID();
String getLockName(long threadId) {
return id + ":" + threadId;
}
2.3.2ロックの取得
ロックを取得するコードは
redLock.tryLock()
またはredLock.tryLock(500, 10000, TimeUnit.MILLISECONDS)
であり、両方の最終コアソースコードは以下のコードであるが、前者がロックを取得するデフォルトのリース時間(leaseTime)はLOCK_EXPIRATION_INTERVAL_SECONDS、すなわち30 s:<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
// redis lua
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
// KEY , , hset (hset REDLOCK_KEY uuid+threadId 1), pexpire ( )
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// KEY , value , , 1,
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// KEY
"return redis.call('pttl', KEYS[1]);",
// KEYS[1],ARGV[1] ARGV[2]
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
ロックを取得するコマンド:
KEYS[1]
はCollections.singletonList(getName())
であり、分布式ロックのkey、すなわちREDLOCK_KEY
を表す.ARGV[1]
はinternalLockLeaseTime
、すなわちロックされたリース時間であり、デフォルトは30 sである.ARGV[2]
はgetLockName(threadId)
であり、ロック時setを取得する唯一の値であるUUID+threadId
である.2.3.2ロック解除
ロックを解除するコードは
redLock.unlock()
で、コアソースは以下の通りです.protected RFuture<Boolean> unlockInnerAsync(long threadId) {
// redis lua
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// KEY , channel
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end;" +
// , value , ,
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
// , 1
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
// 1 0, , ,
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
// 1 0, 1 , KEY,
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
// 5 KEYS[1],KEYS[2],ARGV[1],ARGV[2] ARGV[3]
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
}