redis学习总结

为啥要加分布式锁?为了多个进程对共享资源并发访问时,能够保证数据的一致性和系统的稳定性。在库存扣减场景中使用分布式锁,主要为了高并发下防止超卖。

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
  • 加锁和解锁必须具有原子性。

redis实现分布式锁:

  • redis是基于单线程模型,这个特性可以让调用方的请求排队,对于并发请求,只能有一个调用方能够获得锁。
  • setNx(),redis的API,向redis保存一个key-value,特性是只有key不存在时才会设置成功,否则返回0。体现分布式锁互斥性。
  • expire(),给分布式锁设置超时时间,避免客户端获得锁后宕机造成死锁。
  • 防止误删:给分布式锁的value设置一个requestId,可以是uuid。A线程超时时间内没有完成业务,锁过期删除了,此时B线程获得锁,A线程这时才完成业务并删除锁,但此时删除的是B线程的锁。造成误删。
  • 锁续期:看门狗,开一个线程不断给锁续期,续期三次没有完成业务,报错且回滚。现在能防止误删了,但是业务执行时间超过了锁超时时间,意味着A线程还没处理完,B线程已经获取锁进行处理了,可能造成并发问题。而且没等B线程也没有处理完,锁又过期了,C线程又获得了锁,连锁的并发问题,需要给锁续期。

我觉得,不用使用锁续期。因为比较复杂,而且谁也不会在分布式锁里面加很多操作,少量的操作,这些操作的接口调用超时时间加在一起设置为锁的超时时间就行了

加锁:并且使用requestId防止误删

 public boolean tryLock(String lockKey, String requestId){ 
  String threadName = Thread.currentThread().getName();
  Jedis jedis = this.jedisPool.getResource();
  Long start = System.currentTimeMillis();
  try{
    for (;;){
      String lockResult = jedis.set(lockKey, requestId, setParams);
      if ("OK".equals(lockResult)){
        System.out.println(threadName+": 获取锁成功");
        return true;
    } 
    //否则循环等待,在timeout时间内仍未获取到锁,则获取失败
    System.out.println(threadName+": 获取锁失败,等待中");
    long l = System.currentTimeMillis() ‐ start;
    if (l>=timeout) {
      return false;
    } 
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
   }
  }finally {
    jedis.close();
} 
}

  

解锁:使用lua脚本,保证误删比较操作和删除lockKey操作的原子性。

public boolean releaseLock(String lockKey,String requestId){
  String threadName = Thread.currentThread().getName();
  System.out.println(threadName+":释放锁");
  Jedis jedis = this.jedisPool.getResource();
  String lua = "if redis.call('get',KEYS[1]) == ARGV[1] then" + " return redis.call('del',KEYS[1]) " + "else" + " return 0 " + "end";
  try {
    Object result = jedis.eval(lua, Collections.singletonList(lockKey),Collections.singletonList(requestId));
    if("1".equals(result.toString())){
      return true;
    } 
    return false;
  }finally {
    jedis.close();
  }
}

  

posted @ 2024-09-22 11:53  gaoxing66  阅读(11)  评论(0)    收藏  举报