Home  >  Article  >  Database  >  How to use redis optimistic lock and pessimistic lock

How to use redis optimistic lock and pessimistic lock

王林
王林forward
2023-05-28 17:58:092074browse

Concept

Redis is an in-memory key-value storage system that supports a variety of data structures, including strings, hashes, lists, etc. Redis provides two locking mechanisms, namely optimistic locking and pessimistic locking.

Optimistic lock

Optimistic lock is an optimistic concurrency control strategy. It believes that data will not be occupied by other threads in most cases, so every time the data needs to be modified, it does not The lock is acquired and the modification is made directly. In Redis, optimistic locking can be implemented through the WATCH and CAS commands. The WATCH command is used to monitor one or more keys, and the CAS command is used to check and update the value of the key.

For example, if there is a counter with the key name counter, multiple clients need to operate on it. You can use optimistic locking to monitor the counter key before each client operation by executing the WATCH command

WATCH counter
current_count = GET counter
new_count = current_count + 1
MULTI
SET counter new_count
EXEC

Then, before the EXEC command is executed, use the GET command to obtain the value of the counter key again and save it. Compare with the previously obtained value. If the values ​​are equal, it means that no other client has modified the counter key during the period. At this time, you can use the CAS command to set the new value to the counter key. If the values ​​are different, it means that other clients have modified the counter key during this period and the operation needs to be performed again.

GET counter

Pessimistic lock

Pessimistic lock is a pessimistic concurrency control strategy. It believes that the data will be occupied by other threads in most cases, so every time the data needs to be modified, it will first Obtain the lock to ensure that no other thread can access the data during modification. In Redis, pessimistic locking can be implemented through the WATCH command, which can monitor one or more keys. If the value of any monitored key changes during transaction execution, the entire transaction will be rolled back.

Still the above example

WATCH counter
current_count = GET counter
new_count = current_count + 1
MULTI
SET counter new_count
EXEC

If other clients modify the counter key during the execution of the transaction, the entire transaction will be rolled back and needs to be re-executed.

Although pessimistic locking can ensure the advantage of data consistency, its disadvantage is that it needs to acquire the lock first, which may cause thread blocking, thereby affecting concurrency performance.

Optimistic lock example

Suppose there is an e-commerce platform where users can purchase goods. In order to ensure that the reduction of product inventory is consistent, Redis's optimistic locking mechanism can be used to achieve this.

First, we need to save the inventory information of each product in Redis, using a hash data structure to save it, for example:

Then, in the business logic, when the user purchases a product, The following steps need to be performed:

  • Use the WATCH command to monitor the product inventory key, such as stock:sku001;

  • Use the GET command to obtain the current product inventory quantity ;

  • Check whether the product inventory is sufficient, if not, return an error message directly;

  • Calculate the new inventory quantity and use the MULTI command to open it A transaction;

  • Use the HSET command to save the new inventory quantity into Redis;

  • Execute the transaction if there are other customers during execution If the product inventory is modified on the end, the transaction will be rolled back and needs to be re-executed.

The following is a sample code implemented using 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;
            }
            // 如果事务执行失败,则重试
        }
    }
}

In the above code, we use RedisTemplate to operate Redis, where the watch method is used to monitor product inventory key, the opsForHash method is used to obtain and modify the value of product inventory, and the multi and exec methods are used to open and submit transactions.

Pessimistic lock example

In addition to optimistic locking, Redis also supports pessimistic locking, which can be achieved by setting the NX (Not Exist) or XX (Exist) flag. For example, when the NX flag is set to true, if the lock does not exist, OK will be returned and a lock will be created; if the lock already exists, null will be returned, indicating failure to obtain the lock. On the contrary, when the XX flag is set to true, if the lock already exists, OK will be returned, indicating that the lock acquisition is successful; if the lock does not exist, null will be returned, indicating that the lock acquisition failed.

The following is a pessimistic lock example code implemented using Spring Boot:

@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);
        }
    }
}

In the above code, we use the setIfAbsent method to try to acquire the lock. If the lock already exists, it means that other threads are executing For related operations, false will be returned at this time, indicating that the lock acquisition failed; otherwise, true will be returned, indicating that the lock acquisition was successful. We will first acquire the lock, then set the expiration time of the lock and perform corresponding operations, and finally release the lock.

The above is the detailed content of How to use redis optimistic lock and pessimistic lock. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete