业务背景:
后台定时任务刷新Redis的数据到数据库中,有多台机器开启了此定时同步的任务,但是需要其中一台工作,其他的作为备用,提高可用性。使用Redis分布式锁进行限制,拿到锁的机器去执行具体业务,拿不到锁的继续轮询。

分布式锁原理
分布式锁:当多个进程不在同一个系统中,多个进程共同竞争同一个资源,用分布式锁控制多个进程对资源的互斥访问。采用Redis服务器存储锁信息(即SET一个Key表示已加锁),可以实现多进程的并发读锁的状态,如果没有锁,则只允许一个进程加锁。

Redis分布式锁实现的关键点:

问题 问题描述 解决方案
互斥性 保证只有一个client可以获取资源 加锁
原子性 如果锁不存在则执行加锁操作,必须是原子性操作 原子性命令或者执行Lua脚本
避免死锁 当拿到锁的Client因宕机或网络原因断线后,如果锁不能释放就会产生死锁 为锁加超时时间
锁超时时间设定 锁超时时间到了,业务没执行完问题 心跳线程,不断更新锁超时时间
锁的所属权 解铃还需系铃人,加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了 Client 与锁进行一一对应,使用UUID作为锁的值
自动重连 网络故障导致Client连接Redis 失败的情况,网络恢复后可以自动重连 轮询

实现方案
方案一:采用Redis的原子性命令“SET key value EX expire-time NX”可以实现分布式锁的基本功能,其中的NX(Not Exist)即判断是否已存在锁,如果不存在key则可进行操作,SET key value 等同于加锁,EX expire-time即设置超时时间,可以避免死锁,但是超时时间的设置需要根据具体业务设置一个合理的经验值,避免锁超时时间到了,业务没执行完的问题。

方案二:采用Lua脚本实现,Redis会将整个脚本作为一个整体执行,因此Lua脚本可以实现原子性操作。相较于方案一,此处增加了心跳线程,不断更新锁超时时间,解决锁超时时间设置不合理的问题;生成UUID(或者是随机数字符串)作为锁的值,用于保证锁与Client的一一对应;采用轮询来实现断线自动重连。

实现方案1:SET EX NX
加锁流程图:

定义锁的变量名为lock,那么对应Redis命令:
判断是否加锁的命令:GET lock
加锁的命令:SET lock
设置超时时间的命令:EXPIRE expire-time
三条命令分开执行是不具有原子性的,比如可能会出现一个进程执行GET lock得到的结果为nil即尚未加锁,在其执行SET lock前另一个进程也执行了SET lock,导致两个进程都认为是可以加锁的,失去互斥性。
因此判断尚未加锁、加锁、设置超时时间必须原子操作,使用Redis的命令“SET key value EX expire-time NX”可以实现该原子操作。

