Redis典型应用之分布式锁

本文介绍了如何在分布式系统中使用Redis实现基础的分布式锁,包括设置过期时间和校验ID来提高安全性,以及引入lua和watchdog来确保锁定的原子性。最后,讨论了Redlock算法在处理Redis集群中的故障转移和一致性问题。
摘要由CSDN通过智能技术生成

目录

前言

 分布式锁的基础实现

 引入过期时间:

 引入校验ID:

引入lua

lua的简介:

引入看门狗 (watch dog)

 引入Redlock算法


前言

在一个分布式系统中,也会涉及到多个节点同时去访问一个公共资源的时候,此时就需要通过锁来做互斥控制,避免出现类似于“线程安全”的问题~~

本质上来说就是使用一个公共的服务器,用来记录加锁状态

当然这个服务器也可以是Redis,也可以是其他的组件(MySQL、ZooKeeper等),自己手搓也行

 分布式锁的基础实现

其实就是通过一个键值对来标识锁的状态

例如:买票的时候,存在多个能够购票的软件(12306、携程等)但是票的总数是固定的,所以我们都需要先查询该票的余票是否大于0,如果成立那么票数--

但是上述的情况会出现“线程安全”问题(访问临界资源)

在上述的场景的时候就会出现“超卖”的现象~~

可以在购票服务器中加一个Redis来作为分布式锁的管理器

 

此时如果买票服务器尝试买票,就需要先访问Redis,在Redis上设置一个键值对。比如key就是车次,value就随意

如果当前设置成功,就是为当前没有节点对001车次加锁,那么就可以对数据库进行写操作,操作完成之后再把Redis上的键值对给删除掉

如果在买票服务器1买001车次的票的时候, 买票服务器2也要买001车次的票,此时买票服务器2也会想向Redis中写入key 为 001的键值对,但是此时该key已经存在,那么就会设置失败,那么我们就认为此时其他服务器持有锁,那么买票服务器2就应该等待或放弃~~

Redis提供了setnx操作,即:key不存在就设置,存在直接失败

看起来这样就已经实现了分布式锁了吗?? 

 如果此时买票服务器1宕机了怎么办,话句话说,我忘记给del这个key了!!

 引入过期时间:

为了解决上述的问题,我们可以对key引入一个过期时间,即这个锁有一个默认释放形式

注意:此处的过期时间的设置务必在设置锁的同时设置上! 

 使用set ex nx 的方式,在设置锁的时候设置上过期时间,不要分成set 与 expire 两个指令~~

这样已经大致完善了分布式锁的安全使用,但我们仍要考虑一些特殊情况~~

即购票服务器2的失误操作,将redis中已存在的key删除了~~

 引入校验ID:

为了解决上述的问题,可以引入校验ID 

即设置key的时候对其value的值设置成为服务器的编号,比如:key:"001" , value:"服务器1"

这样就可以在删除key的时候检测一下value是否对应当前执行删除操作的服务器 

 那么伪代码应该如下:

String key = [要加锁的资源 id];
String serverId = [服务器的编号];

// 加锁, 设置过期时间为 10s
redis.set(key, serverId, "NX", "EX", "10s");

// 执⾏各种业务逻辑
// ...


// 解锁, 删除 key. 但是删除前要检验下 serverId 是否匹配.
if (redis.get(key) == serverId) {
    redis.del(key);
}

很明显此时get和del这两步操作不是原子的,即使有过期时间也不应该就这样,过期时间只是最后防线 

 

引入lua

 为了使解锁操作原子,可以使用Redis支持的Lua脚本功能

lua的简介:

Lua也是一门编程语言,语法类似JS,是一个动态弱类型语言,Lua的解释器一般用C语言实现,Lua的语法精炼速度快,解释器轻量(200K左右)

因此Lua经常作为其他程序的内嵌脚本语言,Redis本身就支持Lua作为内嵌脚本

使用Lua脚本完成上述的解锁功能

if redis.call('get',KEYS[1]) == ARGV[1] then
    return redis.call('del',KEYS[1])
else
    return 0
end;

 上述代码可以编写成一个.lua后缀的文件,由redis-cli 或者 redis-plus-plus 或者jedis 等客户端加载,并发送给Redis服务器,由Redis服务器来执行这段逻辑

一个Lua脚本会被Redis服务器以原子的方式来执行

 


引入看门狗 (watch dog)

 上述方案仍然存在一个重要问题:在过期时间内我们的服务仍未执行完成,也就是事没办完你把锁给解了

那么是不是直接加长我们的过期时间就可以了么?

如果这样,服务器1真挂了的话这个锁会僵住很长一段时间,不合理的,只能动态调整~~

所谓的watch dog ,本质就是在加锁的服务器上的一个单独的线程,通过对这个线程的加锁过期时间进行“续费”

举个例子:

初始情况下设置过期时间为10s.同时设定看⻔狗线程每隔 3s 检测⼀次.
那么当3s时间到的时候,看⻔狗就会判定当前任务是否完成

  • 如果任务已经完成,则直接通过 lua 脚本的⽅式,释放锁(删除key)
  • 如果任务未完成,则把过期时间重写设置为10s

 

 引入Redlock算法

 实践中的 Redis ⼀般是以集群的⽅式部署的(⾄少是主从的形式,⽽不是单机).

 

例如:

服务器1向master节点进⾏加锁操作.这个写⼊key的过程刚刚完成,master挂了;slave节点升级成了新的master节点.但是由于刚才写⼊的这个key尚未来得及同步给slave呢,此时就相当于 服务器1 的加锁操作形同虚设了,服务器2仍然可以进⾏加锁(即给新的 master 写⼊ key. 因为新的 master 不包含刚才的 key)

 为了解决这个问题,Redis提出了Redlock算法

引入一组Redis节点,其中每一组Redis节点都包含一个主节点和若干个从节点,并且组与组之间存储的数据都是一致的,相互之间是“备份关系”

加锁的时候,按照一定顺序,写多个master节点,在写锁的时候就需要设定“超时时间”。比如30ms。如果超过了30ms没有成功,就视为加锁失败

 

| 当加锁成功的节点数超过总结点数的一半,才视为加锁成功

| 同理,释放锁的时候,也需要把所有节点都进⾏解锁操作.(即使是之前超时的节点,也要尝试解锁,尽量保证逻辑严密).

在分布式系统中,不能让一台机器" 独断专行 ", 不能过度相信这一台机器不会宕机,最终加锁成功的结论就是“少数服从多数”

  • 22
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Obto-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值