Distributed lock [distributed lock implemented by database optimistic lock, principle of Zookeeper distributed lock, distributed lock implemented by Redis] (3) - comprehensive detailed explanation (learning summary --- from entry to deepening)

 

Table of contents

 Distributed lock solution_distributed lock implemented by database optimistic lock

Distributed lock solution_Distributed lock principle implemented by Redis

 Distributed lock solution_Distributed lock implemented by Redis

Distributed lock solution_Redis distributed lock accidental deletion problem

Distributed lock solution_Redis distributed lock non-reentrant problem

Distributed lock solution_Distributed lock implementation based on Redisson

Distributed lock solution_Zookeeper distributed lock principle

 Distributed lock solution_Distributed lock based on Zookeeper

Comparison of three distributed locks


 

 Distributed lock solution_distributed lock implemented by database optimistic lock

 What is optimistic locking

Always assume the best situation. Every time you go to get the data, you think that others will not modify it, so it will not be locked, but when updating, you will judge whether others have updated the data during this period. You can use the version Number mechanism and CAS algorithm implementation.

 Write an optimistic lock update statement

<update id="decreaseStockForVersion" parameterType="int" >
        UPDATE product SET count = count - # {count}, version = version + 1 WHERE id = #{id} AND count > 0 AND version = #{version}
</update>

Write and create order business layer

/**
     * 创建订单 乐观锁
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String createOrder(Integer productId, Integer count) throws Exception {
        int retryCount = 0;
        int update = 0;
        // 1、根据商品id查询商品信息
        Product product = productMapper.selectById(productId);
        // 2、判断商品是否存在
        if (product == null) {
            throw new RuntimeException("购买商品不存在:" + productId + "不存在");
       }
        // 3、校验库存
        if (count > product.getCount()) {
            throw new Exception("库存不够");
       }
         // 乐观锁更新库存
        // 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回
        // 最多重试3次
        while(retryCount < 3 && update == 0){
            update = this.reduceStock(product.getId(),count);
            retryCount++;
       }
        if (update == 0){
            throw new Exception("库存不够");
       }
        // 6、 创建订单
        TOrder order = new TOrder();
        order.setOrderStatus(1);//待处理
        order.setReceiverName("张三");
        order.setReceiverMobile("18587781068");
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
        baseMapper.insert(order);
        // 7、 创建订单和商品关系数据
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProduceId(product.getId());
        orderItem.setPurchasePrice(product.getPrice());
        orderItem.setPurchaseNum(count);
        orderItemMapper.insert(orderItem);
        return order.getId();
   }
    /**
     * 减库存
     * <p>
     * 由于默认的事务隔离级别是可重复读,produce.findById()
     * 得到的数据始终是相同的,所以需要提取 reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。
     */
    @Transactional(rollbackFor = Exception.class)
    public int reduceStock(int gid,int count) {
        int result = 0;
        //1、查询商品库存
        Product product = productMapper.selectById(gid);
        //2、判断库存是否充足
        if (product.getCount() >= count) {
            //3、减库存
            // 乐观锁更新库存
            result = productMapper.decreaseStockForVersion(gid,count, product.getVersion());
       }
        return result;
   }

Distributed lock solution_Distributed lock principle implemented by Redis

acquire lock

Mutual exclusion: ensures that only one thread acquires the lock

# 添加锁 利用setnx的互斥性
127.0.0.1:6379> setnx lock thread1

release lock

1. Release the lock manually

2. Timeout release: set a timeout period when acquiring the lock

#释放锁 删除即可
127.0.0.1:6379> del lock

timeout release

127.0.0.1:6379> setnx lock tread1
127.0.0.1:6379> expire lock 5
127.0.0.1:6379> ttl lock

Two steps make one step

help set
 SET key value [EX seconds] [PX milliseconds] [NX|XX]
 summary: Set the string value of a key
 since: 1.0.0
 group: string
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> set lock k1 ex 5 nx
OK
127.0.0.1:6379> set lock k1 ex 5 nx
nil

 

 Distributed lock solution_Distributed lock implemented by Redis

 Introduce dependencies

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

Add Redis configuration

spring:
 redis:
   host: localhost
   port: 6379

Write and create an order implementation class

@Override
    public String createOrderRedis(Integer productId, Integer count) throws Exception {
        log.info("*************** 进入方法 **********");
        String key = "lock:";
        String value = UUID.randomUUID().toString();
        // 获取分布式锁
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key+productId,String.valueOf(Thread.currentThread().getId()),30,TimeUnit.SECONDS);
        // 判断是否获取锁成功
        if (!result){
            log.info("我进入了锁");
            return "不允许重复下单";
       }
        try {
            // 1、根据商品id查询商品信息
            Product product = productMapper.selectById(productId);
            // 2、判断商品是否存在 if (product == null) {
                throw new RuntimeException("购买商品不存在:" + productId + "不存在");
           }
            // 3、校验库存
            if (count > product.getCount()) {
                throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
           }
            // 4、计算库存
            Integer leftCount = product.getCount() - count;
            // 5、更新库存
            product.setCount(leftCount);
            productMapper.updateById(product);
            // 6、 创建订单
            TOrder order = new TOrder();
            order.setOrderStatus(1);//待处理
            order.setReceiverName("张三");
            order.setReceiverMobile("18587781068");
            order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
            baseMapper.insert(order);
            // 7、 创建订单和商品关系数据
            OrderItem orderItem = new OrderItem();
            orderItem.setOrderId(order.getId());
            orderItem.setProduceId(product.getId());
            orderItem.setPurchasePrice(product.getPrice());
            orderItem.setPurchaseNum(count);
            orderItemMapper.insert(orderItem);
            return order.getId();
       }catch (Exception e){
            e.printStackTrace();
       }finally {
            // 释放锁
          stringRedisTemplate.delete(key+productId);
       }
        return "创建失败";
   }

Distributed lock solution_Redis distributed lock accidental deletion problem

 

 Configure lock ID

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

acquire lock

//1、获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 2、获得锁 setnx key   value   time   type
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+produceId, threadId, 30,TimeUnit.SECONDS);

release lock

// 获取锁标识
  String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + produceId);
            // 判断标识是否一致
            if (s.equals(threadId)){
                // 释放锁
               stringRedisTemplate.delete(KEY_PREFIX + produceId);
           }

Distributed lock solution_Redis distributed lock non-reentrant problem

 non-reentrancy problem

 How to solve

 

Distributed lock solution_Distributed lock implementation based on Redisson

 Introduction to Redisson

Redisson - is an advanced distributed coordination Redis client, which can help users easily implement some Java objects in a distributed environment. Redisson, Jedis, and Lettuce are three different clients that operate Redis. The APIs of Jedis and Lettuce are more It focuses on CRUD (addition, deletion, modification) of the Reids database, while the Redisson API focuses on distributed development.

Introduce Redisson dependency

<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.17.2</version>
</dependency>

Add Reids configuration

spring:
 redis:
   host: localhost
   port: 6379

Write Redis distributed lock tool class

package com.itbaizhan.lock.utils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class DistributedRedisLock {
    @Autowired
    private RedissonClient redissonClient;
    // 加锁
    public Boolean lock(String lockName) {
        if (redissonClient == null) {
            log.info("DistributedRedisLock redissonClient is null");
            return false;
       }
        try {
            RLock lock = redissonClient.getLock(lockName);
            // 锁15秒后自动释放,防止死锁
            lock.lock(15, TimeUnit.SECONDS);
            log.info("Thread [{}] DistributedRedisLock lock [{}] success",Thread.currentThread().getName(), lockName);
            // 加锁成功
            return true;
       } catch (Exception e) {
            log.error("DistributedRedisLocklock [{}] Exception:", lockName, e);
            return false;
       }
   }
    // 释放锁
    public Boolean unlock(String lockName) {
        if (redissonClient == null) {
            log.info("DistributedRedisLock redissonClient is null");
            return false;
       }
        try {
            RLock lock = redissonClient.getLock(lockName);
            lock.unlock();
            log.info("Thread [{}] DistributedRedisLock unlock [{}] success",Thread.currentThread().getName(), lockName);
            // 释放锁成功
            return true;
} catch (Exception e) {
            log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e);
            return false;
       }
   }
}

Write and create an order interface implementation

/**
     * Redis锁实现
     *
     * @param productId
     * @param count
     * @return
     * @throws Exception
     */
    @Override
    public String createOrderRedis(Integer productId, Integer count) throws Exception {
        //获取锁对象
        if (distributedRedisLock.lock(String.valueOf(productId))) {
            try {
                // 1、根据商品id查询商品信息
                Product product = productMapper.selectById(productId);
                // 2、判断商品是否存在
                if (product == null) {
                throw new RuntimeException("购买商品不存在:" + productId + "不存在");
               }
                // 3、校验库存
                if (count > product.getCount())
               {
                    throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
               }
                // 4、计算库存
                Integer leftCount = product.getCount() - count;
                // 5、更新库存
                product.setCount(leftCount);
                productMapper.updateById(product);
                // 6、 创建订单
                TOrder order = new TOrder();
                order.setOrderStatus(1);//待处理
                order.setReceiverName("张三");
                order.setReceiverMobile("18587781068");
                order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
                baseMapper.insert(order);
                // 7、 创建订单和商品关系数据
                OrderItem orderItem = new OrderItem();
                orderItem.setOrderId(order.getId());
                orderItem.setProduceId(product.getId());
                orderItem.setPurchasePrice(product.getPrice());
                orderItem.setPurchaseNum(count);
                orderItemMapper.insert(orderItem);
                return order.getId();
           } catch (Exception e) {
                e.printStackTrace();
           } finally {
               distributedRedisLock.unlock(String.valueOf(productId));
           }
       }
        return "创建失败";
   }

Distributed lock solution_Zookeeper distributed lock principle

 The principle of fair lock and reentrant lock

 

 This queuing water fetching model is a lock model.

What is a reentrant lock?

 

 Create a temporary sequence node

[zk: localhost:2181(CONNECTED) 1] create -e  -s
/test 123

 

 The realization principle of ZK distributed lock

When the first client request comes in, the Zookeeper client will create a persistent node locks. If it (Client1) wants to acquire a lock, it needs to create a sequence node lock1 under the locks node.

 Then, the client Client1 will search all the temporary order sub-nodes under locks to judge whether its own node lock1 is the one with the smallest order, and if it is, it will successfully obtain the lock.

 

 At this time, if another client client2 comes to try to acquire the lock, it will create a temporary node lock2 under locks.

 The client client2 will also search for all the temporary sequential child nodes under locks to determine whether its own node lock2 is the smallest. At this time, it finds that lock1 is the smallest, so it fails to acquire the lock. If it fails to acquire a lock, it will not be reconciled. client2 registers a Watcher event with its top-ranked node lock1 to monitor whether lock1 exists. That is to say, client2 enters a waiting state if it fails to grab a lock.

 

 At this time, if another client Client3 tries to acquire the lock, it will create another temporary node lock3 under locks.

 

 Similarly, client3 will also search for all temporary sequential child nodes under locks to determine whether its own node lock3 is the smallest, and if it finds that it is not the smallest, it fails to acquire the lock. It will not be reconciled either, it will register the Watcher event with the node lock2 in front of it to monitor whether the lock2 node exists.

 

 release lock

If the task is completed, Client1 will explicitly call the command to delete lock1.


 If the client fails, according to the characteristics of the temporary node, lock1 will be automatically deleted.

 

 After the lock1 node is deleted, Client2 is happy because it has been listening to lock1. When the lock1 node is deleted, Client2 will receive the notification immediately, and will also search for all the temporary sequential sub-nodes under locks, and obtain the lock if the lock2 is the smallest.

 

 Similarly, after Client2 obtains the lock, Client3 also looks at it:

 Distributed lock solution_Distributed lock based on Zookeeper

 Introduction

