1 前言html
前面写了4篇Redis底层实现和工程架构相关文章,感兴趣的读者能够回顾一下:前端
今天开始来和你们一块儿学习一下Redis实际应用篇,会写几个Redis的常见应用。面试
在我看来Redis最为典型的应用就是做为分布式缓存系统,其余的一些应用本质上并非杀手锏功能,是基于Redis支持的数据类型和分布式架构来实现的,属于小而美的应用。redis
结合笔者的平常工做,今天和你们一块儿研究下基于Redis的分布式锁和Redlock算法的一些事情。算法
图片来自网络数据库
1. 锁的双面性编程
如今咱们写的程序基本上都有必定的并发性,要么单台多进线程、要么多台机器集群化,在仅读的场景下是不须要加锁的,由于数据是一致的,在读写混合或者写场景下若是不加以限制和约束就会形成写混乱数据不一致的状况。缓存
若是业务安全和正确性没法保证,再多的并发也是无心义的。安全
这个不禁得让我想起一个趣图:服务器
图片来自网络
高并发多半是考验大家公司的基础架构是否强悍,合理正确地使用锁才是我的能力的体现。
凡事基本上都是双面的,锁能够在必定程度上保证数据的一致性,可是锁也意味着维护和使用的复杂性,固然也伴随着性能的损耗,我见过的最大的锁可能就是CPython解释器的全局解释器锁GIL了。
没办法 好可怕 那个锁 不像话--《说锁就锁》
锁使用不当不但解决不了数据混乱问题,甚至会带来诸如死锁等更多问题,通俗地说死锁现象:
几年前会出现这样的场景: 在异地须要买火车票回老家,可是身份证丢了没法购票,补办身份证又须要本人坐火车回老家户籍管理处,就这样生活太难。
2. 无锁化编程
既然锁这么难以把控,那不得不思考有没有无锁的高并发。
无锁编程也是一个很是有意思的话题,后续能够写一篇聊聊这个话题,本次就只提一下,要打开思路,不要被困在凡是并发必须加锁的思惟定势。
在某些特定场景下会选择一种并行转串行的思路,从而尽可能避免锁的使用,举个栗子:
Post请求: http://abc.def/setdata?reqid=abc123789def&dbname=bighero
假若有一个上述的post请求的URI部分是个覆盖写操做,reqid=abc123789def,服务部署在多台机器,在大前端将流量转发到Nginx以后根据reqid进行哈希,Nginx的配置大概是这样的:
upstream myservice { #根据参数进行Hash分配 hash $urlkey; server localhost:5000; server localhost:5001; server localhost:5002; }
通过Nginx负载均衡相同reqid的请求将被转发到一台机器上,固然你可能会说若是集群的机器动态调整呢?我只能说不要考虑那么多那么充分,工程化去设计便可。
然而转发到一台机器仍然没法保证串行处理,由于单机仍然是多线程的,咱们仍然须要将全部的reqid数据放到同一个线程处理,最终保证线程内串行,这个就须要借助于线程池的管理者Disper按照reqid哈希取模来进行多线程的负载均衡。
通过Nginx和线程内负载均衡,最终相同的reqid都将在线程内串行处理,有效避免了锁的使用,固然这种设计可能在reqid不均衡时形成线程饥饿,不太高并发大量请求的状况下仍是能够的。
只描述不画图 就等于没说:
3. 单机锁和分布式锁
锁依据使用范围可简单分为:单机锁和分布式锁。
Linux提供系统级单机锁,这类锁能够实现线程同步和互斥资源的共享,单机锁实现了机器内部线程之间对共享资源的并发控制。
在分布式部署高并发场景下,常常会遇到资源的互斥访问的问题,最有效最广泛的方法是给共享资源或者对共享资源的操做加一把锁。
分布式锁是控制分布式系统之间同步访问共享资源的一种方式,用于在分布式系统中协调他们之间的动做。
1. 分布式锁的实现简介
分布式CAP理论告诉咱们须要作取舍:
任何一个分布式系统有三大特性:一致性Consistency、可用性Availability和分区容错性Partition Tolerance,可是因为网络分区不受人为控制,在网络发生分区时,咱们必须在可用性和一致性两者中选择之一。
在互联网领域的绝大多数的场景中,都须要牺牲强一致性来换取系统的高可用性,系统每每只保证最终一致性。在不少场景中为了保证数据的最终一致性,须要不少的技术方案来支持,好比分布式事务、分布式锁等。
分布式锁通常有三种实现方式:
2. 分布式锁须要具有的条件
分布式锁在应用于分布式系统环境相比单机锁更为复杂,本文讲述基于Redis的分布式锁实现,该锁须要具有一些特性:
3. 基于单Redis节点的分布式锁
本文的重点是基于多Redis节点的Redlock算法,不过在展开这个算法以前,有必要提一下单Redis节点分布式锁原理以及演进,由于Redlock算法是基于此改进的。
最初分布式锁借助于setnx和expire命令,可是这两个命令不是原子操做,若是执行setnx以后获取锁可是此时客户端挂掉,这样没法执行expire设置过时时间就致使锁一直没法被释放,所以在2.8版本中Antirez为setnx增长了参数扩展,使得setnx和expire具有原子操做性。
在单Matster-Slave的Redis系统中,正常状况下Client向Master获取锁以后同步给Slave,若是Client获取锁成功以后Master节点挂掉,而且未将该锁同步到Slave,以后在Sentinel的帮助下Slave升级为Master可是并无以前未同步的锁的信息,此时若是有新的Client要在新Master获取锁,那么将可能出现两个Client持有同一把锁的问题,来看个图来想下这个过程:
为了保证本身的锁只能本身释放须要增长惟一性的校验,综上基于单Redis节点的获取锁和释放锁的简单过程,使用lua脚本实现以下:
// 获取锁 unique_value做为惟一性的校验 SET resource_name unique_value NX PX 30000 // 释放锁 比较unique_value是否相等 避免误释放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
这就是基于单Redis的分布式锁的几个要点。
Redlock算法是Antirez在单Redis节点基础上引入的高可用模式。
在Redis的分布式环境中,咱们假设有N个彻底互相独立的Redis节点,在N个Redis实例上使用与在Redis单实例下相同方法获取锁和释放锁。
如今假设有5个Redis主节点(大于3的奇数个),这样基本保证他们不会同时都宕掉,获取锁和释放锁的过程当中,客户端会执行如下操做:
上述的5个步骤是Redlock算法的重要过程,也是面试的热点,有心的读者仍是记录一下吧!
1.关于马丁·克莱普曼博士
2016年2月8号分布式系统的专家马丁·克莱普曼博士(Martin Kleppmann)在一篇文章How to do distributed locking 指出分布式锁设计的一些原则而且对Antirez的Redlock算法提出了一些质疑。
笔者找到了马丁·克莱普曼博士的我的网站以及一些简介,一块儿看下:
用搜狗翻译看一下:
1.我是剑桥大学计算机科学与技术系的高级研究助理和附属讲师,由勒弗乌尔姆信托早期职业奖学金和艾萨克牛顿信托基金资助。我致力于本地优先的协做软件和分布式系统安全。
2.我也是剑桥科珀斯克里斯蒂学院计算机科学研究的研究员和主任,我在那里从事本科教学。
3.2017年,我为奥雷利出版了一本名为《设计数据密集型应用》的书。它涵盖了普遍的数据库和分布式数据处理系统的体系结构,是该出版社最畅销书之一。
4.我常常在会议上发言,个人演讲录音已经被观看了超过15万次。
5.我参与过各类开源项目,包括自动合并、Apache Avro和Apache Samza。
6.2007年至2014年间,我是一名工业软件工程师和企业家。我共同创立了Rapportive(2012年被领英收购)和Go Test(2009年被红门软件收购)。
7.我创做了几部音乐做品,包括《二月之死》(德语),这是唐克·德拉克特对该书的音乐戏剧改编,于2007年首映,共有150人参与。
大牛就是大牛,能教书、能出书、能写开源软件、能创业、能写音乐剧,优秀的人哪方面也优秀,服气了。
图片来自网络
2.马丁博士文章的主要观点
马丁·克莱普曼在文章中谈及了分布式系统的不少基础问题,特别是分布式计算的异步模型,文章分为两大部分前半部分讲述分布式锁的一些原则,后半部分针对Redlock提出一些见解:
针对fencing机制马丁给出了一个时序图:
获取锁的客户端在持有锁时可能会暂停一段较长的时间,尽管锁有一个超时时间,避免了崩溃的客户端可能永远持有锁而且永远不会释放它,可是若是客户端的暂停持续的时间长于锁的到期时间,而且客户没有意识到它已经到期,那么它可能会继续进行一些不安全的更改,换言之因为客户端阻塞致使的持有的锁到期而不自知。
针对这种状况马丁指出要增长fencing机制,具体来讲是fencing token隔离令牌机制,一样给出了一张时序图:
客户端1得到锁而且得到序号为33的令牌,但随后它进入长时间暂停,直至锁超时过时,客户端2获取锁而且得到序号为34的令牌,而后将其写入发送到存储服务。随后,客户端1复活并将其写入发送到存储服务,然而存储服务器记得它已经处理了具备较高令牌号的写入34,所以它拒绝令牌33的请求。
Redlock算法并无这种惟一且递增的fencing token生成机制,这也意味着Redlock算法不能避免因为客户端阻塞带来的锁过时后的操做问题,所以是不安全的。
这个观点笔者以为并无完全解决问题,由于若是客户端1的写入操做是必需要执行成功的,可是因为阻塞超时没法再写入一样就产生了一个错误的结果,客户端2将可能在这个错误的结果上进行操做,那么任何操做都注定是错误的。
3.马丁博士对Redlock的质疑
马丁·克莱普曼指出Redlock是个强依赖系统时间的算法,这样就可能带来不少不一致问题,他给出了个例子一块儿看下:
假设多节点Redis系统有五个节点A/B/C/D/E和两个客户端C1和C2,若是其中一个Redis节点上的时钟向前跳跃会发生什么?
分布式异步模型:
上面这种状况之因此有可能发生,本质上是由于Redlock的安全性对Redis节点系统时钟有强依赖,一旦系统时钟变得不许确,算法的安全性也就没法保证。
马丁实际上是要指出分布式算法研究中的一些基础性问题,好的分布式算法应该基于异步模型,算法的安全性不该该依赖于任何记时假设。
分布式异步模型中进程和消息可能会延迟任意长的时间,系统时钟也可能以任意方式出错。这些因素不该该影响它的安全性,只可能影响到它的活性,即便在很是极端的状况下,算法最可能是不能在有限的时间内给出结果,而不该该给出错误的结果,这样的算法在现实中是存在的好比Paxos/Raft,按这个标准衡量Redlock的安全级别是达不到的。
4.马丁博士文章结论和基本观点
马丁表达了本身的观点,把锁的用途分为两种:
最后马丁出了以下的结论:
马丁认为Redlock算法是个糟糕的选择,由于它不三不四:出于效率选择来讲,它过于重量级和昂贵,出于正确性选择它又不够安全。
马丁的那篇文章是在2016.2.8发表以后Antirez反应很快,他发表了"Is Redlock safe?"进行逐一反驳,文章地址以下:http://antirez.com/news/101
Antirez认为马丁的文章对于Redlock的批评能够归纳为两个方面:
Antirez对这两方面分别进行了细致地反驳。
关于fencing机制
Antirez提出了质疑:既然在锁失效的状况下已经存在一种fencing机制能继续保持资源的互斥访问了,那为何还要使用一个分布式锁而且还要求它提供那么强的安全性保证呢?
退一步讲Redlock虽然提供不了递增的fencing token隔离令牌,但利用Redlock产生的随机字符串能够达到一样的效果,这个随机字符串虽然不是递增的,但倒是惟一的。
关于记时假设
Antirez针对算法在记时模型假设集中反驳,马丁认为Redlock失效状况主要有三种:
后两种状况来讲,Redlock在当初之处进行了相关设计和考量,对这两种问题引发的后果有必定的抵抗力。
时钟跳跃对于Redlock影响较大,这种状况一旦发生Redlock是无法正常工做的。
Antirez指出Redlock对系统时钟的要求并不须要彻底精确,只要偏差不超过必定范围不会产生影响,在实际环境中是彻底合理的,经过恰当的运维彻底能够避免时钟发生大的跳动。
分布式系统自己就很复杂,机制和理论的效果须要必定的数学推导做为依据,马丁和Antirez都是这个领域的专家,对于一些问题都会有本身的见解和思考,更重要的是不少时候问题自己并无完美的解决方案。
此次争论是分布式系统领域很是好的一次思想的碰撞,不少网友都发表了本身的见解和认识,马丁博士也在Antirez作出反应一段时间以后再次发表了本身的一些观点:
For me, this is the most important point: I don’t care who is right or wrong in this debate — I care about learning from others’ work, so that we can avoid repeating old mistakes, and make things better in future. So much great work has already been done for us: by standing on the shoulders of giants, we can build better software.
By all means, test ideas by arguing them and checking whether they stand up to scrutiny by others. That’s part of the learning process. But the goal should be to learn, not to convince others that you are right. Sometimes that just means to stop and think for a while.
简单翻译下就是:
对马丁而言并不在意谁对谁错,他更关心于从他人的工做中汲取经验来避免本身的错误重复工做,正如咱们是站在巨人的肩膀上才能作出更好的成绩。
另外经过别人的争论和检验才更能让本身的想法经得起考验,咱们的目标是相互学习而不是说服别人相信你是对的,所谓一人计短,思考辩驳才能更加接近真理。
在Antirez发表文章以后世界各地的分布式系统专家和爱好者都积极发表本身的见解,笔者在评论中发现了一个熟悉的名字:
铁蕾大神的两篇文章写的很是好,本文从中作了不少参考,也是铁蕾大神的文章让笔者了解到这场精彩的华山论剑,感兴趣的能够直接搜索阅读参考3和4。