redis分布式锁的原理及代码实现

分布式锁

redis分布式锁

获取锁:

基于setnx命令,此命令是一个原子性操作,并发请求锁时,因为redis是单线程的,并发的请求会串行执行,只有第一个set值成功的线程才能获取到锁,其他线程获取锁失败

SET resourde_name random_value NX PX 30000

resource_name(key):资源名称,可根据不同的业务区分不同的锁

random value(value):随机值,每个线程的随机值都不同,用于释放锁时的校验(防止并发时顺序混乱,防止线程释放了不属于自己的锁)

NX:key不存在时设置成功,key存在则设置不成功

PX:过期时间,防止出现异常情况锁无法释放,导致死锁

总结:

  • 利用NX的原子性,多个线程并发时,只有一个线程可以设置成功
  • 设置成功即获得锁,可以执行后续的业务处理
  • 如果出现异常,过了锁的有效期,锁自动释放

释放锁:

采用Redis的delete命令,在释放锁时要校验value的随机数是否是获取锁时set进去的随机数,两者相同才能释放锁,释放锁时可以采用LUA脚本

if redis.call("get",KEYS[1])==ARGV[1] 
    then return redis.call("del",KEYS[1])
else return 0
end

说明:

KEYS[1]:set资源时的key

ARGV[1]:当前传入的value

lua脚本通过设置分布式锁时的key来获取redis中对应的value值,如果这个值和当前传入的值相等,就执行delete操作,删除分布式锁,否则返回0不做任何操作

思考:为什么要校验value是否相同?

如图:若不校验value可能会导致并发情况下锁顺序混乱

redis分布式锁的原理及代码实现_第1张图片

分布式锁实现

基于RedisTemplate的redis分布式锁实现

@Slf4j
@Component
public class RedisLock {
     
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    private final String value = UUID.randomUUID().toString();

    public boolean rLock(String key, Long time) {
     
        boolean locked = false;
        //重试次数
        int tryCount = 3;
        //分布式锁,失败重试
        while (!locked && tryCount > 0) {
     
            locked = redisTemplate
                .opsForValue()
                .setIfAbsent(key, this.value, time, TimeUnit.MINUTES);
            tryCount--;
            if (!locked) {
     
                log.info("抢夺锁失败,等待重试!");
            }
            try {
     
                Thread.sleep(300);
            } catch (InterruptedException e) {
     
                log.error("线程被中断" + Thread.currentThread().getId(), e);
            }
        }
        return locked;
    }


    public Boolean unLock(String key) {
     
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
        List<String> keys = Collections.singletonList(key);

        Boolean result = redisTemplate.execute(redisScript, keys, value);
        log.info("释放锁成功!" + result);
        return result;
    }
}

使用

@GetMapping("/lock")
    public String lock() throws InterruptedException {
     
        boolean lock = redisLock.rLock("order-xxx", 10L);
        if (lock){
     
            log.info("抢夺锁成功!执行任务!");
            Thread.sleep(5000L);
            boolean unLock = redisLock.unLock("order-xxx");
            if (unLock){
     
                log.info("释放锁成功!");
            }
        }else {
     
            log.info("抢夺锁失败!");
        }
        return "分布式锁执行!";
    }

使用redission实现分步式锁

  1. 引入依赖
 		
            org.redisson
            redisson-spring-boot-starter
            3.13.1
        
  1. 配置好redis
   redis:  database: 1  
   host: 123.25.252.220  
   port: 6379
  1. 分布式锁的使用
@Autowired
    private RedissonClient redissonClient;
/**
     * 扣库存
     *
     * @param specId    商品规格id
     * @param buyCounts 购买件数
     * @return
     */
    @Override
    public int decreaseItemSpecStock(String specId, int buyCounts) {
     
        //获取redis分布式锁
        RLock lock = redissonClient.getLock("item_lock" + specId);
        //设置超时时间和单位
        lock.lock(3000, TimeUnit.MILLISECONDS);
        int result = 0;
        try {
     
            //模拟扣库存操作
            result = itemsMapperCustom.decreaseItemSpecStock(specId, buyCounts);
            if (result != 1) {
     
                throw new RuntimeException("订单创建失败,原因:库存不足!");
            }
        } catch (RuntimeException e) {
     
            log.error("【扣减库存失败】",e);
        }finally {
     
            //无论执行是否成功都要释放锁
            lock.unlock();
        }
        return result;
    }

你可能感兴趣的:(Redis,锁机制,redis,分布式,并发编程)