【2023】通过redis 实现分布式锁由原生到Redisson代码三种实现和介绍

目录

  • 一、简介
    • 分布式锁的实现应该具备哪些条件
    • 分布式锁的实现方式
  • 二、具体实现
    • 1、RedisTemplate的setnx方式实现
      • 1.1、基本配置
        • 1.1.1、创建spring项目添加依赖
        • 1.1.2、添加RedisTemplate配置bean
      • 1.2、编写DistributedLock类
      • 1.2、编写Controller层使用分布式锁
      • 1.3、可能出现的问题?
    • 2、Lua版本
      • 2.1、创建DistributedLuaLock类
      • 2.2、controller层接口
      • 2.3、存在的不足
    • 3、通过springboot配合Redisson
      • 3.1、依赖
      • 3.2、添加RedissonLock获取和释放锁
      • 3.3、Controller层接口

一、简介

在开发过程中当我们为了提高效率,我们往往会引用多线程并行的方式来提高访问修改并发,我们在面对并发问题时,多个线程一起修改时,如果是单应用部署,直接通过synchronized关键字、 ReetrantLock 类来控制一个JVM进程内多个线程对本地共享资源的访问。
但如果是在分布式、微服务等架构下,不同的服务/客户端通常运行在独立的JVM进程上,使用本地锁的方式就不能有效的解决这个问题啦,因此就引出了一个分布式锁的概念。

下面是我画的一张示意图:
多个服务的线程同时访问同一个共享资源时,在同一时间只会有一个线程获取到,其他线程会进行阻塞(也可以直接返回获取不到锁的结果)。
【2023】通过redis 实现分布式锁由原生到Redisson代码三种实现和介绍_第1张图片

分布式锁的实现应该具备哪些条件

基本条件:

  • 互斥:分布式锁应该确保在任意时刻只有一个客户端能够获得锁,其他客户端需要等待被释放。
  • 高可用:锁服务是高可用的,当一个锁服务出现问题时,需要能够自动释放锁,并且当释放锁的代码逻辑出现问题时,锁最终一定还是会被释放,不会影响其他线程去获取锁对共享资源进行访问。
  • 可重入:同一个客户端在持有锁的情况下,可以多次获取该锁而不会导致死锁
  • 一致性:分布式锁应该在分布式环境下保持一致性,即不同节点之间的锁状态应该一致。

额外条件:

  • 高性能:分布式锁的实现应该尽可能地减少网络通信和资源消耗,以提高性能
  • 安全性:分布式锁应该具备一定的安全性,防止恶意客户端的攻击或误操作。

分布式锁的实现方式

在使用分布式锁实现上:目前主要可以通过三个方式实现:

  • 使用关系型数据库的行锁特性来实现分布式锁
  • 使用分布式协调服务 ZooKeeper 实现分布式锁。
  • 使用分布式键值存储系统如Redis实现分布式锁。

在这里具体介绍通过redis的方式如何实现分布式锁

二、具体实现

1、RedisTemplate的setnx方式实现

1.1、基本配置

1.1.1、创建spring项目添加依赖
  <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

1.1.2、添加RedisTemplate配置bean

继续使用上一篇使用redis实现stream配置即可

   @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        // 这个地方不可使用 json 序列化,如果使用的是ObjectRecord传输对象时,可能会有问题,会出现一个 java.lang.IllegalArgumentException: Value must not be null! 错误
        redisTemplate.setHashValueSerializer(RedisSerializer.string());
        return redisTemplate;
    }

1.2、编写DistributedLock类

用于获取到分布式锁和释放分布式锁

  • 获取锁:使用redisTemplate.opsForValue().setIfAbsent(), 方法实现,该方法的底层其实就是调用了execute()方法实现了setnx命令(先进行判定键是否存在,不存在则设置key,成功后返回true;存在则直接返回false)。
  • 释放锁:直接使用redisTemplate.delete()方法删除掉该key即可释放成功。但在删除之前需要通过验证值的方式,验证是否是该线程自己的锁。
    • 因为如果不做验证,就可能会出现误删的情况(即客户端a在进行操作,服务器发生卡顿,达到key设置的过期时间,解开了锁,客户端b开始进行操作;然后在b进行操作期间,a卡顿结束,继续删锁操作,会导致误删了b的锁)所以需要设置唯一值(uuid等)用于验证;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
 * 使用redis data的RedisTemplate的原生方式实现加锁和释放锁
 */
@Component
public class DistributedLock {

    private static final String LOCK_PREFIX = "lock:";
    private static final long DEFAULT_EXPIRE_TIME = 10; // 默认锁过期时间为60秒

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public boolean tryLock(String lockKey,String value) {
        return tryLock(lockKey,value, DEFAULT_EXPIRE_TIME);
    }

    /**
     * 获取锁
     * @param lockKey
     * @param value
     * @param expireTime 超时时间
     * @return
     */
    public boolean tryLock(String lockKey,String value, long expireTime) {
        String key = LOCK_PREFIX + lockKey;

        Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        return success != null && success;
    }

    /**
     * 释放锁
     * @param lockKey
     * @param value
     */
    public void unlock(String lockKey,String value) {

        String key = LOCK_PREFIX + lockKey;
        String v = redisTemplate.opsForValue().get(key);
        if (!StringUtils.isEmpty(v) && value.equals(v)){
            redisTemplate.delete(key);
        }
    }
}

1.2、编写Controller层使用分布式锁

该处代码主要是模拟发生秒杀情况时,多个线程一起去争抢的场景,通过写了两个一样的接口用于模拟争抢,通过设置debug的断点为Thread模拟测试。
【2023】通过redis 实现分布式锁由原生到Redisson代码三种实现和介绍_第2张图片

具体业务流程:

  1. 先设置currentTime和value等值,
  2. 然后进入循环,去获取锁,看是否可以直接获取到,如果获取到了说明当前没有线程争抢;如果没获取到,说明当前有其他线程持有了锁,在访问共享资源。
  3. 获取成功后直接执行访问资源等后续业务操作,执行完之后删除掉锁,便于下一个线程获取。
  4. 如果获取失败,则通过自旋的发生持续请求获取,避免请求过于频繁耗费资源。可以通过休眠的发生暂停一下,如果持续获取不到可以返回失败结果。
import com.zheng.redislock.setnx.service.ProductService;
import com.zheng.redislock.setnx.util.DistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.UUID;


/**
 * 测试使用redis的setnx命令实现分布式锁功能
 *  使用该方式,会出现两个问题:
 *      1、可能会出现误删的情况(即客户端a在进行操作,服务器发生卡顿,达到key设置的过期时间,解开了
 *      锁,客户端b开始进行操作;然后在b进行操作期间,a卡顿结束,继续删锁操作,会导致误删了b的锁)
 *      所以需要设置唯一值(uuid等)用于验证;
 *      2、该种方式实现因为设置锁和删除锁分开的,因此缺乏原子操作,可以采用lua脚步方式实现
 */

@RequestMapping("/index")
@RestController
@Slf4j
public class Index {

    @Resource
    private ProductService productService;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;
    private final Long time = 1000L;
    @Resource
    private DistributedLock distributedLock;


