ホームページ  >  記事  >  データベース  >  Redis 分散ロックで避けなければならない 2 つの落とし穴は何ですか?

Redis 分散ロックで避けなければならない 2 つの落とし穴は何ですか?

WBOY
WBOY転載
2023-05-29 22:52:041222ブラウズ

1 最初のピット: 間違ったロック解除タイミング

1.1. 問題が見つかりました

次のコードの問題を分析してください:

// 分布式锁服务
public interface RedisLockService {
    // 获取锁
    public boolean getLock(String key);
    // 释放锁
    public boolean releaseLock(String key);
}

// 业务服务
public class BizService {

    @Resource
    private RedisLockService redisLockService;

    public void bizMethod(String bizId) {
        try {
            // 获取锁
            if(redisLockService.getLock(bizId)) {
                // 业务重复校验
                if(!bizValidate(bizId)) {
                    throw new BizException(ErrorBizCode.REPEATED);
                }
                // 执行业务
                return doBusiness();
            }
            // 获取锁失败
            throw new BizException(ErrorBizCode.GET_LOCK_ERROR);
        } finally {
            // 释放锁
            redisLockService.releaseLock(bizId);
        }
    }
}

上記のコードは次のようです。いいですよ、実はこれには大きな問題が隠されています。問題は、ロックを解放するときに、現在のスレッドがロックを取得したかどうかが検証されないことです。

  • スレッド 1 とスレッド 2 が同時にビジネス メソッドにアクセスします

  • スレッド 2 ロックの取得に成功し、業務処理を実行します

  • スレッド 1 はロックの取得に失敗しましたが、ロックの解放に成功しました

  • この時点で、スレッド 3 はロックを取得しようとしました。ロックは成功しましたが、スレッド 2 の業務は処理されていないため、スレッド 3 は業務重複例外を引き起こしません。

  • 最終的には、スレッド 2 とスレッド 3 が繰り返しビジネスを実行します

1.2 問題の解決

解決策は、確認した後にのみロックを解放できるようにすることです。ロックの取得が成功したことを確認します:

public class BizService {

    @Resource
    private RedisLockService redisLockService;

    public void bizMethod(String bizId) {
        boolean getLockSuccess = false;
        try {
            // 尝试获取锁
            getLockSuccess = redisLockService.getLock(bizId);
            // 获取锁成功
            if(getLockSuccess) {
                // 业务重复校验
                if(!bizValidate(bizId)) {
                    throw new BizException(ErrorBizCode.REPEATED);
                }
                // 执行业务
                return doBusiness();
            }
            // 获取锁失败
            throw new BizException(ErrorBizCode.GET_LOCK_ERROR);
        } finally {
            // 获取锁成功才允许释放锁
            if(getLockSuccess) {
                redisLockService.releaseLock(bizId);
            }
        }
    }
}

2 2 番目の落とし穴: キャッシュ無効化の問題

いいえ。2 番目の問題は、Redis にはメモリ クリーニング メカニズムもあり、分散ロックが失敗する可能性があることです。 。

2.1 有効期限のクリーンアップメカニズム

(1) 定期的な削除

Redis は定期的にどのキーの有効期限が切れているかを確認し、有効期限が切れていることが判明した場合は削除します。期限切れ

(2) 遅延削除

#キーが多すぎる場合、通常の削除では大量のリソースが消費されるため、遅延削除戦略が導入されます

Redis がアクセス時にキーの有効期限が切れていることが判明した場合は、直接削除します。

2.2 メモリ リサイクル メカニズム

メモリが不足している場合、Redis は削除する要素をいくつか選択します:

no-enviction

データのエビクションを禁止します、新規です書き込み操作はエラーを報告します

volatile-lru

データ セットから最も最近使用されていないデータを選択します削除する有効期限

volatile-ttl

削除する有効期限が設定されたデータセットから期限切れにするデータを選択します

volatile-random

有効期限が設定されたデータ セットから削除するデータを選択します

allkeys- lru

削除するデータ セットから最も最近使用されていないデータを選択します

allkeys-random

削除するデータ セットから任意のデータを選択してください

分散ロック障害の問題を引き起こすシナリオは少なくとも 2 つあります:

  • シナリオ 1 : Redis にはメモリをリサイクルするためのメモリが不足しています。

    allkeys-lru または allkeys-random を使用してください。リサイクル戦略によりロックが失敗します

  • シナリオ 2:スレッドは分散ロックの取得に成功しましたが、処理時間が長すぎます。このとき、ロックの有効期限が切れて定期的にクリアされるため、他のスレッドがロックの取得に成功しても失敗します。業務の繰り返し実行

2.3 楽観的ロック

一般的な解決策は、データベース層で保護することです。たとえば、在庫控除ビジネスでは、データベース層で楽観的ロックを使用します。

rree

以上がRedis 分散ロックで避けなければならない 2 つの落とし穴は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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