1. 集群设计绕不开的话题
我接触了很多技术栈,比如:Redis、Kafka、ELasticsearch、Zookeeper等等,对于当时是一个初学者的我来说,这些技术栈都很牛。可是为什么有些人会给这些东西分成自带集群和非自带集群两类呢?是不是说配成多台机器再启动的就叫自带集群?不需要配成多台机器启动的就叫非自带集群呢?
后来我去思考,既然这些技术栈存在,且应用广泛,那么除了它们各自特殊的地方以外,肯定还有共通的点。于是,我便带上这样的想法上路了,果不其然,让我发现了技术其实是相通的。
1.1 发现单机的问题
所有的技术的起源都是为了解决某一类问题,那么关于集群,最初想解决的问题是什么呢?
1.1.1 集群解决的第一个问题
当我们用Redis的时候,最严重的问题莫过于生产环境Redis实例挂了吧?所以我们就想了一堆法子来保证Redis不挂,但是它能百分百不挂吗?我可以准确的告诉你,不能。那我们想这一堆法子来保证Redis不挂掉岂不是浪费时间和精力?当然不是!假设我们一年365天,那么每天是99%的可用性和99.999%有多大的区别呢?看下面的计算:
>>> import math
>>> math.pow(365, 0.99)
344.0883239364977
>>> math.pow(365, 0.99999)
364.97846600990744
>>> 364.97846600990744 - 344.0883239364977
20.89014207340972
看见差距了吗?差了近21天!也就是说,如果我们的Redis只有99%的可用性,那么一年中,我们有近21天不能使用它。但我们的可用性如果到了99.999%,那么一年就只有半小时左右不能使用它。显然我们更能接受后者。
其实讲到这,我们就能发现第一个问题!我们在这里引入集群不就是为了保证Redis不挂掉吗?简单来说就是:集群用来解决单点故障!
1.1.2 集群解决的第二个问题
随着业务的发展,我们发现一台Redis已经跟不上我们的需求了。那怎么办?你肯定说这还不简单,扩容啊。那怎么扩容呢?是给这单台机器加内存,还是将其中数据分片出去呢?其实这个选择需要具体问题具体分析。
如果我们的QPS远远满足需求,仅仅是因为单台机器内存不足,那么我们可以选择给Redis加内存,不过需要注意的是,由于Redis目前并没有原生支持的扩大内存机制(这里指的是宿主机已经不足以提供内存空间,杠精别说调整maxmemory然后更新配置),所以如果我们想扩容就得执行这些步骤:暂停当前Redis服务 -> 生成RDB文件 -> 导入新的足够大的机器中 -> 启动Redis并加载RDB -> 对外提供服务。是不是很坑?扩个容还不能热扩容?
那如果我们做分片呢?是不是就能热扩容了?理论上是的。不过有个限制条件是,你得提前留出多余的槽来,如果槽最后还是满了,那不好意思,扩容更麻烦了。后面的文章会提到这个问题,在这就不详细说了。
至此,你应该就知道了集群解决的第二个问题,引入多台机器是为了让Redis拥有更好的性能。总结来说:集群是为了提高承压能力和服务性能。
1.1.3 集群分类
文章最前面说到有些技术自带集群,但有些技术非自带集群,其实他们的区分方法很简单,就是能不能直接给正在对外服务的机器横向扩容,并且不影响服务,不过这往往会带来一些负面影响,比如说:Kafka消息不保证顺序。
关于上面的两个集群解决的问题,保证服务高可用的集群大多是通过主从复制来实现,提高性能的集群大多是通过负载均衡来实现,通常情况下,我们会将主从复制和负载均衡放在一起混合使用。
Redis在解决单机故障的问题上提供的方法是主从复制和哨兵模式,在解决性能的问题上提供的是cluster模式。后面的文章会详细说明这些解决方案。
1.2 发现集群的问题
单机有问题,那集群就没问题了吗?当然不是,集群有集群的问题。
1.2.1 CAP原理
CAP就是Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性)的首字母缩写。我记得我第一次接触这个名词的时候,我还是个初出茅庐的精神小伙,那时候我极其激动,感觉自己已经掌握了开发的究极奥秘,哈哈~
其实这个原理网上随便一搜,全都是各种角度详细的讲解,我在这就只做个简单的说明。
由于我们讨论的是集群,那么一定有多台服务器,所以,分区容错性必须得满足,那么为什么一致性和可用性,不能同时成立?举个例子:
如果我们有两台Redis服务器A和B。当有数据写入的时候,我们要保证一致性,那么AB必须都写成功,如果A写入的时候B挂掉了,那这个组合将无法继续写入数据,也就产生了不可用。
由于一致性和可用性两难全,所以后来人们就提出了Base理论,即允许在一定时间内的数据不一致,但是在有限的时间内最终必须得一致。
Reids的主从数据就是异步来同步信息的,所以并不满足强一致性要求,只是从节点会采用多种策略来尽可能保证从节点和主节点的数据一致。
1.2.2 强一致性
我们既然发现CAP并不能同时满足。那有没有哪种集群,可以保证强一致性而且在可用性上也还算不错的呢?其实是有的。
这就要提及Paxos算法了,Paxos由Lamport于1998年在《The Part-Time Parliament》论文中首次公开,最初的描述使用希腊的一个小岛Paxos作为比喻,描述了Paxos小岛中通过决议的流程,并以此命名这个算法,但是这个描述理解起来比较有挑战性。后来在2001年,Lamport觉得同行不能理解他的幽默感,于是重新发表了朴实的算法描述版本《Paxos Made Simple》。
这个只是个算法,由这个算法衍生出来的Raft(etcd用这个)、zab(zookeeper用这个)等等的具体实现。
Paxos算法:
阶段一:准备阶段,包括预定proposal序号,及accept端不再接受低于这个序号的请求
阶段二:收到超半数的accept回复,发起proposal。
learner可以被通知,也可以主动查询,一旦确认proposal被半数的accepter所接受那么就将自己的proposal的value改成对应的值
Raft算法:
- 个人对这个算法的理解就是主从模式,包括对master的选举。这里的操作日志又涉及到snapchat的知识(分cow和全量拷贝)。
zab算法:
- zab的个人理解:是一个主从结构的集群,但是又不像两段提交需要所有节点全都ack,只需要向paxos协议一样过半数ack即可commit,所以存在部分滞后性(这也是zab在一致性和可用性上做的取舍)。
- 自己的思考:由于zk属于单写多读且只有过半数ack才能commit的一致性处理,所以不适合高强度的写,比较适合少些多读的场景。
Redis并没有相关的强一致性实现,个人认为,本来Redis就是作为缓存数据库来使用的,丢极少量的数据是可以忍受的。如果某个业务场景下数据丢一点都不能忍受,那只能说在技术选型的时候,就应该把Redis排除了。