Spring boot distributed lock optimization distributed lock

Spring boot distributed lock optimization distributed lock

One: Preparation

1. Configuration file

server:
  port: 8080
  servlet:
    session:
      timeout: 30m
spring:
  application:
    name: spring-boot-redis
  cache:
    # 使用了Spring Cache后,能指定spring.cache.type就手动指定一下,虽然它会自动去适配已有Cache的依赖,但先后顺序会对Redis使用有影响(JCache -> EhCache -> Redis -> Guava
    type: REDIS
  redis:
    host: 192.168.0.1
    port: 6379
    password: 123
    # 连接超时时间(ms)
    timeout: 10000
    # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
    database: 0
    lettuce:
      pool:
        # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 100
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: -1
        # 连接池中的最大空闲连接 默认 8
        max-idle: 8
        # 连接池中的最小空闲连接 默认 0
        min-idle: 0

2. pox file

	//为了方便测试 使用了web-starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
    //以下三个必须
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

Two: Test

1.contoller layer code

Distributed lock test control layer

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author WJL
 * @version 1.0
 * @Date: 2022/8/20 23:18
 * @功能描述
 **/
@RestController
public class RedisTestController {
    
    

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("/testLock")
    public void testLock(){
    
    
        //1获取锁,setne
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",3, TimeUnit.SECONDS);
        //2获取锁成功、查询num的值
        if(lock){
    
    
            Object value = redisTemplate.opsForValue().get("num");
            //2.1判断num为空return
            if(StringUtils.isEmpty(value)){
    
    
                return;
            }
            //2.2有值就转成成int
            int num = Integer.parseInt(value+"");
            //2.3把redis的num加1
            redisTemplate.opsForValue().set("num", ++num);
            //2.4释放锁,del
            redisTemplate.delete("lock");

        }else{
    
    
            //3获取锁失败、每隔0.1秒再获取
            try {
    
    
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }

}

First set num "0" in the server's redis

2. Service area test results

Use the ab test tool: httpd-tools (yum install -y httpd-tools)

ab -n (the number of requests sent at one time) -c (the number of concurrent requests) access path

The test is as follows: 5000 requests, 100 concurrent
ab -n 5000 -c 100 http://127.0.0.1:8206/testLock

insert image description here

insert image description here

You can see that the num at this time becomes 1000

Three: The summary was deleted by mistake

1. The process of code execution is sorted out. For example, A first operates 1. Locks 2. Specific operations. Assume that the server is stuck when A is operating, and the time has passed the set expiration time 3. Automatically release the key B grabs the lock 1. Lock it 2. Perform specific operations When the A server responds, continue to operate , the lock needs to be released manually, but the lock is in B at this time, and the lock of B is released before the operation of B is completed!

2. The solution uses UUID to prevent accidental deletion, and uses uuid to identify different operations

insert image description here

Four: Optimize uuid to identify different operations

Mainly add uuid when generating lock

Perform a uuid check when releasing the lock

 String uuid= UUID.randomUUID().toString();
        //1获取锁,setne
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid , 3, TimeUnit.SECONDS);
        
//2.4释放锁,del
//判断比较uuid值是否一样
if(uuid.equals(redisTemplate.opsForValue().get("lock"))){
    
    
    redisTemplate.delete("lock");
}

Five: Continue to optimize using lua script

Delete operations are atomic

Problem: A will release B's lock

insert image description here

Solution

It has certain atomicity, will not be queued by other commands, and can complete some redis transactional operations

@GetMapping("testLockLua")
public void testLockLua() {
    
    
    
    //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
    String uuid = UUID.randomUUID().toString();
    //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
    String skuId = "25"; // 访问skuId 为25号的商品 100008348542
    String locKey = "lock:" + skuId; // 锁住的是每个商品的数据

    // 3 获取锁
    Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

    // 第一种: lock 与过期时间中间不写任何的代码。
    // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
    // 如果true
    if (lock) {
    
    
        // 执行的业务逻辑开始
        // 获取缓存中的num 数据
        Object value = redisTemplate.opsForValue().get("num");
        // 如果是空直接返回
        if (StringUtils.isEmpty(value)) {
    
    
            return;
        }
        // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
        int num = Integer.parseInt(value + "");
        // 使num 每次+1 放入缓存
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
        /*使用lua脚本来锁*/
        // 定义lua 脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', 			KEYS[1]) else return 0 end";
        // 使用redis执行lua执行
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        // 设置一下返回值类型 为Long
        // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
        // 那么返回字符串与0 会有发生错误。
        redisScript.setResultType(Long.class);
        // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
        redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
    } else {
    
    
        // 其他线程等待
        try {
    
    
            // 睡眠
            Thread.sleep(1000);
            // 睡醒了之后,调用方法。
            testLockLua();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

Six: Summary

In order to ensure that distributed locks are available, we must at least ensure that the implementation of locks meets the following four conditions :

  • mutual exclusion. At any one time, only one client can hold the lock.

  • No deadlock will occur. Even if a client crashes while holding the lock and does not actively unlock it, it can ensure that other clients can lock it later.

  • The trouble should end it. Locking and unlocking must be the same client, and the client itself cannot unlock the lock added by others.

  • Locking and unlocking must be atomic.

Guess you like

Origin blog.csdn.net/weixin_45572279/article/details/126595703