vlambda博客
学习文章列表

间谍高度(上帝视角)和redis分布式锁

间谍高度(上帝视角)和redis分布式锁

  • 一 写作动机的初衷

    • 1.1 分享与留下

  • 二 分享

    • 2.1 间谍高度

    • 2.2感受

  • 三 留下

    • 3.1专业技术的思考

  • 四 redis分布式锁的内容

    • 4.1代码

      • 抢锁代码

      • 加锁代码

      • 解锁代码

      • 效果演示

    • 4.2结尾撒花


一 写作动机的初衷

1.1 分享与留下

       编写前,就是忍不住唠叨两句,写博客更多情况下的初衷, 是想记录一些感悟,和通过分享的方式帮助需要的的朋友,并且也是为了我自身触点的梳理和反思。用文字记录的方式,写下那些不论是生活,学习,情感的触动点,当然在这里的博客主要是学习的触动记录啦!其实对于我个人而言这种方式是我留下它,她,他,们的方式吧…
       此篇博客重点为了表达主题中的前者,也就是间谍高度,想阐述一种思想感悟,后者分布式redis锁为一个小案例。

二 分享

2.1 间谍高度

分享一段《人类简史》书中高纬度视角思考问题的片段。
       前方高能,灵魂拷问,你觉得 人类文化的历史有个大方向吗?(思考中ing…)
       答案是肯定的。几千年来,规模较小而简单的各种文化逐渐融入较大,较复杂的文明中,于是世界上的文化数量相比较于今日在逐渐减少,但是规模及复杂程度远胜昨日。这是宏观的粗略说法。从微观层面来看,每次几个文化融合成大型文化时,也是可以看到大型文化的解离,例如蒙古帝国雄霸亚洲,但最后分崩离析;基督教虽然信众数以亿计,但也分裂成无数教派;拉丁文虽然一度流通中西欧,最后还是转化成了各种当地方言,演化出各国的语言。即使有这些例子似乎在反驳答案,可是这并不能说明什么,合久必分只是一时,分久必和才是不变的大趋势。
       想观察历史的方法,重点在于要那种高度。如果是普通的鸟瞰高度,看着几十年或几世纪的发展走向,可能还是很难判断历史趋势究竟是分是合。这个高度不足,必须拉类似太空间间谍卫星的高度,看的不是几世纪,而是几千年的跨度。 这种高度能够让我们一目了然,知道历史趋势就是走向分久闭合,前面所说的基督教分裂和蒙古国崩溃的例子,在这个高度下就像是历史大道上的小小颠簸。
       我们现在常认为地球是一个单位,但是在历史上大多数时间,地球更像一个星系,人类文明各自构成不同的世界。比如公元前4世纪到公元前3世纪左右,中国处于战国时代,群雄争霸;同时美洲也有各个不同的玛雅文明互相竞逐。而这两边的斗争却是毫不相干,对于这些人来说火星和金星一样。可是现在几乎所有的人类都接受同样的经济制度(资本主义的形塑);采用一样的法律制度(人权和国际法至少是四海皆准的);也接受同样的科学体系…

2.2感受

       有没有被这种思考方式惊艳到?这种思考问题的方式,一个字绝!,当高度不足以鸟瞰问题时,无法解决眼前的迷障时,需找到更高维度的视角来看待问题,所以要有个前提就是,我们当前知识的储备量是否足够,但这只是第一步,更重要的是一些知识的串联梳理整合为 体系 后才会拔高我们的视角。站在更高的角度不畏迷雾遮眼!

三 留下

3.1专业技术的思考

       联想起了,技术学习时走过的一些弯路,刚工作学习的时候,接触学习新技术时,身心很排斥,可是当时并没有觉得有哪里不对,只是认为自己的反应不过是技术点难于理解,例如最开始jsp学习无法理解,还有java明明提供了synchronized ,干嘛非要学redis锁,zookeeper锁,文件的存储fastdfs,oss…,为什么nginx可以做到前后端分离。现在看来当时的排斥更大部分程度是,缺少了关键点的垫脚,所以走起阶梯来,十分的费劲和蹩脚,如果学习jsp 前我先了解servlet规范,就会自然的接受;如果先学习了微服务,了解了2pc,3pc,等有关于一致性协议的知识,不会排斥redis锁,zookeeper锁。如果学习nginx,先了解下浏览器的http请求与服务器的交互,http协议,顺其自然的明白前后端分离。同样对fastdfs,oss,obs,的学习如果先前知道了传统的文件的存储带来的不便捷性和考虑维护成本,就会欣然接受这些组件。
       看到这里也许会觉得,这不是在废话吗,要是早知道,肯定先去学习啊。可我想说,为什么这一个一个的问题看起来各有各的原因,但有没有想过,这些问题的原因,其实本质上是一个问题,接触一个新技术时,反复思考需要哪些知识来铺垫!所以要追问自己:首先,它的出现到底解决了什么问题,其次 在没有此项技术前人们是怎么做的(找到知识铺垫)最后才是怎么应用。 如果没有前两步的铺垫,我们所以学的知识,体系是不完整的,无法串联起来, ** 真正导致知识水平差异的,往往不是知识数量,而是知识之间的联系,高考650分和高考400分的人,所学的书籍和资料基本是一样的,造成巨大差异的,更多是考650分的人更善于建立知识体系,他们的知识都是成块的,处理信息熵程度达到最大!**(其实我内心一直都在画一个巨大的知识树,层级分明,关联逻辑清晰,等以后出来会和大家分享)

