ホームページ  >  記事  >  Java  >  分散ロックの王道ソリューション - Redisson

分散ロックの王道ソリューション - Redisson

Java后端技术全栈
Java后端技术全栈転載
2023-08-24 15:31:191161ブラウズ

分散ロックの王道ソリューション - Redisson
#Redis で分散ロックを実装するためのさまざまなソリューションがインターネット上にありますが、最も重要なソリューションは何でしょうか?

答えは:

レディソンです。

まず、分散ロックについて Redis 公式 Web サイトに記載されている内容を見てみましょう。

分散ロックの王道ソリューション - Redisson
Java バージョンの分散ロックのフレームワークは Redisson です。

この実践的な内容は、Redisson を統合するための私のオープンソース プロジェクト PassJava に基づいています。

backendfrontend小プログラムを同じウェアハウスにアップロードしました。Github または ## を使用できます。 #codeyun にアクセスしてください。アドレスは次のとおりです:

Github

: https://github.com/Jackson0714/PassJava-Platform

Code Cloud

:https://gitee.com/jayh2018/PassJava-Platform

Supporting Tutorial

: www.passjava.cn

実際の戦闘の前に、 「レディソンの原理」の使い方を見てください。

1.レディソンとは何ですか?

以前に Redis を使用したことがある場合は、Redisson を使用すると、半分の労力で 2 倍の結果が得られます。Redisson は、Redis を使用する最も簡単で便利な方法を提供します。

Redisson の目的は、ユーザー間で Redis の関心の分離 (Separation of Concern) を促進し、ユーザーがビジネス ロジックの処理により集中できるようにすることです。

Redisson は、Redis に基づいて実装された Java インメモリ データ グリッド (In-Memory Data Grid) です。

分散ロックの王道ソリューション - Redisson
  • Netty フレームワーク: Redisson は、Redis として機能するだけでなく、NIO ベースの Netty フレームワークを採用しています。基盤となるドライバー client には、Redis のさまざまな構成形式への接続を提供する機能、Redis コマンドを同期、非同期、非同期ストリーミングまたはパイプラインで送信する機能、LUA スクリプトの実行処理、および返された結果を処理する機能があります

  • 基本データ構造: ネイティブ Redis HashListSetStringGeoHyperLogLog およびその他のデータ構造は、Java で最もよく知られた マッピング (Map) 、## にカプセル化されます。 #List,Set,General Object Bucket,Geospatial Bucket, カーディナリティ推定アルゴリズム (HyperLogLog) およびその他の構造、

  • #分散データ構造

    : これに基づいて、Multimap、LocalCachedMap、SortedSet、ScoredSortedSet、LexSortedSet、Queue、Blocking Queue、 Bounded Blocking Queue、Deque、Blocking Deque、Blocking Fair Queue、Delayed Queue、Bloom Filter、アトミック整数(AtomicLong)、アトミック倍精度浮動小数点数(AtomicDouble)、BitSetなど、Redisが本来持っていない分散データ構造。

  • 分散ロック: Redisson は、Redis ドキュメントのアプリケーション シナリオで説明されている分散ロック Lock のような高レベルのロックも実装します。実際、Redisson はそれだけではなく、分散ロックに基づいて、 Interlock (MultiLock) ReadWriteLock (ReadWriteLock) Fair Lock (Fair Lock) も提供します。 , Red Lock (RedLock) , セマフォ (Semaphore) , Expirable Semaphore (PermitExpirableSemaphore) および ロック (CountDownLatch) これらマルチスレッドの同時実行性の高いアプリケーションにとって重要な基本コンポーネントです。 Redisson が分散システムを構築するための重要なツールとなるのは、Redis に基づく高次アプリケーション ソリューションの実装を通じてです。

  • Node: 独立ノードとしての Redisson を使用して、分散実行サービス に公開された他のノードを独立して実行できます。 Distributed Scheduling Service のリモート タスク。

#2. Redisson の統合

Redisson を Spring Boot と統合するには 2 つのオプションがあります:

  • プログラムによる構成。
  • #ファイルモードの設定。
  • この記事では、Redisson をプログラムで統合する方法を紹介します。

2.1 Maven 依存関係の導入

passjava-question マイクロサービスの pom.xml に redisson の Maven 依存関係を導入します。

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.15.5</version>
</dependency>

2.2 カスタム構成クラス

次のコードは、単一ノード Redis の構成です。

