Redis典型应用之分布式锁

目录

前言

 分布式锁的基础实现

 引入过期时间:

 引入校验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
    评论
Redis从入门到高可用 分布式实战教程,共140多节课程、 掌握redis主从、哨兵、集群 ,参数调优 目录: 9-9 原生安装-1.准备节点.mp4 9-8 原生安装.mp4 9-7 基本架构.mp4 9-6 虚拟槽哈希分布.mp4 9-5 一致性哈希分区.mp4 9-4 节点取余分区.mp4 9-3 数据分布概论.mp4 9-2 呼唤集群.mp4 9-16 原生命令和redis-trib.rb对比.mp4 9-14 ruby环境准备-操作.mp4 9-13 ruby环境准备-说明.mp4 9-12 原生安装-4.分配主从.mp4 9-10 原生安装-2.节点握手.mp4 9-1 本章目录.mp4 8-9 实现原理-1-故障转移演练.mp4 8-8 python客户端.mp4 8-7 java客户端.mp4 8-6 redis sentinel安装演示-2.mp4 8-5 redis sentinel安装演示-1.mp4 8-4 redis sentinel安装与配置.mp4 8-3 redis sentinel架构.mp4 8-2 主从复制高可用?.mp4 8-19 本章总结.mp4 8-18 高可用读写分离.mp4 8-17 节点运维.mp4 8-16 常见开发运维问题-目录.mp4 8-15 故障转移.mp4 8-14 领导者选举.mp4 8-13 主观下线和客观下线.mp4 8-12 三个定时任务.mp4 8-11 实现原理-3.故障演练(日志分析).mp4 8-10 实现原理-2.故障转移演练(客户端).mp4 8-1 sentinel-目录.mp4 7-9 主从复制常见问题.mp4 7-8 故障处理.mp4 7-7 全量复制开销 + 部分复制.mp4 7-6 全量复制.mp4 7-5 runid和复制偏移量.mp4 7-4 主从复制配置-操作.mp4 7-3 主从复制配置-介绍.mp4 7-2 什么是主从复制.mp4 7-1 目录.mp4 6-4 AOF阻塞.mp4 6-3 子进程开销和优化.mp4 6-2 fork.mp4 6-1 常见问题目录.mp4 5-9 RDB和AOF抉择.mp4 5-8 AOF实验.mp4 5-7 AOF(2).mp4 5-6 AOF(1).mp4 5-5 RDB(3).mp4 5-4 RDB(2).mp4 5-3 RDB(1).mp4 5-2 持久化的作用.mp4 5-1 目录.mp4 4-7 geo.mp4 4-6 hyperloglog.mp4 4-5 bitmap.mp4 4-4 发布订阅.mp4 4-3 pipeline.mp4 4-2 慢查询.mp4 4-1 课程目录.mp4 3-4 Go客户端:redigo简介.mp4 3-3 Python客户端:redis-py.mp4 3-2 Java客户端:Jedis.mp4 3-1 课程目录.mp4 2-9 list(2).mp4 2-8 list(1).mp4 2-7 hash (2).mp4 2-6 hash (1).mp4 2-5 字符串.mp4 2-4 单线程.mp4 2-3 数据结构和内部编码.mp4 2-2通用命令.mp4 2-11 zset.mp4 2-10 set.mp4 13-1 _课程总结.mp4 12-7 运维功能.mp4 12-6 用户功能.mp4 12-5 应用接入.mp4 12-4 机器部署.mp4 12-3 _快速构建.mp4 12-2 _Redis规模化困扰.mp4 12-1 _目录.mp4 11-8 本章总结.mp4 11-7 热点key的重建优化.mp4 11-6 无底洞问题.mp4 11-5 缓存穿透问题.mp4 11-4 缓存粒度问题.mp4 11-3 缓存的更新策略.mp4 11-2 缓存的受益和成本.mp4 11-1 目录.mp4 10-9 集群缩容-操作.mp4 10-8 集群缩容-说明.mp4 10-7 集群扩容演示-2.mp4 10-6 集群扩容演示-1.mp4 10-5 扩展集群-3.迁移槽和数据.mp4 10-4 扩展集群-2.加入集群.mp4 10-35 本章总结.mp4 10-34 集群vs单机.mp4 10-33 数据迁移.mp4 10-32 读写分离.mp4 10-31 请求倾斜.mp4 10-30 数据倾斜.mp4 10-3 扩展集群-1.加入节点.mp4 10-29 集群倾斜-目录.mp4 10-28 PubSub广播.mp4 10-27 带宽消耗.mp4 10-26 集群完整性.mp4 10-25 Redis Cluster常见开发运维问题-目录.mp4 10-24 故障模拟.mp4 10-23 故障恢复.mp4 10-22 故障发现.mp4 10-21 故障转移-目录.mp4 10-20 批量操作优化.mp4 10-2 集群伸缩原理.mp4 10-19 多节点操作命令.mp4 10-18 整合spring-2.mp4 10-17 整合spring-1.mp4 10-16 JedisCluster基本使用.mp4 10-15 smart客户端JedisCluster-目录.mp4 10-14 JedisCluster执行源码分析.mp4 10-13 smart客户端实现原理.mp4 10-12 ask重定向.mp4 10-11 moved异常说明和操作.mp4 10-10 客户端路由-目录.mp4 10-1 集群伸缩目录.mp4 1-9 特性5-功能丰富.mp4 1-8 特性4-多语言客户端.mp4 1-7 特性3-数据结构.mp4 1-6 特性2-持久化.mp4 1-5 特性1-速度快.mp4 1-4 redis特性目录.mp4 1-3 谁在使用Redis.mp4 1-2 Redis初识.mp4 1-15 redis常用配置.mp4 1-14 redis三种启动方式介绍.mp4 1-13 redis典型使用场景.mp4 1-12 特性8-高可用分布式.mp4 1-11 特性7-复制.mp4 1-10 特性6-简单.mp4 1-1 导学.mp4

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Obto-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值