RedLock算法分析

Redis分布式锁-RedLock算法

手写分布式锁的缺点

image.png

Redlock算法设计理念

Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。
锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。
Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用。
image.png

设计理念

该方案也是基于(set 加锁、Lua 脚本解锁)进行改良的,所以redis之父antirez 只描述了差异的地方,大致方案如下。
假设我们有N个Redis主节点,例如 N = 5这些节点是完全独立的,我们不使用复制或任何其他隐式协调系统,
为了获取锁,客户端会执行以下操作:

1获取当前时间,以毫秒为单位;
2依次尝试从5个实例,使用相同的 key 和随机值(例如 UUID)获取锁。当向Redis 请求获取锁时,客户端应该设置一个超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为 10 秒,则超时时间应该在 5-50 毫秒之间。这样可以防止客户端在试图与一个宕机的 Redis 节点对话时长时间处于阻塞状态。如果一个实例不可用,客户端应该尽快尝试去另外一个 Redis 实例请求获取锁;
3客户端通过当前时间减去步骤 1 记录的时间来计算获取锁使用的时间。当且仅当从大多数(N/2+1,这里是 3 个节点)的 Redis 节点都取到锁,并且获取锁使用的时间小于锁失效时间时,锁才算获取成功;
4如果取到了锁,其真正有效时间等于初始有效时间减去获取锁所使用的时间(步骤 3 计算的结果)。
5If the client failed to acquire the lock for some reason (either it was not able to lock N/2+1 instances or the validity time is negative), it will try to unlock all the instances (even the instances it believed it was not able to lock).

容错公式

N=2X+1
N表示总机器数量,X表示宕机了几台。
部署N台可以保证宕机X台的情况客户端还能够正常获得锁。

RedLock算法的Java实现:Redisson

POM

<!--redisson-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.4</version>
</dependency>

RedisConfig

@Bean
public Redisson redisson() {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.200.100:6379").setDatabase(0).setPassword("123456");
    return (Redisson) Redisson.create(config);
}

Service

//V9.0
public String saleByRedisson() {
    String retMessage = "";
    RLock redissonLock = redisson.getLock("RedisLock");
    redissonLock.lock();
    try {
        //1 查询库存信息
        String result = stringRedisTemplate.opsForValue().get("inventory001");
        Integer inventorynumber = result == null ? 0 : Integer.parseInt(result);
        if (inventorynumber > 0) {
            //有库存
            stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventorynumber));
            retMessage = "恭喜你,成功卖出一个商品,库存剩余:" + inventorynumber;
            System.out.println(retMessage + "\t" + "服务端口号:" + port);
        } else {
            retMessage = "库存不足";
        }
    } finally {
        redissonLock.unlock();
    }
    return retMessage + "\t" + "服务端口号:" + port;
}

Controller

@ApiOperation("扣减库存,一次卖一个Redisson")
@GetMapping(value = "/inventory/saleByRedisson")
public String saleByRedisson()
{
    return inventoryService.saleByRedisson();
}

Jmeter压测没问题

不能误删别人的锁!

if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){
    redissonLock.unlock();
}

Redisson源码解析

额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。
Redisson 里面就实现了这个方案,使用“看门狗”定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间;
在获取锁成功后,给锁加一个 watchdog,watchdog会起一个定时任务,在锁没有被释放且快要过期的时候会续期
image.png
这个锁的算法实现了多redis实例的情况,相对于单redis节点来说,优点在于 防止了 单节点故障造成整个服务停止运行的情况且在多节点中锁的设计,及多节点同时崩溃等各种意外情况有自己独特的设计方法。
Redisson 分布式锁支持 MultiLock 机制可以将多个锁合并为一个大锁,对一个大锁进行统一的申请加锁以及释放锁。

最低保证分布式锁的有效性及安全性的要求如下:
1.互斥;任何时刻只能有一个client获取锁
2.释放死锁;即使锁定资源的服务崩溃或者分区,仍然能释放锁
3.容错性;只要多数redis节点(一半以上)在使用,client就可以获取和释放锁

网上讲的基于故障转移实现的redis主从无法真正实现Redlock:
因为redis在进行主从复制时是异步完成的,比如在clientA获取锁后,主redis复制数据到从redis过程中崩溃了,导致没有复制到从redis中,然后从redis选举出一个升级为主redis,造成新的主redis没有clientA 设置的锁,这是clientB尝试获取锁,并且能够成功获取锁,导致互斥失效;

多机案例

Docker建3个Redis实例

docker run -p 6381:6379 --name redis-master-1 -d redis
docker run -p 6382:6379 --name redis-master-2 -d redis
docker run -p 6383:6379 --name redis-master-3 -d redis
docker exec -it redis-master-1 /bin/bash
docker exec -it redis-master-2 /bin/bash
docker exec -it redis-master-3 /bin/bash

POM

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.19.1</version>
</dependency>

坑爹的BUG

Docker创建容器默认都只有ipv6连接,所以在使用桥接模式的时候远程主机连接就会出现问题。

  • ipv6可以兼容ipv4,但是ipv4不可以兼容ipv6

这时候我们就要手动设置ipv4的连接。

  • 数据包转发 net.ipv4.ip_forward
    • 当主机拥有多于一块的网卡时,其中一块收到数据包,根据数据包的目的ip地址将数据包发往本机另一块网卡,该网卡根据路由表继续发送数据包。这通常是路由器所要实现的功能;
  • 执行 /sbin/sysctl net.ipv4.ip_forward 查看: 是否打开了数据包转发功能。
  • 如果是0,就需要打开,使用命令sysctl -w net.ipv4.ip_forward=1即可。
  • 重启Docker容器就可以了。

永久修改 net.ipv4.ip_forward

  • vim 修改文件/etc/sysctl.conf
net.ipv4.ip_forward = 1
  • 保存后调用 sysctl -p 生效, ok问题解决;
  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值