    /**
     *@Description//TODO用户秒杀接收数据端1
     *@param:goodsId商品id
     *@param:userId用户id
     *@return:java.lang.String
     **/
    @GetMapping("doSeckill1")
    public String doSeckill1(String goodsId ,Integer userId ){
        Long currentTime = 0L;
        String value= UUID.randomUUID() + "-" + Thread.currentThread().getId();
//        通过key到redis中去获取锁,如果成功则返回true并且设置过期时间,如果失败则返回false;
        while (currentTime <= time){
            if (distributedLock.tryLock(goodsId,value, 60)) {
//                获取到锁,执行业务退出
                try {
//                执行具体业务 ****
                    productService.update(goodsId, userId);
                } finally {
//                删除key
                    distributedLock.unlock(goodsId,value);
                }
                log.info("一号直接获取锁成功");
                return "库存扣减成功";
            }
//            未获取到继续自旋,并且暂停100毫秒
            try {
                Thread.sleep(100);
                log.warn("一号自旋获取结果:失败,继续重试,时间{}",currentTime);

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            currentTime+=100;
        }

        return "库存扣减失败";
    }

    /**
     *@Description//TODO用户秒杀接收数据端2
     *@param:goodsId商品id
     *@param:userId用户id
     *@return:java.lang.String
     **/
    @GetMapping("doSeckill2")
    public String doSeckill2(String goodsId ,Integer userId ){
        Long currentTime = 0L;
        String value= UUID.randomUUID() + "-" + Thread.currentThread().getId();
//        通过key到redis中去获取锁,如果成功则返回true并且设置过期时间,如果失败则返回false;
        while (currentTime <= time){
            if (distributedLock.tryLock(goodsId,value, 60)) {
//                获取到锁,执行业务退出
                try {
//                执行具体业务 ****
                    productService.update(goodsId, userId);
                } finally {
//                删除key
                    distributedLock.unlock(goodsId,value);
                }
                log.info("二号直接获取锁成功");
                return "库存扣减成功";
            }
//            未获取到继续自旋,并且暂停100毫秒
            try {
                Thread.sleep(100);
                log.warn("二号自旋获取结果:失败,继续重试,时间{}",currentTime);

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            currentTime+=100;
        }

        return "库存扣减失败";
    }
}

1.3、可能出现的问题?

当多个线程同时争抢同一个redis锁时,如果这些线程都是通过使用 RedisTemplate 的 delete() 方法来释放锁的话,可能会出现下面情况:

  1. 线程a成功获取到锁,并且设置了一个过期时间;

  2. 过了一段时间后,刚刚验证通过value是否一致时,锁的过期时间到了,自动过期了
    【2023】通过redis 实现分布式锁由原生到Redisson代码三种实现和介绍_第3张图片

  3. 此时刚好线程b过来尝试获取锁,因为线程a的锁已经到期过期了,然后线程b获取锁成功。
    【2023】通过redis 实现分布式锁由原生到Redisson代码三种实现和介绍_第4张图片

  4. 这时线程a去调用了 RedisTemplate 的 delete() 方法来释放锁,这时因为持有锁的其实已经是线程b的锁了,所以就会把线程b的锁给释放掉,从而到导致线程b到锁失效。

因此,使用 RedisTemplate 的 delete() 方法来释放锁的方式可能存在删除其他线程获取的锁的风险。如果要避免这种情况发生,可以使用Redis到lua脚步,在执行释放锁时,把进行验证锁的value值和删除操作去保证一个原子操作。从而避免误删其他线程的锁

2、Lua版本

基本配置同上

2.1、创建DistributedLuaLock类

在上面的demo中虽然获取锁使用的setnx保证了原则性,但在删除释放锁时,因为需要先验证value值,是分开两步操作的,就没法保证原子性了,所以需要引用lua脚步去保证。

在使用lua脚本的使用方式可以使用redisTemplate.execute()方法,这个方法总共三个参数。
script:用于封装脚本和执行完返回的参数;
keys:对应着redis的键名的集合;
args:数组,按照顺序对应着lua脚本中的每一个argv[]值,可以多个
【2023】通过redis 实现分布式锁由原生到Redisson代码三种实现和介绍_第5张图片

  • 获取锁:

    • 解析步骤:先执行redis的setnx命令,如果执行后结果返回1(代表获取到锁),则继续 添加超时时间然后返回true;如果执行setnx命令返回的不是1(代表锁被别到线程获取)则直接返回false。同上面的demo的获取锁其实内部调用的命令是一样的。
    private static final String LOCK_SCRIPT =
            "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                    "return true; " +
                    "else return false; " +
                    "end";
    
  • 释放锁

    • 步骤解析: 先执行get()命令取出值,看结果是否和ARGV[1]的值相等(代表锁自己的锁),如果相等则del命令删除该key,然后返回true;如果不相等(则代表不是自己的锁)则直接返回false,不执行del删除命令。
    private static final String UNLOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "redis.call('del', KEYS[1]); " +
                    "return true; " +
                    "else return false; " +
                    "end";
    

具体代码:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
 * 使用redis加lua脚步方式实现加锁和释放锁
 */
@Component
public class DistributedLuaLock {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String LOCK_PREFIX = "luaLock:";
    // 锁的过期时间,单位毫秒
    private static final long LOCK_EXPIRE_TIME = 60000;


    // 获取锁的 Lua 脚本:
    private static final String LOCK_SCRIPT =
            "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                    "return true; " +
                    "else return false; " +
                    "end";

    // 释放锁的 Lua 脚本:
    private static final String UNLOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "redis.call('del', KEYS[1]); " +
                    "return true; " +
                    "else return false; " +
                    "end";
    
    public boolean luaLock(String key,String value){
        return luaLock(key,value, LOCK_EXPIRE_TIME);
    }


    /**
     * 获取分布式锁
     * @param key
     * @param value
     * @param lockTime 超时时间(毫秒)
     * @return
     */
    public boolean luaLock(String key,String value,Long lockTime){
        String[] args = {value,String.valueOf(lockTime)};
        RedisScript<Boolean> script = new DefaultRedisScript<>(LOCK_SCRIPT, Boolean.class);
        Boolean result = redisTemplate.execute(script, Arrays.asList(LOCK_PREFIX+key), args);
        return result != null && result;
    }
    
    /**
     * 释放锁
     * @param key
     * @param value
     * @return
     */
    public boolean unlock(String key,String value){
        String[] args = {value};
        DefaultRedisScript<Boolean> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Boolean.class);
        Boolean result = redisTemplate.execute(script, Arrays.asList(LOCK_PREFIX+key), args);
        return result != null && result;
    }
}

2.2、controller层接口

具体执行业务逻辑上和上面的demo逻辑都是一样的,只不过这里在释放锁的时候,使用lua脚本的方式把验证value值和删除键绑定为一个原子操作了

/**
 *lua脚本方式实现接口
 */
@RequestMapping("/indexLua")
@RestController
@Slf4j
public class IndexLua {

    @Resource
    private ProductService productService;

    private final Long time = 1000L;


    @Resource
    private DistributedLuaLock distributedLuaLock;


    /**
     * 1
     **/
    @GetMapping("doSeckill1")
    public String doSeckill1(String goodsId ,Integer userId ){
        Long currentTime = 0L;
        String value= UUID.randomUUID() + "-" + Thread.currentThread().getId();
//        通过key到redis中去获取锁,如果成功则返回true并且设置过期时间,如果失败则返回false;
        while (currentTime <= time){
            if (distributedLuaLock.luaLock(goodsId,value, 60000l)) {
//                获取到锁,执行业务退出
                try {
//                执行具体业务 ****
                    productService.update(goodsId, userId);
                } finally {
//                删除key
                    distributedLuaLock.unlock(goodsId,value);
                }
                log.info("一号直接获取锁成功");
                return "库存扣减成功";
            }
//            未获取到继续自旋,并且暂停100毫秒
            try {
                Thread.sleep(100);
                log.warn("一号自旋获取结果:失败,继续重试,时间{}",currentTime);

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            currentTime+=100;
        }

        return "库存扣减失败";
    }

    /**
     * 2
     **/
    @GetMapping("doSeckill2")
    public String doSeckill2(String goodsId ,Integer userId ){
        Long currentTime = 0L;
        String value= UUID.randomUUID() + "-" + Thread.currentThread().getId();
//        通过key到redis中去获取锁,如果成功则返回true并且设置过期时间,如果失败则返回false;
        while (currentTime <= time){
            if (distributedLuaLock.luaLock(goodsId,value, 60000l)) {
//                获取到锁,执行业务退出
                try {
//                执行具体业务 ****
                    productService.update(goodsId, userId);
                } finally {
//                删除key
                    distributedLuaLock.unlock(goodsId,value);
                }
                log.info("二号直接获取锁成功");
                return "库存扣减成功";
            }
//            未获取到继续自旋,并且暂停100毫秒
            try {
                Thread.sleep(100);
                log.warn("二号自旋获取结果:失败,继续重试,时间{}",currentTime);

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            currentTime+=100;
        }

        return "库存扣减失败";
    }
}

2.3、存在的不足

虽然使用lua脚本的方式实现可以有效的解决非原子性造成的删除错锁的问题,看似是已经很完美了;但该种方式在设置锁时,设计超时时间是固定的,如上我们设置的是60秒,但实际开发过程中,我们可能因为某些特定的业务场景以及系统问题可能会有不同,所以在设置锁的过期时间上需要根据实际业务需求来进行设置,如果过短可能会导致频繁获取地去获取锁,如果过长则可能会造成失效不及时的问题。

在解决这个问题上可以采用下面的Redisson来实现,Redisson使用来一种叫看门狗的机制实现对锁的自动续期,可以有效的帮我们解决锁的超时时间设置不当的问题。

3、通过springboot配合Redisson

Redisson是一个基于Redis的分布式Java对象和服务框架,它提供了一系列的分布式对象和服务,使得在Java应用程序中使用Redis变得更加方便和高效。

3.1、依赖


        <dependency>
            <groupId>org.redissongroupId>
            <artifactId>redisson-spring-boot-starterartifactId>
            <version>3.15.6version>
        dependency>

3.2、添加RedissonLock获取和释放锁

Redisson在内部给我们提供了很多锁供我们选择。主要通过redissonClient接口去获取到不同的锁对象。

Redisson常用分布式锁对象

  • RLock lock = redissonClient.getLock(key) // 可重入锁(最常用的)
  • RLock fairLock = redissonClient.getFairLock(key);//公平锁
  • RLock spinLock = redissonClient.getSpinLock(key);//自旋锁
  • RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);//读写锁
  • RLock multiLock = redissonClient.getMultiLock(lock);//多重锁

获取锁方式:
获取锁的方式上主要有两种方式获取,一种上阻塞的方式去获取;一种上通过非阻塞的方式获取。

