花5min就能搞清楚redis和zookeeper分布式锁的区别,太有必要读一下了
今天有个师弟问到了我这个问题,我说网络上文章有很多,自己查一下吧,他说读了好几篇还是不太清楚,于是我就搜了一下,呃……
最终还是耐心地给他上了一课,他听完以后感激涕零,想到他晚上回到家,倒上二两散装白酒,跟女友分享今天学习到新技能时的喜悦,我欣慰地笑了。
一、目标
锁,解决的是多线程或多进程情况下的数据一致性问题;分布式锁,解决的是分布式集群下的数据一致性问题。
本身这个事情就没有多复杂,问起这个问题的人起码都使用过redis和zookeeper吧,在这里我就不多哔哔了,咱们直接搞起来。
两者的区别,根本上在于两者的实现不同。
二、单机
单机场景下,首选redis实现分布式锁。
理由如下(同时也是redis的特点):
1.redis完全基于内存
2.redis单线程多路复用,减少了多个线程创建时的开销,避免了不必要的上下文切换以及资源竞争导致的锁开销
3.全程hash结构
总之就一个字,速度贼鸡儿快!
而zk的话是目录树结构,性能完全没得比。
有些人说,哎呀,redis可能会丢数据的呀~
我不知道这话从哪听到的,纯属扯淡!现在我拍着胸脯向你保证,redis他妈的不丢数据!
redis持久化方式有两种对吧,rdb和aof。rdb遇到redis宕机,的确是会丢掉上次bgsave到目前的数据,可是你能选择aof模式啊。
aof模式下还需要做件小事情,打开redis.conf文件,找到${appendfsync}这个字段,给它配置成always,搞定,如此简单。
${appendfsync}:
no:完全依赖于操作系统的调度
everysec:每隔一秒调用一次fsync
always:每次写命令都会调用fsync,最安全,性能差
fsync的意思是说将缓存中的数据刷到磁盘。需要知道,为了提高速度,文件系统也是有缓存的,运维工程师每次释放linux系统的内存前都要执行sync命令,就是干这个的。
还有些人说,我选择zk实现,zk的ephemeral节点在连接端挂掉之后能够自己删除~
呵呵,你给redis设置个过期时间不就OK了吗?业务未执行完担心锁过期?你可以做续时啊,你不会做你可以使用现成的redisson啊,redisson有watch dog能帮你搞定的。
所以单实例场景下,redis完全莫问忒。唯一要注意的就是redis的内存淘汰,别鸡儿莫名其妙数据给删了都不知道。内存淘汰策略的话可以选择noeviction,到了阈值就抛异常,获取锁的时候要写数据嘛,这样内存满了就不能写了。
但是如今稍微大一些的系统,都不太可能使用单实例,redis和zk实现的分布式锁主要区别也不是在单实例场景下。
三、集群
说起分布式集群,必然要提起的是CAP原则,CAP即Consistency、Availability、Partition tolerance,就是一致性、可用性和分区容错性,三者只能取其二,我们就是要从CP、AP来选则,为什么不选CA?因为选了CA就是单机版本了。
先说redis集群,redis集群是基于AP实现的,有3种模式:
第一种是master-slaver,简单的主从模式;
第二种是sentinel,叫哨兵模式,就是多了个哨兵,假如master宕机了,它帮你做主备切换,不用再人工干涉了;
第三种是cluster,叫集群模式,这种模式下多个节点存储不同的数据。节点上有个叫slot的玩意儿,将范围限制在0-16383,每次数据来了,通过crc16计算出一个结果,然后对16384取余数,通过这个数就知道哈希槽在哪个节点了,最终到对应的节点上存数据。需要知道的是,为了保证高可用,每个节点也引入了主从模式
问题就出在这里,说一下redis主从复制过程:
1.从服务器连接主服务器,发送sync命令;
2.主服务器收到sync命令后,开始执行bgsave命令生成rdb文件并使用缓冲区记录此后执行的所有写命令;
3.主服务器bgsave执行完后,向所有从服务器发送rdb文件,并在发送期间继续记录被执行的写命令;
4.从服务器收到rdb文件后丢弃所有旧数据,载入收到的快照;
5.主服务器发送完rdb文件后开始向从服务器发送缓冲区中的写命令;
6.从服务器完成对rdb文件的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
7.主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令
划重点,redis为了保证高可用,主从复制过程是异步的,也就是说,当客户端向master写了一个数据,master提交完就给你飞吻了,而不管slaver是否已经同步完成数据,这样写数据和同步数据各玩各的,同步数据过程中不影响写数据,这样叫做高可用,但也带来了以下两个问题。
问题1,主从复制丢数据
C1向master申请到了锁a,master在同步数据的过程中挂掉了,此时slaver还没有拿到锁a的数据,主备切换,slaver升级成master1,C2向master1申请锁a,申请成功
问题2,脑裂
C1向master申请到了锁a,master网络出问题了,sentinel收不到master的心跳,于是将slaver选举成master1,但此时还有些C端能够访问到master,假如此时C2能够访问到master,但C3访问的却是master1,于是C2在master上能够申请到锁b,C3在master1上也能够申请到锁b
当这两种情况出现的时候,锁肯定就有问题了,即便是RedLock来实现,也只能降低风险,而不能保证100%可靠。不要拿超级计算机上使用成千上万个redis单实例实现RedLock来杠。
顺便提醒一下,redis中rdb文件生成,不管是持久化还是主从复制,都要先copy完整的数据副本到内存中,所以必须要注意redis的内存量与当前系统的内存量。
下面说zk集群,zk集群是基于CP实现的,具有强一致性。而强一致性,则是通过ZAB(ZooKeeper Atomic Broadcast)协议实现的,即ZooKeeper原子播送协议。
ZAB协议支持自动恢复和原子播送,是专门为zk量身定做的一种一致性协议。
关于自动恢复是通过投票选举来实现的,有兴趣的伙伴可以自行查阅,我就懒得说了,这里专门讲一下原子播送。
原子播送即同步复制的过程,我们从其它的一致性协议2PC说起。
2PC协议,叫两阶段提交协议(Two-phase Commit),简单些来说:
第一阶段:表决(请求阶段)
事务协调者通知每个参与者准备提交或取消事务,然后进入表决过程,参与者要么在本地执行事务,但不提交;参与者将告知协调者自己的决策: 同意或取消
第二阶段:执行(提交阶段)
此时,协调者将基于请求阶段的投票结果进行决策: 提交或取消;当且仅当所有的参与者同意提交事务,协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务;参与者在接收到协调者发来的消息后将执行相应的操作
2PC协议隐藏的问题中在这里我们仅仅需要关注的是同步阻塞,即同步过程中不允许有新的数据写入。
其他的一致性协议还有3PC、Paxos等,ZAB协议的过程可以参考2PC协议(但ZAB协议是过半有效),但实际上更像是Paxos协议的改进版本,由于比较复杂,这里放不下,咿,还听押韵的~就不说了。但是,他们都没有能够彻底解决同步阻塞的问题。
四、结论
redis和zk分布式锁的区别,关键就在于高可用性和强一致性的选择,redis的性能高于zk太多了,可在可靠性上又远远不如zk,具体场景下如何选择,只能仁者见仁,智者见智,老鼠的儿子会打洞了。
简单说一下,就是老生常谈的几个话题(和洋洋姐确认过了):
火车站窗口卖票:需要的是强一致性,zk的话性能堪忧,春运的时候岂不是炸了?
银行转账:需要的是最终一致性,redis的话不如zk可靠,需要做一些其它补偿。
网上购物:需要的是最终一致性,容错率更高,比如你看着还有俩,但是买不到了,也不是很严重对吧。
有任何问题或者发现文章中的错误之处,欢迎私信或者邮箱过来,谢谢。
公主号搜索:以镒称铢