package mainimport ("fmt""time""github.com/gomodule/redigo/redis"
)func main() {rds, err := redis.Dial("tcp", "127.0.0.1:6379")if err != nil {fmt.Println("Connect to redis error", err)return}   defer rds.Close()for true {//检查是否有所与加锁必须是原子性操作result, err := rds.Do("SET", "lock", 1, "EX", 5, "NX")   if err != nil {fmt.Println("redis set error.", err)return}result, err = redis.String(result, err)// 加锁失败,继续轮询if result != "OK" {fmt.Println("SET lock failed.")time.Sleep(5 * time.Second)continue}// 加锁成功fmt.Println("work begining...")// 此处处理业务fmt.Println("work end");// 业务处理结束后释放锁result, err := rds.Do("del", "lock")break;}
}

此方法弊端是对超时时间的设置有要求,需要根据具体业务设置一个合理的经验值,避免锁超时时间到了,业务没执行完的问题。

实现方案2:Lua脚本

package mainimport ("fmt""time""github.com/satori/go.uuid""github.com/gomodule/redigo/redis"
)var uuidClient uuid.UUIDconst (SCRIPT_LOCK = ` local res=redis.call('GET', KEYS[1])if res thenreturn 0elseredis.call('SET',KEYS[1],ARGV[1]);redis.call('EXPIRE',KEYS[1],ARGV[2])return 1end `   SCRIPT_EXPIRE = ` local res=redis.call('GET', KEYS[1])if not res thenreturn -1end if res==ARGV[1] thenredis.call('EXPIRE', KEYS[1], ARGV[2])return 1elsereturn 0end `   SCRIPT_DEL = ` local res=redis.call('GET', KEYS[1])if not res then return -1end if res==ARGV[1] thenredis.call('DEL', KEYS[1])elsereturn 0end `
)func ResetExpire() {fmt.Println("Reset expire begin...")rds, err := redis.Dial("tcp", "127.0.0.1:6379")if err != nil {fmt.Println("Connect to redis server error.", err)return}for true {luaExpire := redis.NewScript(1, SCRIPT_EXPIRE)result, err := redis.Int(luaExpire.Do(rds, "lock", uuidClient.String(), 5))if err != nil {fmt.Println("luaExpire exec error", err)break}if result != 1 {fmt.Println("Reset expire failed.")break} else {fmt.Println("Reset expire succeed.")}time.Sleep(3 * time.Second)}fmt.Println("Reset expire end.")
}func main() {for true {rds, err := redis.Dial("tcp", "127.0.0.1:6379")if err != nil {fmt.Println("Connect to redis server error.", err)time.Sleep(5 * time.Second)continue}defer rds.Close()// 生成UUID标识锁与Client的对应关系uuidClient,err = uuid.NewV4() //也可以生成随机数字符串来代替if err != nil {fmt.Println("New uuid error.", err)return}luaLock := redis.NewScript(1, SCRIPT_LOCK)luaDel:= redis.NewScript(1, SCRIPT_DEL)for true {result, err := redis.Int(luaLock.Do(rds, "lock", uuidClient.String(), 5))if err != nil {fmt.Println("luaLock exec error.", err)break}if result == 0 {fmt.Println("Set lock failed.")time.Sleep(5 * time.Second)continue}fmt.Println("Set lock succeed.")go ResetExpire()// 加锁成功fmt.Println("work begining...")// 此处处理业务fmt.Println("work end");// 业务处理结束后释放锁result, err = redis.Int(luaDel.Do(rds, "lock", uuidClient.String()))return}}
}

Redis采用Lua脚本可以执行更多的个性化的原子操作,在我项目中就采用这种容错性更高的方式。

Redis分布式锁原理相关推荐

  1. Redis分布式锁原理(一)——redis分布式锁需要注意的问题

    下一篇:Redis分布式锁原理(二)--Redisson分布式锁源码浅析 虽然目前Redisson框架已经帮我们封装好了分布式锁的实现逻辑,我们可以直接像调用本地锁一样使用即可,但本文并不直接剖析Re ...

  2. redis分布式锁原理与实现

    分布式锁原理 分布式锁,是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候, ...

  3. Redis分布式锁原理解析

    这章节我们来学习一下,Redis分布式锁的一个原理,首先我们看一下目录,最开始我们要讲一下,Redis分布式锁,相关的一些命令,然后在分布式锁演进的时候呢,还会以时间戳进行一个结合,后边还会讲一下,R ...

  4. redis分布式锁原理及实现

    一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubbo)聊起,一路聊到分布式事务.分布式锁.ZooKeeper等知识. 所以咱们这篇文 ...

  5. 拜托,面试官不要在问我Redis分布式锁原理了

    大家好,给大家先做个自我介绍 我是码上代码,大家可以叫我码哥 我也是一个普通本科毕业的最普通学生,我相信大部分程序员或者想从事程序员行业的都是普通家庭的孩子,所以我也是靠自己的努力,从毕业入职到一家传 ...

  6. redis分布式锁 在集群模式下如何实现_收藏慢慢看系列:简洁实用的Redis分布式锁用法...

    在微服务中很多情况下需要使用到分布式锁功能,而目前比较常见的方案是通过Redis来实现分布式锁,网上关于分布式锁的实现方式有很多,早期主要是基于Redisson等客户端,但在Spring Boot2. ...

  7. @scheduled cron启动后和每小时执行_小耶哥: 一个Redis分布式锁又要和小鑫同学扯半个小时!...

    1 Redis分布式锁 |1-1 定时任务重复执行-问题引入 最近小耶哥在做一个功能, 什么功能呢? 就是超时未支付的订单我们要定时关闭, 释放库存, 并且短信通知用户该订单因超时被取消了.由于小耶哥 ...

  8. 简洁实用的Redis分布式锁用法

    在微服务中很多情况下需要使用到分布式锁功能,而目前比较常见的方案是通过Redis来实现分布式锁,网上关于分布式锁的实现方式有很多,早期主要是基于Redisson等客户端,但在Spring Boot2. ...

  9. redis分布式锁及秒杀系统实战

    本文分为两部分: 一.介绍redis分布式锁的原理和使用方法: 二.使用redis分布式锁实现一个简单的秒杀系统. 注意:本文使用java1.8,最后的例子为springboot项目. 目录 redi ...

最新文章

  1. python ftp 上传
  2. web worker原理 SSE原理
  3. 汇编语言(十)之最小偶数
  4. php如果能编译就完美了,centos7 完美编译PHP7 php-7.2.10.tar.gz
  5. JSF MVC 流程
  6. 卷积神经网络(CNN)相关的基础知识
  7. 康奈尔大学计算机科学人工智能,美国康奈尔大学工程学院申请之计算机科学
  8. AVEVA InTouch安全网关 AccessAnywhere 任意文件读取漏洞 CVE-2022-23854
  9. C#昵图素材下载器源码可下我图、包图、千图等(带数据库)
  10. CPDA项目数据分析师和CDA数据分析师有什么区别?
  11. 背后实力大比拼 探秘七大IT巨头实验室
  12. linux模拟键盘按键_Linux上的自动键盘按键
  13. 笔记本电脑耳机插入后声音还是外放的解决办法
  14. 乐理知识1--十二平均律
  15. python把汉字变成拼音英文_Python把汉字转换成拼音
  16. m132nw与m132snw差异_最新评测揭秘m132nw与m132snw差异哪个好?各个型号有什么区别?用户实话实说...
  17. 【车载以太网测试从入门到精通】——以太网TCP/IP协议自动化测试(含CAPL源码)
  18. 通过图片像素计算图片大小的方法
  19. Unity中使用另一个遮罩方案解决Mask组件的锯齿现象
  20. python中删除list中某指定元素

热门文章

  1. box-sizing:border-box;的一些坑
  2. beego validate验证包的修改原有验证提示信息与使用自定义函数验证的方法
  3. LINUX运维知识点总结(容器;云计算)
  4. idea启动多个相同的项目
  5. 身份证识别在移动端和服务器端的应用与特点
  6. mybatis-plus-join用法
  7. easyui的combobox设置下拉框默认选中某一项
  8. 电力应急指挥需要哪些终端设备?
  9. Java中脚手架是干什么用的_如何理解面向对象?
  10. 【机器学习/人工智能】 大作业:手写数字识别系统