  • lock.lock():阻塞的方式获取,使用该方式去获取,如果没有获取到锁时,会一直阻塞请求获取锁,直到获取到锁为主;如果不设置超时时间则默认使用看门狗功能自动续期(一般不介意设置超时时间)。默认30秒为最大时间10秒续期一次,续期锁调用RedissonBaseLock类的renewExpirationAsync() 方法实现锁的异步续期
  • lock.tryLock(expireTime,TimeUnit.SECONDS):非阻塞的方式获取锁,最多可以设置三个参数,分别指定重试时间,锁过期时间,和时间单位;一般建议设置一个重试时间,如果指定时间没获取则返回false,因为毕竟是非阻塞执行的嘛,如果不设置则会一直去请求获取锁。

释放锁:则直接通过lock.unlock()进行释放即可

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
 * 使用redisson方式实现加锁和释放锁
 */
@Component
public class RedissonLock {
    @Resource
    private RedissonClient redissonClient;
    private static final String LOCK_PREFIX = "sonLock:";


    /**
     * 阻塞方式获取锁
     * @param key
     * @param expireTime
     * @return
     */
    public RLock lock(String key,Integer expireTime){
//        可重入锁
        RLock lock = redissonClient.getLock(LOCK_PREFIX + key);
//        redissonClient.getFairLock(); //公平锁
//        redissonClient.getSpinLock(); //自旋锁
//        redissonClient.getReadWriteLock(); //读写锁
//        redissonClient.getMultiLock();  //多重锁

//        waitTime:设置超时时间(),unit:时间单位
//          超时过期时间我们一般不需要设置redisson内部实现了看门狗自动续时功能。
//              看门狗续期前也会先判断是否需要执行续期操作,需要才会执行续期,否则取消续期操作。
//        lock.lock(expireTime, TimeUnit.SECONDS); //阻塞方式获取锁,设置过期时间
        lock.lock();
        return lock;
    }


    /**
     * 非阻塞方式获取锁
     * @param key
     */
    public Boolean tryLock(String key,Integer expireTime){
        try {
            RLock lock = redissonClient.getLock(LOCK_PREFIX + key);

//            waitTime:获取锁重试时间,leaseTime:设置超时时间,unit:时间单位
//            lock.tryLock(10,expireTime,TimeUnit.SECONDS);
            return lock.tryLock(10,TimeUnit.SECONDS); //非阻塞方式获取锁,设置在指定时间内失败重试获取锁
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    /**
     * 释放锁
     * @param key
     */
    public void unlock(String key) {
        RLock lock = redissonClient.getLock(LOCK_PREFIX+key);
        if (lock.isLocked()) {
            lock.unlock();
        }
    }

}

3.3、Controller层接口

通过redsson实现,在业务调用上相对就比较简单了,内部已经做过相应的重试封装了。
内部两个接口分别是调用阻塞和非阻塞方式获取锁去实现分布式锁功能。

import com.zheng.redislock.setnx.service.ProductService;
import com.zheng.redislock.setnx.util.DistributedLuaLock;
import com.zheng.redislock.setnx.util.RedissonLock;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.UUID;

@RequestMapping("/indexson")
@RestController
@Slf4j
public class Indexson {
    @Resource
    private ProductService productService;
    private final Long time = 1000L;
    @Resource
    private RedissonLock redissonLock;

    /**
     *非阻塞方式获取锁
     *@param:goodsId商品id
     *@param:userId用户id
     *@return:java.lang.String
     **/
    @GetMapping("doSeckill1")
    public String doSeckill1(String goodsId ,Integer userId ){
        Boolean b = redissonLock.tryLock(goodsId, 30);
        if (b) {
//                获取到锁,执行业务退出
                try {
//                执行具体业务 ****
                    productService.update(goodsId, userId);
                } finally {
//                删除key
                    redissonLock.unlock(goodsId);
                }
                log.info("一号直接获取锁成功");
                return "库存扣减成功";
            }
        return "库存扣减失败";
    }
    /**
     *阻塞方式获取锁
     *@param:goodsId商品id
     *@param:userId用户id
     *@return:java.lang.String
     **/
    @GetMapping("doSeckill2")
    public String doSeckill2(String goodsId ,Integer userId ){
        RLock lock = redissonLock.lock(goodsId,30);
        try {
            //                执行具体业务 ****
            productService.update(goodsId, userId);
        } finally {
            if (lock.isLocked()){
                lock.unlock();
            }
        }
        return "库存扣减成功";
    }
}

使用redisson实现基本上就没有什么漏洞了,因为 Redisson 本身就是基于 Redis 的分布式锁实现。但在使用的时间需要根据转身业务情况看是使用阻塞方式获取还是非阻塞方式获取,如果是非阻塞的方式将需要去设置尝试获取锁的最大等待时间避免线程一直阻塞的去获取锁。
当然如果锁集群环境下可能会存在由于数据分片和主从复制等机制造成的节点未能及时同步等问题;这个的话可以使用Redis 的 RedLock 算法来实现分布式锁。算法可以在多个 Redis 节点之间进行协作,确保锁的正确性和可靠性。具体这里就不做讲解了

你可能感兴趣的:(Spring,Cloud,alibaba,中间件,redis,数据库,分布式锁,微服务,spring,cloud)