ホームページ > データベース > Redis > 分散ロックの原理と、Redis が分散ロックを実装する方法について話しましょう

分散ロックの原理と、Redis が分散ロックを実装する方法について話しましょう

藏色散人
リリース: 2023-01-27 07:30:01
転載
1197 人が閲覧しました

この記事では、主に分散ロックとは何かを紹介する、redis に関する関連知識を提供します。 Redis は分散ロックをどのように実装しますか?どのような条件を満たす必要がありますか?以下を見てみましょう。困っている友達のお役に立てれば幸いです。

1. 分散ロックの基本原則

分散ロック: 分散システムまたはクラスター モードの複数のプロセスに表示され、相互に排他的なロック。

分散ロックが満たすべき条件:

  • 可視性: 複数のスレッドが同じ結果を確認できる 注: ここで言及されている可視性は、同時プログラミングにおけるメモリの可視性を指すものではなく、複数のプロセスが変更を認識できるということだけを指します。除外: 相互排他は分散ロックの最も基本的な条件であり、プログラムをシリアルに実行します。
  • 高可用性: プログラムはクラッシュしにくく、常に高可用性が保証されます。
  • 高パフォーマンス: ロック自体がパフォーマンスを低下させるため、すべての分散ロックにはより高いロック パフォーマンスとロック解放パフォーマンスが必要です
  • 安全性: セキュリティもプログラムの一部です重要なリンク
は 3 つの一般的な分散ロックです:

    Mysql: mysql 自体にはロック メカニズムがありますが、mysql 自体の平均的なパフォーマンスのため、分散ロックを使用する場合、実際には比較的まれです。 mysql を分散ロックとして使用するには
  • Redis: redis は分散ロックとして非常に一般的です この使用法では、現在のエンタープライズ レベルの開発では基本的に Redis または Zookeeper を分散ロックとして使用します。 setnx メソッド、キーの挿入が成功した場合、ロックが取得されたことを意味します。誰かが挿入に成功し、他の人が挿入に失敗した場合、ロックを取得できないことを意味します。ロック、このロジック セットを使用して分散ロックを実装します。
  • Zookeeper: Zookeeper は、エンタープライズ レベルの開発で分散ロックを実装するための優れたソリューションでもあります

分散ロックの原理と、Redis が分散ロックを実装する方法について話しましょう 2. Redis に基づく分散ロックの実装

#分散ロックを実装する場合は、次の 2 つの基本メソッドを実装する必要があります:

    ロックの取得:
  • 相互排他: 1 つのスレッドのみがロックを取得できるようにします
    • ノンブロッキング: 1 回試行し、成功した場合は true、失敗した場合は false を返します
  • ロックの解放:
  • 手動解放
    • タイムアウト解放: ロック取得時にタイムアウトを追加
Redis に基づく分散ロックの原則:

SET resource_name my_random_value NX PX 30000
ログイン後にコピー

resource_name: リソース名、さまざまなビジネスに応じてさまざまなロックを区別できます
  • my_random_value: ランダムな値、ランダム各スレッドの値は異なり、ロック解除時の検証に使用されます。
  • NX: キーが存在しない場合は設定成功、キーが存在する場合は設定失敗
  • PX:自動有効期限、異常が発生した場合、ロックを期限切れにすることができます。
  • NX のアトミック性を利用し、複数のスレッドが並行している場合、1 つのスレッドのみが正常に設定されます。設定の成功はロックの取得を意味します。後続の業務処理を実行できます。例外が発生してロックの有効期限が切れると、ロックは自動的に解放されます。

バージョン 1

1. ILock インターフェイスの定義

public interface ILock extends AutoCloseable {
    /**
     * 尝试获取锁
     *
     * @param timeoutSec 锁持有的超时时间,过期后自动释放
     * @return true代表获取锁成功;false代表获取锁失败
     */
    boolean tryLock(long timeoutSec);

    /**
     * 释放锁
     * @return
     */
    void unLock();
}
ログイン後にコピー

2. Redis に基づく分散ロックの実装 - RedisLock

public class SimpleRedisLock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String name;

    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String threadId = Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unLock() {
        //通过del删除锁
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }

    @Override
    public void close() {
        unLock();
    }
}
ログイン後にコピー
ロックの誤削除の問題

問題の説明:

ロックを保持しているスレッド 1 はロック内でブロックされています。このとき、ロックはタイムアウト後に自動的に解放されます。このとき、スレッド 2 はロックの取得を試みます。その後、スレッド 2 の実行中に、ロックを保持しています。ロックを実行すると、スレッド 1 が反応して実行を継続します。ロック ロジックを削除すると、スレッド 2 に属するはずのロックが削除されます。これは、ロックを誤って削除した場合です。

解決策:

ロックを保存するときは、自分のスレッドの識別子を入力し、ロックを削除するときは、現在のロックの識別子が自分のスレッドの識別子であるかどうかを確認します。保存されています。入力されている場合は削除され、入力されていない場合は削除されません。

バージョン 2: 偶発的なロック削除の問題を解決する

public class SimpleRedisLock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String name;

    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unLock() {
        // 获取线程标示
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 获取锁中的标示
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        // 判断标示是否一致
        if(threadId.equals(id)) {
            // 释放锁
            stringRedisTemplate.delete(KEY_PREFIX + name);
        }
    }

    @Override
    public void close() {
        unLock();
    }
}
ログイン後にコピー

ロック解放の原子性の問題

問題分析:

上記の解放ロックコードには依然として誤ってロックを削除してしまう問題があり、スレッド 1 がロック内のスレッド ID を取得し、その ID に基づいて自分のロックであると判断すると、ロックが期限切れになると自動的にロックが解放されます。スレッド 2 がロックを取得しようとしてロックを取得しますが、スレッド 1 がロックを解放する操作を実行しているときに、スレッド 2 が保持しているロックが誤って削除されてしまいます。

その理由は、Java コードによって実装されるロック解放プロセスがアトミックな操作ではなく、スレッドの安全性の問題があるためです。

解決策:

Redis には Lua スクリプト機能があり、複数の Redis コマンドを 1 つのスクリプトに記述することで、複数のコマンドの実行をアトミックに実行できます。

バージョン 3: Lua スクリプトを呼び出して分散ロックを変換する

public class SimpleRedisLock implements ILock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String name;

    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unLock() {
        String script = "if redis.call("get",KEYS[1]) == ARGV[1] then\n" +
                " return redis.call("del",KEYS[1])\n" +
                "else\n" +
                " return 0\n" +
                "end";
        //通过执行lua脚本实现锁删除,可以校验随机值
        RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
        stringRedisTemplate.execute(redisScript,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX + Thread.currentThread().getId());
    }

    @Override
    public void close() {
        unLock();
    }
}
ログイン後にコピー

推奨学習: 「

Redis ビデオ チュートリアル


以上が分散ロックの原理と、Redis が分散ロックを実装する方法について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.im
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート