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可能会导致并发情况下锁顺序混乱

分布式锁实现

基于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. 引入依赖
 		<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.13.1</version>
        </dependency>
  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中的setnx命令来实现。当一个客户端第一次请求获取锁时,通过setnx设置一个唯一标识符作为锁,如果返回值为1,则表示获取锁成功,否则获取锁失败。Java代码示例如下: ``` public class RedisLock { private final static String LOCK_KEY = "redis_lock";//锁的键值 private final static int LOCK_EXPIRE = 3000;//锁的过期时间,单位毫秒 private JedisPool jedisPool;//Jedis连接池 public RedisLock(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * 加锁 * @return */ public boolean lock() { Jedis jedis = null; try { jedis = jedisPool.getResource(); long expires = System.currentTimeMillis() + LOCK_EXPIRE + 1; String expiresStr = String.valueOf(expires); if (jedis.setnx(LOCK_KEY, expiresStr) == 1) { jedis.expire(LOCK_KEY, LOCK_EXPIRE);//设置过期时间,避免锁忘记释放导致死锁 return true; } String currentValueStr = jedis.get(LOCK_KEY); if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { String oldValueStr = jedis.getSet(LOCK_KEY, expiresStr); if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { jedis.expire(LOCK_KEY, LOCK_EXPIRE);//设置过期时间,避免锁忘记释放导致死锁 return true; } } } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return false; } /** * 释放锁 */ public void unlock() { Jedis jedis = null; try { jedis = jedisPool.getResource(); jedis.del(LOCK_KEY); } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } } } ``` 以上就是关于redis分布式锁的简单原理及Java代码实现示例,希望能对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值