Redis シリーズクライアント Redisson

概要

公式に推奨されるクライアントは、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 サーバーの応答タイムアウトが発生しました。コマンド: パラメータ:

解決策は上記と同じですが、新しい構成を使用します。

参考

おすすめ

転載: blog.csdn.net/lonelymanontheway/article/details/115363360