Apache Curator is a relatively complete ZooKeeper client framework, which simplifies the operation of ZooKeeper through a set of high-level APIs encapsulated.

 

 Introduce Curator dependency

       <dependency>
         <groupId>org.apache.curator</groupId>
          <artifactId>curator-framework</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>5.2.0</version>
        </dependency>

Write Zookeeper configuration

@Configuration
public class ZookeeperConfig {
    @Bean
    public CuratorFramework zookeeperClient() {
        CuratorFramework client = CuratorFrameworkFactory.builder()
               .connectString("127.0.0.1:2181")
               .sessionTimeoutMs(5000)
               .connectionTimeoutMs(5000)
               .retryPolicy(new ExponentialBackoffRetry(1000, 3))
               //.namespace("test")
               .build();
        client.start();
        return client;
   }
}

Write and create an order interface implementation

Use the acquire and release methods of InterProcessMutex to acquire and release locks.

@Autowired
    CuratorFramework client;
    @Override
    public String createOrderZookeeper(Integer productId, Integer count) throws Exception 
     {
        // client cruator中zk客户端对象   path 抢锁路径同一个锁path需要一致
        InterProcessMutex lock = new InterProcessMutex(client, "/lockPath");
        //第一个属性:定时的时间数字
        //第二个属性:定义时间的单位
        if (lock.acquire(3, TimeUnit.SECONDS))
          {
            try {
                // 1、根据商品id查询商品信息
                Product product = productMapper.selectById(productId);
                // 2、判断商品是否存在
                if (product == null) {
            throw new RuntimeException("购买商品不存在:" + productId + "不存在");
               }
                // 3、校验库存
                if (count > product.getCount())
                 {
                    throw new RuntimeException("商品" + productId + "仅剩" +
product.getCount() + "件,无法购买");
               }
                // 4、计算库存
                Integer leftCount = product.getCount() - count;
                // 5、更新库存
                product.setCount(leftCount);
                productMapper.updateById(product);
                // 6、 创建订单
                TOrder order = new TOrder();
                order.setOrderStatus(1);//待处理
                order.setReceiverName("张三");
                order.setReceiverMobile("18587781068");
                order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
                baseMapper.insert(order);
                // 7、 创建订单和商品关系数据
                OrderItem orderItem = new OrderItem();
                orderItem.setOrderId(order.getId());
                orderItem.setProduceId(product.getId());
                orderItem.setPurchasePrice(product.getPrice());
                orderItem.setPurchaseNum(count);
                orderItemMapper.insert(orderItem);
                return order.getId();
           } finally {
                lock.release();
           }
       }
        return "创建失败";
   }

Comparison of three distributed locks

 Database distributed lock implementation

Advantages: Simple, easy to use, no need to introduce middleware such as Redis and Zookeeper.

Disadvantages: 1. Not suitable for high concurrency scenarios 2. Poor db operation performance

Redis distributed lock implementation

Advantages: 1. Good performance, suitable for high concurrency scenarios 2. Lightweight 3. Better framework support, such as Redisson

 Disadvantages: 1. The expiration time is not easy to control. 2. It is necessary to consider the scenario where the lock is accidentally deleted by other threads.

Zookeeper distributed lock implementation

Advantages: 1. It has better performance and reliability 2. It has a better package framework, such as Curator

Disadvantages: 1. The performance is not as good as the distributed lock implemented by Redis. 2. The relatively heavy distributed lock.

Summary and comparison

1. From a performance point of view: Redis > Zookeeper >= database

2. From the perspective of implementation complexity: Zookeeper > Redis > database

3. From the perspective of reliability: Zookeeper > Redis > database

Guess you like

Origin blog.csdn.net/m0_58719994/article/details/131711292