一致性hash
使用Hash时遇到的问题
在上述hash取模的过程中,我们虽然不需要对所有Redis服务器进行遍历而提升了性能。但是,使用Hash算法缓存时会出现一些问题,Redis服务器变动时,所有缓存的位置都会发生改变。
比如,现在我们的Redis缓存服务器增加到了8台,我们计算的公式从hash(product.png) % 6 = 5变成了hash(product.png) % 8 = ? 结果肯定不是原来的5了。
再者,6台的服务器集群中,当某个主从群出现故障时,无法进行缓存,那我们需要把故障机器移除,所以取模数又会从6变成了5。我们计算的公式也会变化。
由于上面hash算法是使用取模来进行缓存的,为了规避上述情况,Hash一致性算法就诞生了
一致性Hash算法原理
一致性Hash算法也是使用取模的方法,不过,上述的取模方法是对服务器的数量进行取模,而一致性的Hash算法是对2的32方取模。即,一致性Hash算法将整个Hash空间组织成一个虚拟的圆环,Hash函数的值空间为0 ~ 2^32 - 1(一个32位无符号整型),整个哈希环如下:
将数据Key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针查找,遇到的服务器就是其应该定位到的服务器。
总结:一致性hash通过减少影响范围的方式解决了增减服务器而导致数据散列的问题。从而解决了分布式环境下负载均衡的问题
一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,有很好的容错性和可扩展性。
数据倾斜问题
这时我们发现有大量数据集中在节点A上,而节点B只有少量数据。为了解决数据倾斜问题,一致性Hash算法引入了虚拟节点机制,即对每一个服务器节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。
数据定位算法不变,只需要增加一步:虚拟节点到实际点的映射。
所以加入虚拟节点之后,即使在服务节点很少的情况下,也能做到数据的均匀分布。
Raft
Raft 的数据一致性策略
Raft 协议强依赖 Leader 节点来确保集群数据一致性。即 client 发送过来的数据均先到达 Leader 节点,Leader 接收到数据后,先将数据标记为 uncommitted 状态,随后 Leader 开始向所有 Follower 复制数据并等待响应,在获得集群中大于 N/2 个 Follower 的已成功接收数据完毕的响应后,Leader 将数据的状态标记为 committed,随后向 client 发送数据已接收确认,在向 client 发送出已数据接收后,再向所有 Follower 节点发送通知表明该数据状态为committed。
Consul如何通过Raft协议实现强一致性?
首先,库存服务注册到Leader Server的时候,会采取Raft协议,要求必须让Leader Server把这条注册数据复制给大部分的Follower Server才算成功。这就保证了,如果你认为自己注册成功了,那么必然是多台Consul Server都有这条注册数据了。如果你刚发送给Leader Server他自己就宕机了,那么这次注册会认为失败。此时,Consul Server集群会重新选举一个Leader Server出来,你需要再次重新注册。这样就可以保证你注册成功的数据绝对不会丢,然后别人发现服务的时候一定可以从Leader Server上获取到最新的强一致的注册数据。
CAP理论
CP 和 AP不可能同时满足
P
代表分区容错, 在整个分布式系统中某个节点服务挂掉了,并不影响整个系统的运作和使用,
因为他可以在稍后或者通过切换可用节点立即恢复使用
C:
写操作之后的读操作,必须返回该值。
注册中心集群中: leader的作用, 所有的写操作都依赖于leader来完成,为了保证数据的一致性, leader只有一个
假如: 没有leader,首先加入我们新加入一台数据处理服务,就会向注册中心1进行注册,注册中心1写入数据处理服务的ip
等等基本信息,并且准备同步给其他注册中心节点, 结果这个在还没发生同步的过程中,注册中心1挂掉了,
然后客户端准备调用数据中心写入,这个时候就因为注册中心1挂掉了,就直接切到了注册中心2,但是注册中心2没有
收到数据处理服务的添加请求,所以没有这个服务,这个时候就对客户端显示不可用了.
A:
没有leader,可以很容易的切换到可用的注册中心,对于客户端的调用总是及时反应, 在上述C操作的例子中,
对于向服务注册,获取服务注册的基本信息,比如ip来说,基本不会存在,因为像Eureka来说,我们的服务可以
向所有的注册中心节点发起注册请求, 这样就不会存在注册中心节点服务列表不一致的情况
AP、CP及使用场景
在分布式环境下,为了保证系统可用性,通常都采取了复制的方式,避免一个节点损坏,导致系统不可用。那么就出现了每个节点上的数据出现了很多个副本的情况,而数据从一个节点复制到另外的节点时需要时间和要求网络畅通的,所以,当P发生时,也就是无法向某个节点复制数据时,这时候你有两个选择:
- 选择可用性 A(Availability),此时,那个失去联系的节点依然可以向系统提供服务,不过它的数据就不能保证是同步的了(失去了C属性)。
- 选择一致性C(Consistency),为了保证数据库的一致性,我们必须等待失去联系的节点恢复过来,在这个过程中,那个节点是不允许对外提供服务的,这时候系统处于不可用状态(失去了A属性)。
最常见的例子是读写分离,某个节点负责写入数据,然后将数据同步到其它节点,其它节点提供读取的服务,当两个节点出现通信问题时,你就面临着选择A(继续提供服务,但是数据不保证准确),C(用户处于等待状态,一直等到数据同步完成)。
P不存在,就是单机环境了,单机下CA是一定存在。
为什么选择大多数节点?因为分区后,我们要尽可能多的对外提供服务,所以在网络分区时当然要选择大多数节点。
nacos
阿里的nacos : 性能最好
他同时支持AP和CP模式,他根据服务注册选择临时和永久来决定走AP模式还是CP模式,
他这里支持CP模式对于我的理解来说,应该是为了配置中心集群,因为nacos可以同时作为注册中心和配置中心,
因为他的配置中心信息是保存在nacos里面的,假如因为nacos其中一台挂掉后,还没有同步配置信息,
就可能发生配置不一致的情况. 配置中心的配置变更是服务端有监听器,配置中心发生配置变化,
然后服务端会监听到配置发生变化,从而做出改变
eureka+spring cloud config:
性能也不差,对于服务数量小于上千台来说,性能没有问题
-
eureka: 可以做注册中心,完全AP,支持注册中心之间的节点复制,同时支持服务端同时注册多个注册中心节点, 所以不存节点信息不一致的情况
-
config: 单独服务,是从git仓库拉取配置信息,然后服务端从config服务里面拉取配置信息缓存到本地仓库
这里配置的变更比较麻烦,他需要结合bus组件,同时约束了只能用rabbitmq和kafka来进行通知服务端进行配置变更,但是保证了数据的一致性,因为他的配置信息在git仓库上,git仓库只有一个,就会数据一致
阿里nacos异常情况 leader挂了
1.不影响服务之间互相调用
2.不影响服务注册
3.不影响服务正常启动拉取配置文件
4.选举新leader差不多4,5秒钟