ホームページ  >  記事  >  Java  >  Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

Java后端技术全栈
Java后端技术全栈転載
2023-08-23 14:54:26666ブラウズ

#この記事の主な内容は次のとおりです:

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

##1. ローカルロックの問題

まず、ローカル ロックの問題を確認してみましょう。

問題の現在のマイクロサービスは 4 つのマイクロサービスに分割されています。フロントエンドリクエストが到着すると、それらは別のマイクロサービスに転送されます。フロントエンドが 10 W のリクエストを受信し、各マイクロサービスが 2.5 W のリクエストを受信した場合、キャッシュが失敗すると、各マイクロサービスはデータベースにアクセスするときにロック (

synchronzied または lock ) を通じてロックされます。 キャッシュの故障を防ぐために、独自のスレッド リソースをロックします。

これは

ローカル ロックメソッドであり、分散状況でデータの不整合を引き起こします。たとえば、サービス A がデータを取得した後、キャッシュ キー =100 を更新します。 、サービス B はサービス A のロック制限を受けず、同時にキャッシュ キー = 99 を更新します。最終結果は 99 または 100 になる可能性がありますが、これは不明な状態です。 は期待される結果と矛盾します 。フローチャートは次のとおりです。

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

2. 分散ロックとは何ですか

上記のローカル ロックの問題に基づいて、サポートが必要です分散クラスター環境ロック の下: DB にクエリを実行する場合、DB にアクセスできるのは 1 つのスレッドだけであり、他のスレッドは実行を続行する前に、最初のスレッドがロック リソースを解放するまで待つ必要があります。

人生の事例: ロックはドアの外の ロック と見なされ、すべての同時スレッドが people と比較されます。誰もがその部屋に入りたいのですが、部屋に入ることができるのは 1 人だけです。誰かが入ってきたら、ドアを施錠し、他の人は入ってきた人が出てくるまで待たなければなりません。

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

次の図に示すように、分散ロックの基本原理を見てみましょう。分析してください。上の図の分散ロック:

  • 1. フロントエンドは、10W の高同時実行リクエストを 4 つのトピック マイクロサービスに転送します。
  • 2.各マイクロサービスは 2.5 W のリクエストを処理します。
  • 3. リクエストを処理する各スレッドは、ビジネスを実行する前にロックを取得する必要があります。それは「穴を占領する」と理解できます。
  • 4. ロックを取得したスレッドは、業務終了後にロックを解放します。それは「穴を解放する」と理解できます。
  • 5. 取得されていないスレッドは、ロックが解放されるまで待つ必要があります。
  • 6. ロックが解放された後、他のスレッドがロックを捕捉します。
  • 7. 手順 4、5、6 を繰り返します。

現地語での説明: 要求されたすべてのスレッドは同じ場所に移動します「ピットを占有します」。ピットがある場合、ビジネス ロジックが実行されます。ピットはありません。「ピット」を解放するには別のスレッドが必要です。このピットはすべてのスレッドに表示されます。このピットを Redis キャッシュまたはデータベースに置くことができます。この記事では、Redis を使用して 「分散ピット」 を作成する方法について説明します。

3. Redis の SETNX

Redis はパブリックにアクセスできる場所であるため、「活用する」場所として使用できます。

Redis を使用して分散ロックを実装するためのいくつかのソリューションでは、すべて SETNX コマンド (キーを特定の値に設定) を使用します。ハイエンドスキームで渡されるパラメータの数のみが異なり、異常事態が考慮されます。

このコマンドを見てみましょう。SETNX は、set If not存在の略です。これは、キーが存在しない場合はキーの値を設定し、キーが存在する場合は何もしないことを意味します。

Redis コマンド ラインでの実行方法は次のとおりです:

set <key> <value> NX

Redis コンテナーに入って、SETNX コマンドを試すことができます。

最初にコンテナを入力してください:

docker exec -it <容器 id> redis-cli

然后执行 SETNX 命令:将 wukong 这个 key 对应的 value 设置成 1111

set wukong 1111 NX

返回 OK,表示设置成功。重复执行该命令,返回 nil表示设置失败。

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

四、青铜方案

我们先用 Redis 的 SETNX 命令来实现最简单的分布式锁。

3.1 青铜原理

我们来看下流程图:

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン
  • 多个并发线程都去 Redis 中申请锁,也就是执行 setnx 命令,假设线程 A 执行成功,说明当前线程 A 获得了。
  • 其他线程执行 setnx 命令都会是失败的,所以需要等待线程 A 释放锁。
  • 线程 A 执行完自己的业务后,删除锁。
  • 其他线程继续抢占锁,也就是执行 setnx 命令。因为线程 A 已经删除了锁,所以又有其他线程可以抢占到锁了。

