分布式锁


01 CAP理论

CAP理论:在一个分布式系统中,做多满足下面三项中的两项

C 一致性: 在分布式系统中所有的数据备份,在同一时刻都有相同的值

A 可用性: 保证每个请求不管成功还是失败都有响应

P 分区容错性:任何信息的丢失和失败都不影响系统的运行

02 分布式锁

0201 概述

介绍

在分布式系统中访问共享资源时需要采取一致互斥机制,来防止相互干扰,以保证一致性

分布式锁:在分布式模型中,保证数据只有一份(限制),锁技术

分布式锁应该具备的条件

互斥性: 在分布式系统中,一个方法在同一时间 只能被一个机器的一个线程执行

高可用的获取锁与释放锁

高性能的获取锁与释放锁

可重入性:具备可重入性,具备锁失效机制,防止死锁,,即就算一个客户端持有锁,因系统失败未主动释放锁,也需要保证后续其他用户能够加锁成功

非阻塞:没有获取到锁直接返回获取锁失败

应用场景:秒杀与抢优惠卷

0202 分布式锁的几种实现方式

基于数据库、基于redis,基于zookeeper

020201 基于数据库

A. 悲观锁

利用select … where xx=yy for update排他锁,不建议使用

B. 乐观锁

在表中添加一个时间戳或者是版本号的字段来实现

版本号不一致,更新失败

020202 zookeeper分布式锁

利用zookeeper的临时有序节点,顺序号最小的获取锁

实现步骤:

  1. 创建一个mylock目录

  2. 线程a在mylock目录下创建临时有序节点

  3. 获取mylock目录下所有的子节点,然后获取比自己小的节点,如果不存在,则说明当前线程的顺序号最小,获取锁

  4. 线程b创建节点向该节点注册监听事件,监听该节点,其他节点依次监听自己的上一个节点

  5. 线程a处理完后,删除自己的节点,其他节点监听变更事件后,重新去获取锁

使用Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁

使用案例

InterProcessMutex interProcessMutex = new InterProcessMutex(client,"/anyLock"); 
interProcessMutex.acquire();
//业务逻辑
interProcessMutex.release();

优缺点:

  1. 可靠性高

  2. 性能较好

  3. CAP模型中属于CP

020203 redis分布式锁

主要基于命令:SETNX key value

redis分布式锁:在分布式系统中采取一致互斥机制,

考虑因素:

原子性:lua脚本

synchronized只能解决单进程的原子性问题

Redis2.6.12之前的版本,只能通过lua脚本来保证原子性

Redis 2.6.12版本及以上推荐使用set原生命令

死锁,删除锁失败

异常导致删除锁失败,解决方案: finally里删除锁

系统奔溃不执行删除锁逻辑,解决方案: 设置锁过期时间,考虑原子性,和时长合理性

错误的释放锁

流程:

A进程加锁并设置超时时间,比如10秒

当10秒后进程A的锁已经被redis中释放了

B进程请求进来,加锁成功

A进程执行完毕,就把B进程的锁释放了,

接着C进程进来,加锁成功,等等......

以此类推

解决方案:

加锁时将值设为唯一值,比如uuid,

Redlock
Redisson

使用如下:

a.引入依赖

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

b.增加配置文件

@Configuration
public class RedissonConfig {

   @Bean
   public Redisson redisson() {
       Config config = new Config();
       //单机版
       //config.useSingleServer().setAddress("redis://192.168.1.1:8001").setDatabase(0);

       //集群版
       config.useClusterServers()
              .addNodeAddress("redis://192.168.1.1:8001")
              .addNodeAddress("redis://192.168.1.1:8002")
              .addNodeAddress("redis://192.168.1.2:8001")
              .addNodeAddress("redis://192.168.1.2:8002")
              .addNodeAddress("redis://192.168.1.3:8001")
              .addNodeAddress("redis://192.168.1.3:8002");
       return (Redisson) Redisson.create(config);
  }
}

c.分布式锁的实现

@Service
public class RedisLockDemo {
   @Autowired
   private StringRedisTemplate redisTemplate;

   @Autowired
   private Redisson redisson;

   public String deduceStock() {
       String lockKey = "lockKey";
       RLock redissonLock = redisson.getLock(lockKey);

       try {
           //加锁(超时默认30s), 实现锁续命的功能(后台启动一个timer, 默认每10s检测一次是否持有锁)
           redissonLock.lock();

           //------ 执行业务逻辑 ----start------
           int stock = Integer.valueOf(redisTemplate.opsForValue().get("stock"));
           if (stock > 0) {
               int newStock = stock - 1;
               //执行业务操作减库存
               redisTemplate.opsForValue().set("stock", newStock + "");
               System.out.println("扣减库存成功, 剩余库存:" + newStock);
          } else {
               System.out.println("库存已经为0,不能继续扣减");
          }
           //------ 执行业务逻辑 ----end------
      } finally {
           //解锁
           redissonLock.unlock();
      }
       return "success";
  }
}

03 总结:

  • 追求数据可靠性/强一致性:使用Zookeeper

  • 追求性能:选择Redis,推荐Redisson

  • Redis分布式锁目前最大问题在于:主从模式下/集群模式下,master节点宕机,异步同步数据导致锁丢失问题

  • Redis的RedLock算法具有很大争议性,一般不推荐使用

 

参考原文地址: 【分布式锁】三种分布式锁的实现https://blog.csdn.net/jiandanokok/article/details/114296755?