@Configuration
public class MyRedissonConfig {
    /**
     * 对 Redisson 的使用都是通过 RedissonClient 对象
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod="shutdown") // 服务停止后调用 shutdown 方法。
    public RedissonClient redisson() throws IOException {
        // 1.创建配置
        Config config = new Config();
        // 集群模式
        // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
        // 2.根据 Config 创建出 RedissonClient 示例。
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }
}

2.3 テスト構成クラス

新しい単体テスト メソッドを作成します。

@Autowired
RedissonClient redissonClient;

@Test
public void TestRedisson() {
    System.out.println(redissonClient);
}

このテスト メソッドを実行し、redissonClientを出力します

org.redisson.Redisson@77f66138

3. 分散リエントラント ロック

3.1 リエントラント ロック テスト

Redis に基づく Redisson 分散リエントラント ロックRLockJava オブジェクトは、java.util.concurrent.locks.Lock インターフェイスを実装します。また、非同期 (Async)、反射型 (Reactive)、および RxJava2 標準インターフェイスも提供します。

RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();

passjava オープン ソース プロジェクトを使用して、2 つの再入可能ロックをテストします:

  • (1) 複数のスレッドがロックを捕捉します。後続のロックは待機する必要がありますか? ?
  • (2) ロックをプリエンプトしたスレッドが存在するサービスが停止した場合、ロックは解放されますか?

3.1.1 検証 1: リエントラント ロックはブロックされていますか?

上記 2 点を確認するために、デモ プログラムを作成しました。コード プロセスは、WuKong-lock ロックを設定し、それをロックし、スレッド ID を出力して待機するというものです。 10 秒間ロックを解除すると、最終的に「test lock ok」という応答が返されます。

@ResponseBody
@GetMapping("test-lock")
public String TestLock() {
    // 1.获取锁,只要锁的名字一样,获取到的锁就是同一把锁。
    RLock lock = redisson.getLock("WuKong-lock");

    // 2.加锁
    lock.lock();
    try {
        System.out.println("加锁成功,执行后续代码。线程 ID:" + Thread.currentThread().getId());
        Thread.sleep(10000);
    } catch (Exception e) {
        //TODO
    } finally {
        lock.unlock();
        // 3.解锁
        System.out.println("Finally,释放锁成功。线程 ID:" + Thread.currentThread().getId());
    }

    return "test lock ok";
}

まず最初の点を確認し、2 つの http リクエストを使用してプリエンプション ロックをテストします。

リクエストされた URL:

http://localhost:11000/question/v1/redisson/test/test-lock
分散ロックの王道ソリューション - Redisson

第一个线程对应的线程 ID 为 86,10秒后,释放锁。在这期间,第二个线程需要等待锁释放。

第一个线程释放锁之后,第二个线程获取到了锁,10 秒后,释放锁。

画了一个流程图,帮助大家理解。如下图所示:

分散ロックの王道ソリューション - Redisson
  • 第一步:线程 A 在 0 秒时,抢占到锁,0.1 秒后,开始执行等待 10 s。
  • 第二步:线程 B 在 0.1 秒尝试抢占锁,未能抢到锁(被 A 抢占了)。
  • 第三步:线程 A 在 10.1 秒后,释放锁。
  • 第四步:线程 B 在 10.1 秒后抢占到锁,然后等待 10 秒后释放锁。

由此可以得出结论,Redisson 的可重入锁(lock)是阻塞其他线程的,需要等待其他线程释放的。

3.1.2 验证二:服务停了,锁会释放吗?

如果线程 A 在等待的过程中,服务突然停了,那么锁会释放吗?如果不释放的话,就会成为死锁,阻塞了其他线程获取锁。

我们先来看下线程 A 的获取锁后的,Redis 客户端查询到的结果,如下图所示:

分散ロックの王道ソリューション - Redisson

WuKong-lock 有值,而且大家可以看到 TTL 在不断变小,说明 WuKong-lock 是自带过期时间的。

通过观察,经过 30 秒后,WuKong-lock 过期消失了。说明 Redisson 在停机后,占用的锁会自动释放。

分散ロックの王道ソリューション - Redisson

那这又是什么原理呢?这里就要提一个概念了,看门狗

分散ロックの王道ソリューション - Redisson

3.2 看门狗原理

如果负责储存这个分布式锁的 Redisson 节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。

默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

如果我们未制定 lock 的超时时间,就使用 30 秒作为看门狗的默认时间。只要占锁成功,就会启动一个定时任务:每隔 10 秒重新给锁设置过期的时间,过期时间为 30 秒。

如下图所示:

分散ロックの王道ソリューション - Redisson
看门狗原理图-1

当服务器宕机后,因为锁的有效期是 30 秒,所以会在 30 秒内自动解锁。(30秒等于宕机之前的锁占用时间+后续锁占用的时间)。

如下图所示:

分散ロックの王道ソリューション - Redisson
看门狗原理图-2

3.3 设置锁过期时间

我们也可以通过给锁设置过期时间,让其自动解锁。

如下所示,设置锁 8 秒后自动过期。

lock.lock(8, TimeUnit.SECONDS);

如果业务执行时间超过 8 秒,手动释放锁将会报错,如下图所示:

分散ロックの王道ソリューション - Redisson

所以我们如果设置了锁的自动过期时间,则执行业务的时间一定要小于锁的自动过期时间,否则就会报错。

四、王者方案

上一篇我讲解了分布式锁的五种方案:Redis 分布式锁|从青铜到钻石的五种演进方案,这一篇主要是讲解如何用 Redisson 在 Spring Boot 项目中实现分布式锁的方案。

因为 Redisson 非常强大,实现分布式锁的方案非常简洁,所以称作王者方案

原理图如下:

分散ロックの王道ソリューション - Redisson

代码如下所示:

// 1.设置分布式锁
RLock lock = redisson.getLock("lock");
// 2.占用锁
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();

和之前 Redis 的方案相比,简洁很多。

下面讲解下 Redisson 的其他几种分布式锁,相信大家在以后的项目中也会用到。

五、分布式读写锁

基于 Redis 的 Redisson 分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了 RLock接口。

写锁是一个排他锁(互斥锁),读锁是一个共享锁。

  • 读锁 + 读锁:相当于没加锁,可以并发读。
  • 读锁 + 写锁:写锁需要等待读锁释放锁。
  • 写锁 + 写锁:互斥,需要等待对方的锁释放。
  • 写锁 + 读锁:读锁需要等待写锁释放。
分散ロックの王道ソリューション - Redisson

示例代码如下:

RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

六、分布式信号量

基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

关于信号量的使用大家可以想象一下这个场景,有三个停车位,当三个停车位满了后,其他车就不停了。可以把车位比作信号,现在有三个信号,停一次车,用掉一个信号,车离开就是释放一个信号。

分散ロックの王道ソリューション - Redisson

我们用 Redisson 来演示上述停车位的场景。

先定义一个占用停车位的方法:

/**
* 停车,占用停车位
* 总共 3 个车位
*/
@ResponseBody
@RequestMapping("park")
public String park() throws InterruptedException {
  // 获取信号量(停车场)
  RSemaphore park = redisson.getSemaphore("park");
  // 获取一个信号(停车位)
  park.acquire();

  return "OK";
}

再定义一个离开车位的方法:

/**
 * 释放车位
 * 总共 3 个车位
 */
@ResponseBody
@RequestMapping("leave")
public String leave() throws InterruptedException {
    // 获取信号量(停车场)
    RSemaphore park = redisson.getSemaphore("park");
    // 释放一个信号(停车位)
    park.release();

    return "OK";
}

为了简便,我用 Redis 客户端添加了一个 key:“park”,值等于 3,代表信号量为 park,总共有三个值。

分散ロックの王道ソリューション - Redisson

然后用 postman 发送 park 请求占用一个停车位。

分散ロックの王道ソリューション - Redisson

次に、redis クライアントで park の値を確認すると、2 に変更されていることがわかります。呼び出しを 2 回続けて、park が 0 であることがわかります。4 回目の呼び出しでは、要求が waiting であることがわかり、十分な駐車スペースがないことが示されます。ブロックを回避したい場合は、tryAcquire または tryAcquireAsync を使用できます。

駐車スペースから出るメソッドを再度呼び出します。park の値は 1 に変更されます。これは、駐車スペースが 1 つ残っていることを意味します。

: セマフォ解放操作が複数回実行されると、残りのセマフォは 3 で制限されるのではなく、増加し続けます。

その他の分散ロック:

  • フェア ロック

  • インターロック (マルチロック)

  • #レッド ロック (RedLock)

  • ##読み取り/書き込みロック (ReadWriteLock)

  • 期限切れセマフォ (PermitExpirableSemaphore)

  • ラッチ (CountDownLatch)

分散ロックの王道ソリューション - Redissonこの記事では説明しない分散ロックが他にもあります。興味のある学生は公式ドキュメントを確認してください。

以上が分散ロックの王道ソリューション - Redissonの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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