代码示例如下,Java 中 setnx 命令对应的代码为 setIfAbsent

setIfAbsent 方法的第一个参数代表 key,第二个参数代表值。

// 1.先抢占锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "123");
if(lock) {
  // 2.抢占成功,执行业务
  List<TypeEntity> typeEntityListFromDb = getDataFromDB();
  // 3.解锁
  redisTemplate.delete("lock");
  return typeEntityListFromDb;
} else {
  // 4.休眠一段时间
  sleep(100);
  // 5.抢占失败,等待锁释放
  return getTypeEntityListByRedisDistributedLock();
}

一个小问题:那为什么需要休眠一段时间?

因为该程序存在递归调用,可能会导致栈空间溢出。

3.2 ブロンズ ソリューションの欠陥

ブロンズがブロンズと呼ばれる理由は、それが最も基本的であり、間違いなく多くの問題を引き起こすためです。

家族の風景を想像してみてください: 夜、シャオコンが一人でドアの鍵を開けて部屋に入り、電気をつけますか?その後、突然 停電になりました、シャオコンはドアを開けて外に出ようとしますが、ドアのロック位置が見つからないと、シャオミンも中に入ることができず、外にいる人も入ることができません。

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン
技術的な観点から見ると、setnx はロックを正常に占有し、ビジネス コードが異常であるかサーバーがダウンしており、ロックを削除するロジックは実行されませんでした。その結果、

デッドロックが発生します。

では、このリスクを回避するにはどうすればよいでしょうか?

ロックの

自動有効期限を設定します。一定時間が経過すると、ロックは自動的に削除され、他のスレッドがロックを取得できるようになります。

4. シルバー ソリューション

4.1 実例での例

上記のブロンズ ソリューションにはデッドロックの問題があります。上記のリスク回避計画を使用して設計します。これが当社のシルバープランです。

人生の例: Xiao Kong はロックのロック解除に成功した後、スマート ロックの Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン砂時計のカウントダウン⏳
を設定しました。完了すると、ドアが自動的に開きます。急にお部屋が停電しても、しばらくすると自動的に鍵が開き、他の人が入ってくることができます。

4.2 技術回路図

ブロンズ ソリューションとの違いは、ロックの占有に成功した後、ロックの有効期限を設定することです。これら 2 つのステップは段階的に実行されます。ステップ。 。以下に示すように:
Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

4.3 示例代码

清理 redis key 的代码如下

// 在 10s 以后,自动清理 lock
redisTemplate.expire("lock", 10, TimeUnit.SECONDS);

完整代码如下:

// 1.先抢占锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "123");
if(lock) {
    // 2.在 10s 以后,自动清理 lock
    redisTemplate.expire("lock", 10, TimeUnit.SECONDS);
    // 3.抢占成功,执行业务
    List<TypeEntity> typeEntityListFromDb = getDataFromDB();
    // 4.解锁
    redisTemplate.delete("lock");
    return typeEntityListFromDb;
}

4.4 白银方案的缺陷

白银方案看似解决了线程异常或服务器宕机造成的锁未释放的问题,但还是存在其他问题:

因为占锁和设置过期时间是分两步执行的,所以如果在这两步之间发生了异常,则锁的过期时间根本就没有设置成功。

所以和青铜方案有一样的问题:锁永远不能过期

五、黄金方案

5.1 原子指令

上面的白银方案中,占锁和设置锁过期时间是分步两步执行的,这个时候,我们可以联想到什么:事务的原子性(Atom)。

原子性:多条命令要么都成功执行,要么都不执行。

将两步放在一步中执行:占锁+设置锁过期时间。

Redis 正好支持这种操作:

# 设置某个 key 的值并设置多少毫秒或秒 过期。
set <key> <value> PX <多少毫秒> NX
或
set <key> <value> EX <多少秒> NX

然后可以通过如下命令查看 key 的变化

ttl <key>

下面演示下如何设置 key 并设置过期时间。注意:执行命令之前需要先删除 key,可以通过客户端或命令删除。

# 设置 key=wukong,value=1111,过期时间=5000ms
set wukong 1111 PX 5000 NX
# 查看 key 的状态
ttl wukong

执行结果如下图所示:每运行一次 ttl 命令,就可以看到 wukong 的过期时间就会减少。最后会变为 -2(已过期)。

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

5.2 技术原理图

黄金方案和白银方案的不同之处:获取锁的时候,也需要设置锁的过期时间,这是一个原子操作,要么都成功执行,要么都不执行。如下图所示:

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

5.3 示例代码

设置 lock 的值等于 123,过期时间为 10 秒。如果 10 秒 以后,lock 还存在,则清理 lock。

setIfAbsent("lock", "123", 10, TimeUnit.SECONDS);

5.4 黄金方案的缺陷

我们还是举生活中的例子来看下黄金方案的缺陷。

5.4.1 ユーザー A がロックをプリエンプトします

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン
  • ユーザー A が最初にロックをプリエンプトし、10 秒後に自動的にロックが解除されるように設定します。番号は 123 です。
  • 10 秒後、A はまだタスクを実行しており、ロックは自動的に開きます。

5.4.2 ユーザー B が鍵を握る

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン
  • ユーザー B は、部屋の鍵がかかっていることを確認します。が開いています。そのため、ロックがプリエンプトされ、ロック番号が 123 に設定され、有効期限が 10 秒 に設定されました。
  • ルーム内でタスクを実行できるのは 1 人のユーザーだけであるため、ユーザー A とユーザー B がタスクを実行した 結果、競合が発生しました。
  • ユーザー A は
    15 秒 後にタスクを完了しましたが、ユーザー B はまだタスクを実行していました。
  • ユーザー A は、率先して
    123 の錠を開けました。
  • ユーザー B はまだタスクを実行中ですが、ロックが開いていることに気づきました。
  • ユーザー B はとても怒っています:
    まだタスクが終わっていないのに、どうして鍵が開いてしまうのですか?
