Redisは、文字列、ハッシュ、リストなどを含むさまざまなデータ構造をサポートするインメモリのキー/値ストレージ システムです。 Redis は、楽観的ロックと悲観的ロックという 2 つのロック メカニズムを提供します。
オプティミスティック ロックはオプティミスティックな同時実行制御戦略であり、ほとんどの場合、データは他のスレッドによって占有されないと考えられるため、データの変更が必要になるたびに占有されます。 not ロックが取得され、変更が直接行われます。 Redis では、WATCH および CAS コマンドを使用してオプティミスティック ロックを実装できます。WATCH コマンドは 1 つ以上のキーの監視に使用され、CAS コマンドはキーの値の確認と更新に使用されます。
たとえば、キー名が counter のカウンターがある場合、複数のクライアントがそれを操作する必要があります。 WATCH コマンド
WATCH counter current_count = GET counter new_count = current_count + 1 MULTI SET counter new_count EXEC
を実行することで、オプティミスティック ロックを使用して各クライアント操作の前にカウンター キーを監視できます。その後、EXEC コマンドを実行する前に、GET コマンドを使用してカウンター キーの値を再度取得し、保存します。以前に取得した値と比較します。値が等しい場合は、期間中に他のクライアントがカウンター キーを変更していないことを意味します。このとき、CAS コマンドを使用して新しい値をカウンター キーに設定できます。値が異なる場合は、この期間中に他のクライアントがカウンター キーを変更したことを意味し、操作を再度実行する必要があります。
GET counter
悲観的ロックは悲観的な同時実行制御戦略です。ほとんどの場合、データは他のスレッドによって占有されると考えられるため、データを変更する必要があるたびに、まずロックを取得して、変更中に他のスレッドがデータにアクセスできないようにします。 Redis では、1 つ以上のキーを監視できる WATCH コマンドを通じて悲観的ロックを実装でき、トランザクションの実行中に監視対象のキーの値が変更されると、トランザクション全体がロールバックされます。
引き続き上記の例です
WATCH counter current_count = GET counter new_count = current_count + 1 MULTI SET counter new_count EXEC
トランザクションの実行中に他のクライアントがカウンター キーを変更すると、トランザクション全体がロールバックされるため、再実行する必要があります。
悲観的ロックはデータの一貫性という利点を確保できますが、欠点は、最初にロックを取得する必要があるため、スレッドのブロックが発生し、同時実行パフォーマンスに影響を与える可能性があることです。
ユーザーが商品を購入できる電子商取引プラットフォームがあると仮定します。製品在庫の削減を一貫して行うために、Redis のオプティミスティック ロック メカニズムを使用してこれを実現できます。
まず、ハッシュ データ構造を使用して、各製品の在庫情報を Redis に保存する必要があります。たとえば、次のようになります。
次に、ビジネス ロジックで、ユーザーが購入したときに、
WATCH コマンドを使用して、stock:sku001; などの製品在庫キーを監視します。
GET コマンドを使用して現在の製品在庫数量を取得します。
##HSET コマンドを使用して、新しい在庫数量を Redis に保存します。
実行中に他の顧客がいる場合はトランザクションを実行します。最後に製品在庫が変更された場合、トランザクションはロールバックされるため、再実行する必要があります。
以下は Spring Boot を使用して実装されたサンプル コードです:
@Service public class OrderService { private final RedisTemplate<String, Integer> redisTemplate; @Autowired public OrderService(RedisTemplate<String, Integer> redisTemplate) { this.redisTemplate = redisTemplate; } public void placeOrder(String sku, int quantity) { String stockKey = "stock:" + sku; while (true) { // 监视商品库存键,以便在事务开始前检测是否有其他客户端修改了库存 redisTemplate.watch(stockKey); // 获取当前库存数量 int currentStock = redisTemplate.opsForHash().get(stockKey, sku); // 检查库存是否足够 if (currentStock < quantity) { // 库存不足,放弃事务并抛出异常 redisTemplate.unwatch(); throw new RuntimeException("Out of stock"); } // 计算新的库存数量 int newStock = currentStock - quantity; // 开始事务 redisTemplate.multi(); // 更新库存数量 redisTemplate.opsForHash().put(stockKey, sku, newStock); // 提交事务 List<Object> results = redisTemplate.exec(); // 如果事务执行成功,则退出循环 if (results != null) { break; } // 如果事务执行失败,则重试 } } }
悲観的ロックの例
Redis は、楽観的ロックに加えて、NX (存在しない) または XX (存在) フラグを設定することで実現できる悲観的ロックもサポートしています。たとえば、NX フラグが true に設定されている場合、ロックが存在しない場合は OK が返され、ロックが作成されます。ロックがすでに存在する場合は、ロックの取得に失敗したことを示す null が返されます。逆に、XX フラグが true の場合、既にロックが存在する場合はロックの取得が成功したことを示す OK が返され、ロックが存在しない場合はロックの取得が失敗したことを示す null が返されます。 。
@Service public class OrderService { private final RedisTemplate<String, String> redisTemplate; @Autowired public OrderService(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } public void placeOrder(String sku, int quantity) { String lockKey = "lock:" + sku; // 尝试获取锁,如果锁已经存在,说明有其他线程正在执行相关操作 Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked"); if (!locked) { // 获取锁失败,抛出异常 throw new RuntimeException("Unable to acquire lock"); } // 设置锁的过期时间,防止锁被一直占用 redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS); try { // 执行订单创建、扣减库存等操作 } finally { // 释放锁 redisTemplate.delete(lockKey); } } }
上記のコードでは、setIfAbsent メソッドを使用してロックの取得を試みます。ロックがすでに存在する場合は、他のスレッドが実行中であることを意味します 関連する操作については、この時点で false が返され、ロックの取得が失敗したことを示します。それ以外の場合は、true が返され、ロックの取得が成功したことを示します。まずロックを取得し、次にロックの有効期限を設定して対応する操作を実行し、最後にロックを解放します。
以上がRedisの楽観的ロックと悲観的ロックの使い方の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。