一、引入依赖

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

二、锁的相关概念

一把合格的锁应该具备以下几个方面:

1.互斥:锁具有独占性,一把锁在同一时刻只能有一个持有者。

2.安全:安全指的是解锁时的安全性,只能解锁自己持有的锁。

3.不死锁:不能因为意外的发生,导致锁不能被正常的释放。

三、实现分布式锁

工具类:

package com.example.springbootdemo.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author qx
 * @date 2023/07/16
 * @desc Redis分布式锁工具
 */
@Component
public class RedisLock {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 加锁
     *
     * @param key     键
     * @param value   值
     * @param timeOut 过期时间
     * @return 设置成功返回1,设置失败返回0
     */
    public boolean lock(String key, String value, Long timeOut) {
        return stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeOut, TimeUnit.SECONDS);
    }

    /**
     * 解锁
     *
     * @param key 键
     */
    public void unLock(String key) {
        stringRedisTemplate.delete(key);
    }

}

测试类:

package com.example.springbootdemo.controller;

import com.example.springbootdemo.util.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qx
 * @date 2023/07/16
 * @desc
 */
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisController {

    private static final Long TIME_OUT = 60L;
    private static final String REDIS_KEY = "redis_lock";

    @Autowired
    private RedisLock redisLock;

    @GetMapping("/lock")
    public String lock(String value) {
        //加锁
        boolean isLock = redisLock.lock(REDIS_KEY, value, TIME_OUT);
        if (!isLock) {
            throw new RuntimeException("当前请求的资源已被占用,请稍后再试");
        }
        //处理其他逻辑
        log.info("处理其他逻辑");
        //解锁
        redisLock.unLock(REDIS_KEY);
        return "success";
    }
}

我们启动程序,访问这个请求地址,如果多个线程访问我们的地址的话,可以实现同一时刻只有一个持有者进行操作。

SpringBoot集成Redis实现分布式锁_redis

2023-07-16 09:17:33.500  INFO 6256 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-07-16 09:17:33.502  INFO 6256 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms
2023-07-16 09:17:34.726  INFO 6256 --- [nio-8080-exec-1] c.e.s.controller.RedisController         : 处理其他逻辑

四、改进版本

假如有两个线程A和B,在A执行完某些操作后,恰好key到了过期时间,而这时B获取到了锁,那么接下来会发生一个情况就是A会执行unLock方法将B获得的锁删除掉,我们该如何解决这个问题?

我们需要在删除这个锁之前判断一下是不是自己持有的锁,如果是就删除操作,如果不是自己的锁或者锁过期那么就不在去删除。

package com.example.springbootdemo.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author qx
 * @date 2023/07/16
 * @desc Redis分布式锁工具
 */
@Component
public class RedisLock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 加锁
     *
     * @param key     键
     * @param value   值
     * @param timeOut 过期时间
     * @return 设置成功返回1,设置失败返回0
     */
    public boolean lock(String key, String value, Long timeOut) {
        return stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeOut, TimeUnit.SECONDS);
    }

    /**
     * 解锁
     *
     * @param key 键
     */
    public void unLock(String key, String value) {
        String oldValue = stringRedisTemplate.opsForValue().get(key);
        if (Objects.nonNull(oldValue) && oldValue.equals(value)) {
            stringRedisTemplate.delete(key);
        }
    }

}
package com.example.springbootdemo.controller;

import com.example.springbootdemo.util.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qx
 * @date 2023/07/16
 * @desc
 */
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisController {

    private static final Long TIME_OUT = 60L;
    private static final String REDIS_KEY = "redis_lock";

    @Autowired
    private RedisLock redisLock;

    @GetMapping("/lock")
    public String lock(String value) {
        //加锁
        boolean isLock = redisLock.lock(REDIS_KEY, value, TIME_OUT);
        if (!isLock) {
            throw new RuntimeException("当前请求的资源已被占用,请稍后再试");
        }
        //处理其他逻辑
        log.info("处理其他逻辑");
        //解锁 加上自己的value
        redisLock.unLock(REDIS_KEY, value);
        return "success";
    }
}

五、其他实现方案

其他实现方案我们后续再学习。

1.Lua脚本

Redis通过调用Lua脚本,可以实现更加强大和复杂的功能,在执行Lua脚本时该操作具有原子性。这恰好可以用来实现分布式锁,将多个操作封装到一个脚本中,可以让多个操作具备原子性。

2.Redission

Redission是一个基于Redis的第三方组件,是官方推荐的分布式锁解决方案。所以在生产环境中我们更加推荐使用Redission来作为我们分布式锁的方案。