一、使用StringRedisTemplate实现分布式锁

1. 使用docker安装redis

① 拉取镜像

  1. docker pull redis:latest

②查看本地镜像

docker images

image.png
③运行镜像

docker run -itd --name redis-test -p 6379:6379 redis

参数说明:

  • -p 6379:6379:映射容器服务的 6379 端口到宿主机的 6379 端口。外部可以直接通过宿主机ip:6379 访问到 Redis 的服务。

④安装成功
使用 docker ps 查看正在运行的容器
通过 redis-cli 连接测试使用 redis 服务

docker exec -it redis-test /bin/bash

image.png

2. 新建spring boot项目

①引入依赖

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

②编写配置文件

  spring: 
    redis:
      database: 5
      host: 192.168.0.100
      port: 6379
      password:
      timeout: 5000
      jedis:
        pool:
          max-idle: 500
          min-idle: 50
          max-active: 1000
          max-wait: 2000

③编写逻辑代码

@Service
public class RecordServiceImpl implements RecordService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void consumeAmount(Long userId, BigDecimal amount) {
        String key = "lock_"+userId;
        String value = UUID.randomUUID().toString();
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, value, 10, TimeUnit.SECONDS);
        while (!result) {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            result = stringRedisTemplate.opsForValue().setIfAbsent(key, value, 10, TimeUnit.SECONDS);
        }
        try {
            //...........
            //逻辑代码
        }finally {
            if (value.equals(stringRedisTemplate.opsForValue().get(key))) {
                stringRedisTemplate.delete(key);
            }
        }
    }
}

④代码解析

  1. 每个线程锁唯一

String value = UUID.randomUUID().toString();
保证每个线程只释放自己的锁,避免代码执行时间大于锁有效时间,导致锁永久失效。

//释放对应线程自己的锁
if (value.equals(stringRedisTemplate.opsForValue().get(key))) {
                stringRedisTemplate.delete(key);
   }
  1. 加锁及添加过期时间用一条语句

Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, value, 10, TimeUnit.SECONDS);
保证原子性,如果在key赋值和过期时间为两条语句,在该两条语句之间出现服务器宕机,导致锁永不过期,下面逻辑永远无法执行。

  1. 循环获取锁

         while (!result) {
             try {
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             result = stringRedisTemplate.opsForValue().setIfAbsent(key, value, 10, TimeUnit.SECONDS);
         }
    

    若线程获取锁失败,则循环获取锁,保证每条线程都可以执行逻辑代码,保证一致性。循环执行时间一般为锁存活时间1/3 。

  2. 在try内编写逻辑,finally释放锁

    try {
             //...........
             //逻辑代码
         }finally {
             if (value.equals(stringRedisTemplate.opsForValue().get(key))) {
                 stringRedisTemplate.delete(key);
             }
    

    避免逻辑代码内出现异常而无法释放锁。