5.4.3 ユーザー C がロックを取得します

Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン
  • ユーザー B のロックは次のユーザーによって取得されます。 A 開封後、A は部屋を出ましたが、B はまだタスクを実行しています。
  • ユーザー C がロックを奪い、C がタスクの実行を開始します。
  • ルーム内でタスクを実行できるのは 1 人のユーザーだけであるため、タスクを実行するユーザー B とユーザー C の間に競合が発生します。
  • 上記のケースから、ユーザー A がタスク を処理するのに必要な時間は、自動ロックのクリーニング (ロック解除) の時間 よりも長いため、その後、別のユーザーがロックを奪取しました。ユーザー A がタスクを完了すると、他のユーザーが占有したロックを積極的に開けます。

    なぜここで他の人の鍵が開いているのですか? ロック番号はすべて "123" と呼ばれるため、ユーザー A はロック番号のみを認識し、"123" という番号のロックを見たときにロックを開きます。その結果、 、ユーザー B のロックが開いています。、ユーザー B は現時点ではタスクを完了していません。もちろん、彼は怒っています。

    6. プラチナ プラン

    6.1 実際の例

    上記のゴールド プランの欠点も簡単に解決できます。 各ロックに異なる番号を設定すると良いのではないでしょうか~

    下の図に示すように、B によってプリエンプトされたロックは青色であり、B によってプリエンプトされた緑色のロックとは異なります。 A.こうすればAさんには開けられなくなります。

    わかりやすいようにアニメーション画像を作成しました:

    Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プランアニメーション画像のデモンストレーション
    静止画像はより高精細で、撮影できます。外観:

    Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

    6.2 技術回路図

    ゴールデン ソリューションとの違い:

    • 设置锁的过期时间时,还需要设置唯一编号。
    • 主动删除锁的时候,需要判断锁的编号是否和设置的一致,如果一致,则认为是自己设置的锁,可以进行主动删除。
    Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

    6.3 代码示例

    // 1.生成唯一 id
    String uuid = UUID.randomUUID().toString();
    // 2. 抢占锁
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
    if(lock) {
        System.out.println("抢占成功:" + uuid);
        // 3.抢占成功,执行业务
        List<TypeEntity> typeEntityListFromDb = getDataFromDB();
        // 4.获取当前锁的值
        String lockValue = redisTemplate.opsForValue().get("lock");
        // 5.如果锁的值和设置的值相等,则清理自己的锁
        if(uuid.equals(lockValue)) {
            System.out.println("清理锁:" + lockValue);
            redisTemplate.delete("lock");
        }
        return typeEntityListFromDb;
    } else {
        System.out.println("抢占失败,等待锁释放");
        // 4.休眠一段时间
        sleep(100);
        // 5.抢占失败,等待锁释放
        return getTypeEntityListByRedisDistributedLock();
    }
    • 1.生成随机唯一 id,给锁加上唯一值。
    • 2.抢占锁,并设置过期时间为 10 s,且锁具有随机唯一 id。
    • 3.抢占成功,执行业务。
    • 4.执行完业务后,获取当前锁的值。
    • 5.如果锁的值和设置的值相等,则清理自己的锁。

    6.4 铂金方案的缺陷

    上面的方案看似很完美,但还是存在问题:第 4 步和第 5 步并不是原子性的。

    Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン
    • 时刻:0s。线程 A 抢占到了锁。

    • 时刻:9.5s。线程 A 向 Redis 查询当前 key 的值。

    • 时刻:10s。锁自动过期。

    • 时刻:11s。线程 B 抢占到锁。

    • 时刻:12s。线程 A 在查询途中耗时长,终于拿多锁的值。

    • 时刻:13s。线程 A 还是拿自己设置的锁的值和返回的值进行比较,值是相等的,清理锁,但是这个锁其实是线程 B 抢占的锁。

    那如何规避这个风险呢?钻石方案登场。

    七、钻石方案

    上面的线程 A 查询锁和删除锁的逻辑不是原子性的,所以将查询锁和删除锁这两步作为原子指令操作就可以了。

    7.1 技术原理图

    如下图所示,红色圈出来的部分是钻石方案的不同之处。用脚本进行删除,达到原子操作。

    Redis分散ロック|ブロンズからダイヤモンドへの5つの進化プラン

    7.2 代码示例

    那如何用脚本进行删除呢?

    我们先来看一下这段 Redis 专属脚本:

    if redis.call("get",KEYS[1]) == ARGV[1]
    then
        return redis.call("del",KEYS[1])
    else
        return 0
    end

    这段脚本和铂金方案的获取key,删除key的方式很像。先获取 KEYS[1] 的 value,判断 KEYS[1] 的 value 是否和 ARGV[1] 的值相等,如果相等,则删除 KEYS[1]。

    那么这段脚本怎么在 Java 项目中执行呢?

    分两步:先定义脚本;用 redisTemplate.execute 方法执行脚本。

    // 脚本解锁
    String script = "if redis.call(&#39;get&#39;,KEYS[1]) == ARGV[1] then return redis.call(&#39;del&#39;,KEYS[1]) else return 0 end";
    redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);

    上面的代码中,KEYS[1] 对应“lock”,ARGV[1] 对应 “uuid”,含义就是如果 lock 的 value 等于 uuid 则删除 lock。

    而这段 Redis 脚本是由 Redis 内嵌的 Lua 环境执行的,所以又称作 Lua 脚本。

    那钻石方案是不是就完美了呢?有没有更好的方案呢?

    下篇,我们再来介绍另外一种分布式锁的王者方案:Redisson。

    8. 概要

    この記事では、分散ロックの問題からローカル ロックの問題までを紹介します。次に、5 つの分散ロック ソリューションを紹介し、浅いものから深いものまでさまざまなソリューションの改善について説明します。

    上記のソリューションの継続的な進化から、システムのどこに異常な状況が存在する可能性があるか、そしてそれらをより適切に処理する方法がわかりました。

    1 つの例から推論すると、この進化する思考モデルは他のテクノロジーにも適用できます。

    以下は、上記の 5 つのソリューションの欠点と改善点をまとめたものです。

    ブロンズ ソリューション:

    • 欠陥: ビジネス コードが異常であるか、サーバーがダウンしているため、ロックを積極的に削除するロジックが機能しません。実行され、死亡する。ロック。
    • 改善点: ロックの自動有効期限を設定します。一定時間が経過すると、ロックは自動的に削除され、他のスレッドがロックを取得できるようになります。

    #シルバー ソリューション:

    • 問題点: ロックの占有とロックの有効期限の設定は 2 つのステップで実行されます。アトミック操作ではありません。
    • 改善点: ロックを占有してロックの有効期限を設定することで、アトミックな操作が保証されます。

    ゴールデンソリューション:

    • 欠陥: ロックがアクティブに削除されると、ロック値はすべて同様に、他のクライアントが占有しているロックも削除されます。
    • 改善点: ロックが占有されるたびに、ロックはより大きな値にランダムに設定され、ロックがアクティブに削除されると、ロックの値は自分で設定した値と比較されます。等しいかどうかを確認します。

    プラチナ ソリューション:

    • 欠陥: ロックの取得、ロックの値の比較、ロックの削除、これらの 3 つのステップは間違っています Atomic。ロックが途中で自動的に期限切れになり、他のクライアントによって占有されたため、ロックの削除時に他のクライアントが占有していたロックも削除された可能性があります。
    • 改善点: Lua スクリプトを使用して、ロックの取得、ロックの比較、ロックの削除のアトミック操作を実行します。

    ダイヤモンドプラン:

    • 欠点: 専門的ではない分散ロック ソリューション。
    • 改善: 分散ロックの再分割。

    王様の計画、次の記事でお会いしましょう~

    以上がRedis分散ロック|ブロンズからダイヤモンドへの5つの進化プランの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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