分布式锁

01 CAP理论

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

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

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

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

02 分布式锁

0201 概述

介绍

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

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

分布式锁应该具备的条件

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

高可用的获取锁与释放锁

高性能的获取锁与释放锁

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

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

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

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

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

020201 基于数据库

A. 悲观锁

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

B. 乐观锁

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

版本号不一致,更新失败

020202 zookeeper分布式锁

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

img

实现步骤:

  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.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.13.4</version>
</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算法具有很大争议性,一般不推荐使用

参考原文地址: 【分布式锁】三种分布式锁的实现

posted @ 2022-03-02 16:44  进击的小蔡鸟  阅读(168)  评论(0编辑  收藏  举报