概要
公式に推奨されるクライアントは、Redis シングル インスタンス、Redis Sentinel、Redis Cluster、Redis マスター/スレーブなどのさまざまなデプロイメント アーキテクチャをサポートしています。
GitHub、
関数:
- 分散ロック
分散ロック
Redisson が提供する分散ロックを使用する最も一般的なシナリオの 1 つは、アプリケーションを複数のノードにデプロイし、xxl-job (基盤となるシステムはデータベース悲観的ロックに基づいています)
@Scheduled(cron = "0 0 8 * * ?")
public void execute() {
RLock lock = redissonClient.getLock("myLock");
try {
boolean isLock = lock.tryLock(1, 5, TimeUnit.MINUTES);
if (!isLock) {
log.warn("job正在执行!");
return;
}
log.info("任务开始执行!");
} catch (Exception e) {
log.error("执行失败:", e);
lock.unlock();
}
}
ソース コードを表示してlock.tryLock()
、内部を段階的に見ていきます。
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "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; 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; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{
this.internalLockLeaseTime, this.getLockName(threadId)});
}
読みやすいように少しフォーマットします。
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;
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;
return redis.call('pttl', KEYS[1]);
つまり、Lua スクリプトを実行する必要があります。
KEYS[1]
ロックされたキーを表します。つまり、myLock
ARGV[1]
ロックされたキーの生存時間を表します。デフォルトは 30 秒ですARGV[2]
ロックしているクライアント ID を表します。形式は次のとおりUUID:n
ですe197fb92-deeb-4f9d-9d34-51b9b09f0bd7:1
。 n は Redis Cluster クラスター ノードを表します。
最初のif判定文で判定し、exists myLock
ロックしたいKeyが存在しない場合はhset myLock e197fb92-deeb-4f9d-9d34-51b9b09f0bd7:1 1
コマンドでロック、つまりハッシュデータ構造を設定します。コマンドが実行されると、次のようなデータ構造が生成されます。
myLock:
{
"e197fb92-deeb-4f9d-9d34-51b9b09f0bd7:1": 1
}
次にpexpiremyLock 30000
コマンドを実行し、ロックキー myLock の生存時間を 30 秒に設定すると、ロックが完了します。
ウォッチドッグ自動延長メカニズム
クライアント 1 によってロックされたキーのデフォルトの有効期限は 30 秒です。クライアント 1 が正常にロックされている限り、ウォッチドッグ バックグラウンド スレッドが開始され、クライアント 1 がまだロック キーを保持しているかどうかが 10 秒ごとにチェックされます。 . 、ロックキーの生存時間を継続的に延長します。
ロックの
実行を解放しますlock.unlock()
。つまり、分散ロックを解放し、1 回実行してlock.unlock()
、myLock データ構造内のロックの数を 1 つ減らします。
ロックの数が 0 でない場合は、クライアントがロックを保持しなくなったことを意味し、削除コマンドをトリガーしますdel myLock
。他のクライアントがロックを試みる可能性があります。
欠点がある
上記の解決策の最大の問題は、myLock などのロック キーの値を特定の Redis マスター インスタンスに書き込むと、その値が対応するマスター スレーブ インスタンスに非同期的にコピーされてしまうことです。
ただし、このプロセス中に Redis マスターがダウンすると、マスターとバックアップが切り替わり、Redis スレーブが Redis マスターになります。
これにより、クライアント 2 はロックしようとしたときに新しい Redis マスターのロックを完了し、クライアント 1 もロックに成功したと認識します。
この時点で、複数のクライアントが分散ロックのロックを完了します。現時点では、システムにはビジネス セマンティクスに間違いなく問題が発生し、その結果、さまざまなダーティ データが生成されます。
したがって、これは、Redis Cluster、または Redis マスター/スレーブ アーキテクチャのマスター/スレーブ非同期レプリケーションによって引き起こされる Redis 分散ロックの最大の欠陥です。Redis マスター インスタンスがダウンすると、複数のクライアントが同時にロックを完了する可能性があります。
NIO ベースの Netty フレームワークをベースに、Redis が提供する一連の利点を最大限に活用し、
質問
ClassNotFoundException: org.nustaq.serialization.FSTConfiguration
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.redisson.api.RedissonClient]: Factory method 'redisson' threw exception; nested exception is java.lang.NoClassDefFoundError: Lorg/nustaq/serialization/FSTConfiguration;
Caused by: java.lang.ClassNotFoundException: org.nustaq.serialization.FSTConfiguration
pom.xml
ファイルに追加された解決策:
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
<version>2.57</version>
</dependency>
ロックのロックを解除しようとしましたが、現在のスレッドによってノード ID によってロックされていません
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 633dfc8a-b388-4ba1-ad64-75b491d0c5f2 thread-id: 118
at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:88)
at org.redisson.command.CommandAsyncService.handleReference(CommandAsyncService.java:1067)
at org.redisson.command.CommandAsyncService.handleSuccess(CommandAsyncService.java:1059)
at org.redisson.command.CommandAsyncService.checkAttemptFuture(CommandAsyncService.java:1041)
at org.redisson.command.CommandAsyncService$12.operationComplete(CommandAsyncService.java:805)
at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:88)
at org.redisson.client.handler.CommandDecoder.completeResponse(CommandDecoder.java:448)
at org.redisson.client.handler.CommandDecoder.handleResult(CommandDecoder.java:443)
at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:354)
at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:128)
at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:108)
解決:
finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
コマンド (SET)、params [] は正常に送信されましたが、チャネル [] は閉じられました
詳細なエラー メッセージ:
org.springframework.data.redis.RedisConnectionFailureException: Command (SET), params [] succesfully sent, but channel [] has been closed!
at org.redisson.spring.data.connection.RedissonExceptionConverter.convert(RedissonExceptionConverter.java:40)
at org.redisson.spring.data.connection.RedissonExceptionConverter.convert(RedissonExceptionConverter.java:35)
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
at org.redisson.spring.data.connection.RedissonConnection.transform(RedissonConnection.java:237)
at org.redisson.spring.data.connection.RedissonConnection.syncFuture(RedissonConnection.java:232)
at org.redisson.spring.data.connection.RedissonConnection.sync(RedissonConnection.java:462)
at org.redisson.spring.data.connection.RedissonConnection.write(RedissonConnection.java:828)
at org.redisson.spring.data.connection.RedissonConnection.set(RedissonConnection.java:596)
at org.springframework.data.redis.connection.DefaultStringRedisConnection.set(DefaultStringRedisConnection.java:946)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95)
at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:236)
参照GitHub の問題:
Redis 接続が何らかの理由で閉じられています。pingConnectionInterval: 60000 を設定してみてください。
解決策:redisson.yml
ファイルに新しい構成を追加します。pingConnectionInterval: 60000
RedisResponseTimeoutException: 3 回の再試行後に Redis サーバーの応答タイムアウトが発生しました。コマンド: パラメータ:
解決策は上記と同じですが、新しい構成を使用します。