四 redis分布式锁的内容

       为了文章的连贯性,还是在说一遍前提知识的铺垫:传统的单节点服务,运行在同一个jvm中,但是当拆分为微服务后,程序自然是运行在不同的jvm中,对于单体架构的加锁,我们可以用内置锁synchronized,或者显示锁Lock ,但这局限于单体中,如果拆分后,或者单体的在集群部署后,对某一公共资源的访问,需要加锁访问,就要考虑我们使用中间件协助加锁,这里又涉及到了选择,常用的中间件 ,一般有redis,和zookeeper,暂时不讨论他们的区分,从整体角度来说都是,操作同一个数据的值,并且在同一时间只能有一个线程设置成功。【对于redis来说是KV,而zookeeper是节点的操作访问】

       redis锁这也是最近工作时遇到的一个内容,其实在早之前 看过很多博客和例子,也手动实现过,但这是第一次在工作里的应用实践,将来要部署到生产环境中,所以实践后也仔细的检查了下。下面说下整体的逻辑:

  • 使用下面命令实现加锁

127.0.0.1:6379> set key value [EX seconds] [PX milliseconds] [NX|XX]

  • 使用lua脚本进行解锁(怕误删其他client的锁)

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

NX:表示只有当前key:value 不存在的时候才能设置成功,
EX:表示设置当前值的存在时间,可以用来避免死锁,在一定时间范围后让锁失效。
看下图片简单操作:哦对了,这个操作对redis来说是一条命令:保证了原子性(1 比较是否存在 2 设置值 3 设置过期时间,这三个封装成一个原子命令)

简单解释下加锁:在并发多线程下,只有一个线程抢到锁,才能继续运行程序,其他未抢到的锁的线程,需要等待,对应到redis 中就是,如果set 一个key,如果恰巧当前不存在此key:value,则成功设置value 表明抢到锁,如果设置失败,说明有其他线程已经抢到锁,失败的线程需要下次尝试,这个尝试行为,对应到java代码中是一个递归调用,稍后看下gitHub上的代码即可明白。为了避免死锁,通过ex 参数设置过期时间,(这是考虑到如果,有个线程获取锁后,还未释放,就挂掉了,会造成死锁的情况)

解锁:解锁时需要用lua,脚本保证 get 和 del 这两个命令的原子性,因为lua 脚本属于面向过程性语言,可以保证两个命令的原子性。
【特别说明下:有的朋友会疑惑为啥子要比较value后再删除,明明已经是当前的锁了,还有哪个线程能神奇的拿到锁,这里需要考虑一种特殊情况,假设锁过期时间为3s钟,但是线程执行逻辑达到了5s钟,则第二个线程一定获取锁了,那么此时第一个线程在去执行解锁代码删除的是第二个线程的锁。请看下图】
间谍高度(上帝视角)和redis分布式锁
看完图细心的你会发现,如果业务逻辑复杂,没等执行完,就过期该怎么办?所以我们要给锁续命,
redis 锁 从时间上分两种类型:1 固定时间的租约锁 2 不固定时间的续约锁
怎么区分应用

  1. 在你前提知道自己的业务逻辑一定能在多长时间内跑完,就使用固定时间的租约锁。

  2. 如果不知道也好办,新开一个线程,不断循环当前key:value是否要过期,例如redisson这个工具其实已经实现了此功能,它默认是总过期时间是30s,但是如果过了20s 业务还未处理完成则,续约修改redis中键值对的过期时间, 我们不断循环的条件就是从当前过期时间是否达到我们的阈值,并且当前锁的value值,也的确是对应当前线程所拥有的.如果都达到,则修改redis 中的值,否则跳出循环,结束线程。

4.1代码

github : https://github.com/brinjaul/demo-redis-lock.git

贴下关键代码的解释

抢锁代码

对于未签到锁的线程,递归调用抢锁。
间谍高度(上帝视角)和redis分布式锁

加锁代码

间谍高度(上帝视角)和redis分布式锁

解锁代码

解锁需要删除属于当前线程的锁
间谍高度(上帝视角)和redis分布式锁

效果演示

测试用例中测试20个并发对唯一变量进行+1 操作。在正常线程安全情况下最终值当为10,
我们先看下未加锁的结果。


再看下加锁的后的状态:

4.2结尾撒花

       本篇博客也仅仅是我一点小小见解,后面的代码也很粗糙,未优化整理,仅仅作为demo小样来使用,我想说,身处计算机世界中,才更能体会到,技术知识的浩瀚,我们所接触的不过是九牛一毛,如果你有兴趣去探索,一定会发现一个更加广阔的世界。希望我们能互相分享交流