(三)漫谈分布式之集群篇:99.99…%高可用与PB级数据存储背后的架构原理

引言

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

集群,相信诸位对这个概念并不陌生,集群已成为现时代中,保证服务高可用不可或缺的一种手段。

回想起初集中式部署的单体应用,因为只有一个节点,因此当该节点出现任意类型的故障(网络、硬件资源、物理环境......)时,都会造成整个系统对客户端不可用,而这就是所谓的"单点故障问题"。

单点故障是建立高可用系统的第一道坎,而集群恰恰是解决单点故障最有效的手段,就算系统内一个节点出现故障,依旧有其他健康的节点能处理请求,保障系统正常运行,实现99.999......%高可用。

很多人对集群的认知,都源自于Nginx,因为当程序性能跟不上业务需求、又不想对服务器升配时,就可以使用Nginx来加机器,使用多台"价格优美"的低配机器,为服务做集群化部署,从而提升系统整体的吞吐量。

不过许多人对集群的认知止步于此,如何实现PB级的海量数据存储?大部分人并不清楚,而本文则来详细聊聊集群的各方面知识,为诸位量身打造出结构化的集群知识体系。

PS:个人编写的《技术人求职指南》小册已完结,其中从技术总结开始,到制定期望、技术突击、简历优化、面试准备、面试技巧、谈薪技巧、面试复盘、选Offer方法、新人入职、进阶提升、职业规划、技术管理、涨薪跳槽、仲裁赔偿、副业兼职......,为大家打造了一套"从求职到跳槽"的一条龙服务,同时也为诸位准备了七折优惠码:3DoleNaE,感兴趣的小伙伴可以点击:s.juejin.cn/ds/USoa2R3/了解详情!

一、集群的定义与分类

集群,即是指:通过多台物理机器,组成一台逻辑上的庞大机器使用,集群带来的优势有四:

  • ①高可用:集群内某个节点故障,可迅速将流量迁移至其他节点,解决了单点故障;
  • ②吞吐量:多台机器并行处理外部请求,可以为系统带来更强大的负载与吞吐能力;
  • ③拓展性:可根据业务的增长/萎靡,动态伸缩集群内的节点数量,系统更加灵活;
  • ④性价比:无需花费更高的价格升配机器,可使用多台价格、配置较低的机器构建。

集群带来的好处很多,即解决了单点故障,又兼顾了吞吐与性能、动态伸缩、性价比,同时对客户端是无感知的,客户端在请求时,无法分辨出究竟采用了多少台机器来部署服务。

上面是集群的基本定义与优势,现在问大家一个问题:从性质维度出发,你认为集群可以分为哪几种?

1.1、集群的分类

集群大家很熟,但一问集群的分类,估计各位会愣住,这......我没想过啊~

其实集群可粗略分为两大类,逻辑处理集群、数据存储集群,前者对应着业务系统,后者对应着数据存储组件,举些例子说明:

  • 逻辑处理型集群:业务服务、API网关、请求分发器、并行运算(科学计算)服务等;
  • 数据存储型集群:缓存中间件、消息中间件、数据库、搜索中间件集群、对象存储等。

仔细观察下,如果熟悉云技术的小伙伴会发现,这跟云平台里定义的有状态、无状态概念很类似~

简单来说,逻辑处理型的集群,只需要处理客户端请求的逻辑运算,拿业务系统来说,目前有个登录功能,系统只需根据业务流程,执行完对应的业务逻辑,接着就可给客户端响应结果。

PS:为了便于后续讲述,逻辑处理型集群则用业务系统来代替,当提到业务系统时,可自动代入"逻辑处理型集群"。

而存储型的集群,客户端一般是"程序",如DB、Redis、MQ、ES......,生产环境里,处理的绝大多数请求,都来自于业务系统。对于客户端的请求,需要保存信息,处理写入请求需要将客户端带来的数据存好,处理读取请求则需将之前存好的数据拿出来返回。

当然,有人也许会疑惑,业务系统不也是读写请求吗?为什么将其归类到逻辑处理型?这是因为业务系统自身不会存储任何信息,客户端(用户)提交的数据,会由业务系统间接调用各类组件来存储,如登录信息放到Redis、业务数据放到DB......。

1.2、逻辑处理型集群的核心

在之前的模式中,因为只有一台机器,域名可直接映射服务器的公网IP,当出现对应域名的请求,DNS可直接解析到对应的服务器IP,客户端(如浏览器)直接对拿到的IP发起请求就行。

但是当一个业务系统,使用多个节点部署组成集群时,所面临的最大问题,即是请求如何分发到具体服务器?

为了解决该问题,不得不引入请求分发器,域名映射请求分发器所在的IP,分发器接到客户端的请求后,再分发给具体的业务节点处理。

当外部请求来到分发器时,分发器可以在已配置的节点列表中选一台机器,负责处理具体的业务请求逻辑。但这里有个问题,如何保证各节点的负载均衡呢?随机分发貌似不太合适,咋办?选择合适的分发算法,如轮询。

所谓的轮询,则是根据配置的节点列表顺序,依次分发请求,如第一个请求给第一个节点、第二个请求给第二个节点......,当分发到最后一个节点时,再回到第一个节点,周而复始。听起来不错对吧?但有个问题,来看例子。

现在有个房子要装修,打电话定了一车水泥,总共六十包,目前有四个人在场:30岁的男人、9岁的儿子、30岁的老婆、60岁的老爹。

这里的水泥就是请求,按照轮询算法,六十包、四个人,你一拍大腿!正好一人十五包,合理不?显然不合理,先不说别的,光看九岁儿子那小身板,像个能抗十五袋的人不?

上述例子,换到集群场景中亦是同理,组成集群的机器有好有差,如果一视同仁,站在那些较差的服务器来说,请求分发的不够合理,因此该如何保证分发的负载均衡?

负载均衡是两个词,负载即服务器目前承担的压力,均衡代表压力一致,组合起来就是指:集群内各节点承担的压力要一致 !相同的访问量,分发到一台2C4G服务器上,CPU利用率经常打到95%;但放到8C16G的机器上根本不是事。

综上所述,负载均衡要考虑各机器本身的性能,这时就得用到一些较为智能的分发算法,如:

  • 平滑加权轮询算法:在轮询的基础上,根据机器配置,为各节点分配权重值;
  • 最小活跃数算法:根据实际负载情况进行调整,自动寻找活跃度最低的节点处理请求;
  • 最优响应算法:根据分发后请求的处理时间,新请求到来时,分发给响应最快的节点处理;
  • ......

这些智能化的分发策略,能综合考虑机器性能、实际负载、响应速度等因素,选择出相对合适的节点处理请求,但这里不做过多展开,感兴趣可参考《网络编程-请求分发篇》

1.3、为什么分发器性能那么高?

业务系统做集群,通常会选择Nginx,毕竟它除开提供负载均衡的能力外,还能做反向代理,避免了将后端服务直接暴露在公网的隐患性,可为什么这类负载均衡器,性能那么高呢?

相同的访问量,Nginx可以轻松抗住,而后端服务或许要起几个才能勉强处理,为啥?其实道理很简单,因为这类负载均衡器,自身并不负责处理请求,只是负责做请求分发,所以用户的请求,在Nginx里逗留的时间极其短暂。

一台机器处理请求的速度越快,在相同的时间窗口里,其吞吐量更高。除此之外,因为不需要处理业务逻辑,自然也不存在资源之类的竞争(如锁资源),并且Nginx底层选用了多路复用模型,实际负责分发请求的线程数极少,也不存在多线程应用那种线程上下文频繁切换的开销......,种种因素下,为其高性能表现提供了强有力的支撑。

1.4、双机热备机制

工作年限较长的一点的小伙伴,应该在之前的招聘需求上,见过这么一条:

"具备集群、双机热备等高可用系统经验者优先......"

其中的双机热备技术是指啥?所谓的热备机制,可以理解成集群技术的另类体现,它是一种系统冗余设计方案,即同时部署两套系统,一主一备,主系统和备用系统并行运行。在主系统发生故障时,备用系统能迅速接管主系统的工作,维持系统正常运作,以确保服务的可用性,及业务的连续性。

可是有了Nginx这类负载均衡器,还要啥热备技术呀?恰巧,就是Nginx这类组件需要热备技术支持,虽然业务系统通过Nginx做了集群化部署,避免单点故障造成系统不可用的风险,但Nginx就成了"咽喉要地",因为它只有一个节点,如果部署Nginx的机器发生故障,就会导致外部流量无法分发到业务系统,造成整个系统瘫痪。

热备机制如何实现呢?可以借助keepalived、TurbolinuxTurboHA、Heartbeat这类专门保障高可用的技术栈实现,建设热备机制时要考虑几点:

  • ①当进程出现故障(内存溢出、进程宕机等)时,热备机制能自动重启服务;
  • ②当部署进程的机器出现故障(断网、停电、硬件损坏)时,备机能及时接替主机工作;
  • ③新主上线接管后,旧主重新启动能自动成为新主的备机,保障热备机制的可持续性;
  • ④出现热备机制无法处理的故障时(硬件问题、机房环境问题等),能及时通知人工介入。

做好上述四点,则代表热备机制较为完善,可具体咋做呢?这里不过多赘述,感兴趣可参考《Keepalived搭建双机热备机制》

PS:除一主一备外,也可以选择搭建双主热备,这样能最大程度利用资源,避免备机长时间处于空闲状态。

二、多形态的存储型集群

在上阶段,我们简单聊了下集群的基本知识,以及快速过了一下逻辑处理型集群的内容,下面重点来看看存储型集群,毕竟这块才是重头戏,集群的形态在其中有着多种多样的变化。

逻辑处理型的应用,部署集群架构是为了解决单点故障、获得更高的吞吐量,集群内各节点之间没有依赖关系,同时遵循着"去中心化思想",即多个节点里没有所谓的"老大"。

反观存储型的应用,需要保存数据、记录会话数据、用户认证信息、事务状态等信息,如果要搭建集群架构,则需要考虑各节点之间的数据一致性和完整性,来看个例子:

假设现在数据库使用三个节点组成集群,如果和业务系统集群一样,多个节点之间地位平等,此时客户端读写数据时,究竟该去到哪个节点呢?包含多个操作的事务如何保证落到同一节点?用户在A节点登录后,如何把登录态(连接信息)同步给B、C节点?

正是因为上述一堆的问题,存储型集群里的节点必须要划分等级,一定要有一个等级更高的节点,来承担客户端的写操作。因此,在很长一段时间内,涉及到数据存储的应用,集群方案都是以主从模式为主。

趣事:主从模式对应的英文是Master/Slave,不过大家会发现,后来许多技术的主从模式,都叫Leader/Follower,这是为啥?因为Master/Slave代表的是"主人/奴隶",再加上集群存在选举机制,所以在西方被痛斥为"此命名的政治思想不正确",于是许多技术栈都经历过"语言净化"(如Redis)......

好了,上面提到主从集群,下面来展开聊聊存储型应用的集群方案,粗分下来也是两大类:

主从模式:集群内的节点区分等级,可以有一或多个主节点,具备完整的读写能力;也允许拥有多个从节点,但能力方面有所阉割,只具备处理读取请求的能力。
分片模式:集群内节点地位平等,各节点都具备数据读写能力,但每个节点只负责一部分数据,完整数据分散在各节点上。

2.2、主从架构

主从集群,这个概念所有人都熟悉,它的身影在各种技术栈随处可见,如主从集群、镜像集群、复制集群、副本集群......,尽管在不同技术栈的叫法不同,可本质上都是同一个东西。

上图是经典的一主多从架构,客户端的写请求,都会落到主节点A上,而后再给同步给B、C这两个从节点,这就是大名鼎鼎的"主从复制技术",数据同步有三种方案:

  • 同步复制:等数据写入集群所有节点后,再给客户端返回写入成功;
  • 半同步复制:等集群内一半数量以上的节点写入数据成功后,给客户端返回写入成功;
  • 异步复制:数据在主节点上写入成功后,立即给客户端返回写入成功。

大多数支持主从复制的技术栈,几乎都上面三种同步方案,从数据一致性角度看:同步复制>半同步复制>异步复制 ,而从性能角度出发则完全相反:异步复制>半同步复制>同步复制

如今大多数技术栈默认的复制方案都是第三种,虽然有数据丢失的风险,可是它的性能最好,何况"主节点写入完数据,正好就发生故障"的这种几率很小。当然,如果你的系统更在乎数据一致性和安全性,那就可以选择半同步模式,如果完全不在乎性能,则可以切换成同步复制模式。

2.2.1、读写分离

在主从架构中,因为从节点被阉割掉了处理写请求的能力,所以在大部分时间里,如果从节点只作为数据副本而存在,显然会造成很大的资源浪费。为了充分利用从节点的资源,一种名为"读写分离"的技术曾风靡一时。

虽然从节点没有处理写请求的能力,可是它会把主节点写入的所有数据都同步过来,为此,它具备"一定程度"的读请求处理能力,此时就可以将读取数据的请求分发给从机,既能充分利用空闲资源,又能减轻主节点的访问压力

PS:从节点只具备"一定程度"的读请求处理能力,注意里面用引号标出的"一定程度",这啥意思?

因为主从复制技术,都依靠网络来同步数据,而网络并不可靠,也必然存在延迟性。在异步复制的模式下,当一个数据写入到主节点,立马出现读请求到从节点读取,此时有可能读不到最新的数据。

2.2.2、故障转移

本文开篇提到的集群四大优势,其中一条则是保证了高可用,解决了单点故障问题,可实际上部分技术栈,虽然支持主从集群,但却只提供了基本的主从复制功能(如MySQL),并不具备故障转移能力。

所谓的故障转移,通常是针对主节点而言的,即:当主节点发生故障时,集群能自动推选出新主节点,并将流量自动转移到新主处理 。大家熟知的MySQL,它并不具备自动故障转移功能,当原本设定的主节点故障后,必须通过人工介入、或第三方工具(如MHA),又或者自己研发的组件实现故障转移。

当然,也有很多技术栈,官方就实现了故障转移机制,比如RedisSentinel哨兵、MongoDBArbiter仲裁者、KafkaController控制器......,这些中间件的主从集群,在主节点发生故障时,都能实现无人力介入、自动转移效果。

这种自动故障转移机制,它们内部是如何实现的?其实原理大同小异,故障转移的第一步,是需要能检测出故障的节点,有两种方案可选:

  • ①探测模式:负责故障处理的节点,每隔一段时间向集群所有节点发送探测包(ping),没有回复的节点说明已经故障;
  • ②上报模式:集群内所有节点,主动向负责故障处理的节点发送存活信息(心跳),当一个节点没有心跳时说明陷入故障。

不过为了兼容网络分区问题,通常会组合起来使用,正常情况下,集群节点主动发送心跳包,当某个节点没有心跳后,故障处理节点会主动发起探测请求,如果还是没有回复,则会认为该节点陷入故障。考虑到网络分区的影响,故障处理节点也可能陷入了分区,因此也会向其他故障处理节点,发出二次确认的请求,以此进一步确认对应节点是否故障。

PS:上述概念类似于Redis哨兵里的主观下线、客观下线,同时也不仅仅只有故障处理节点,具备故障检测能力,集群内的节点也拥有该能力,如从节点去拉数据时,也可以检测出主节点的健康状态。

如果本次检测出的故障节点是主节点,则会进入新主选举流程,选举一般是通过投票机制实现,新主上任的前提是"节点数一半以上的票数";而投票机制的内在逻辑则是数据POS点,即谁的数据更新则更有机会成为新主,总体流程如下:

  • ①故障检测:当感知到某个节点不可用时,会先发起通信向其他可用节点进行二次确认;
  • ②故障处理:如果检测到主节点不可用,从节点会将自己转换为候选人,并向其他成员宣布;
  • ③选举开始:轮次号加一,开启一轮新的选举,每个候选人节点开始向其他成员发送拉票请求;
  • ④投票开始:在一个新的选举轮次中,每个节点只能投一票,可以投给自己或者其他节点;
  • ⑤投票结束:所有节点已投票,或抵达本轮选举的时间限制后,将获得大多数投票的节点立为新主;
  • ⑥主从切换:新主会向其他节点发送"上位"消息,其他节点更新自己的配置,接受新主上位;
  • ⑦数据同步:完成主从切换后,从节点以新主为数据基准,校验自身数据是否完整,有缺失则同步。

上面只是大概流程,但几乎所有技术栈实现的思路都是这样,因为它们都遵循着Paxos、Raft等协议制定的标准,本文对此不做展开,后续会单开篇章来聊,大家对详细过程感兴趣,可参考《MongoDB仲裁机制原理》《Redis哨兵机制原理》

2.2.3、级联复制

级联复制是主从架构的变种,其本质还是主从模型,主要用于一主多从的结构中,如:

在主从架构中,想要实现数据同步有两种方式:

  • 主节点推送:当主节点出现数据变更时,主动向自身注册的所有从节点推送新数据写入。
  • 从节点拉取:从节点定期去询问一次主节点是否有数据更新,有则拉取新数据写入。

但不管是哪种方式,都会存在一个问题:主节点同步数据的压力,会随着从节点的数量呈线性增长!啥意思?因为不管是推还是拉,数据都需要从主节点出站。

对主节点来说,数据同步会对磁盘、内存、带宽、CPU带来不小压力,此时多一个从节点,自然多出一倍的压力,数据量较大时,特别容易把主节点的资源占满,从而造成主节点无法处理客户端的请求,级联复制的出现,就是为了解决此问题。

级联复制模式中,只有一个从节点B挂在主节点A下面,其余的从节点都会成为B的从节点,数据由B先同步到自身,其余节点再从B去同步数据,这样就解决前面的问题,可此时又会带来新问题:

  • 数据延迟:本身主从复制就存在延迟性,而新加入一层后,数据延迟性会更高;
  • 级联节点故障:负责同步主节点数据的B一旦故障,会导致其余从节点无法同步数据。

凡事有利必有弊,虽然级联复制能解决原本的问题,可是也会带来新问题,这时就需要处理新的问题,或者把新问题的影响降到最低。

PS:用来同步数据的Canal中间件,就类似于级联复制的思想。当然,除开小部分特殊的项目,一般很少用到级联复制模式。

2.2.4、多主热备

虽然可以通过读写分离、级联复制,减轻主节点的部分压力,可对于写请求,都必须得落到主节点上处理,而主节点再强悍,总有扛不住的时候,这时又出现了变种集群:双主/多主模式

多主模式也是建立在主从复制的基础之上,比如双主模式时,两个节点互为主从,各自都具备完整的读写能力,客户端的写请求,可以落入任意节点上处理。不管数据落到哪个节点,另一个节点都会将数据同步过去,此时客户端的读取请求,在任意节点上都能读到数据。

通过这种方案,可以将写入性能翻个倍,并且可以继续拓展出更多的主节点,组成多主集群。不过一般是双主,因为大部分技术栈支持的主从集群,仅允许为一个从节点配置一个主节点,在这个限制下,想要实现多主,则只能组成环形多主集群:

这种环形多主架构,由于每个节点都支持写入数据,所以能极大提升写入吞吐量,但成也萧何败也萧何,一个节点写入新数据后,需要经过N次(节点数量-1)复制,才能将数据同步给所有节点,集群内的延迟性很高,这也是为什么一般只搭建双主的根本原因。

PS:多主集群除开能提升写入吞吐量外,只要客户端略加适配,就可以实现"客户端版故障转移",即客户端检测到一个节点不可用后,自动切换到其他节点上读写数据。

2.3、分片架构

不管是开始提到的横向集群,还是上面聊到的主从集群,本质上都是"克隆"多个具备完整功能的单机节点组合部署,尽管主从里的从节点功能有所阉割,可整体上还是属于主节点的复制体。

对业务系统这类应用而言,用多个克隆体组成集群没任何问题,这也是集群概念的标准思想,但对于存储型应用来说,就会出现三个致命问题:

  • 木桶效应:三个节点的磁盘容量为512G、1T、1T,集群最大容量则为512G
  • 写入受限:传统的主从集群,写入的吞吐量受单个主节点限制,写入的并发很难提升;
  • 容量危机:主从集群通过升配存储GB、TB级数据,但上升到PB甚至更高量级时无力。

为了解决木桶效应,必须保证集群所有节点的资源配置一致,否则同步数据时存不下;为了解决写入受限,可以搭建另类的多主集群,可这种模式数据延迟太大;为了存储海量数据,只能不断加硬件资源带来更大的存储空间......

主从集群有着天然的弊端,虽然可以通过特殊手段环境,但无法根治"先天性的劣势",这种传统型的集群方案,不再适用于增长迅猛的系统(无法处理高并发与海量数据存储)。正因如此,存储型应用在设计集群模式时,做出了一个违背祖宗的决定:将数据分区存储,既然一台机子存不下,那我就用多台机子存!

2.3.1、分片式集群

分区存储,就是目前大数据存储领域大名鼎鼎的分布式存储技术,也叫:分片式集群,我们再来好好聊聊这块知识~

之前的单体模式也好,主从集群也罢,数据其实都是集中式存储,即所有数据都存储在一起。分区存储,就相当于把原本的一缸水,分到不同的桶子里,如下:

每个分片节点,都具备读写数据的能力,这样就从根本上解决了写入瓶颈,并且数据分散到多个节点独立存储,这并不受木桶效应的影响,解决了PB级数据存储造成的容量危机。

分片集群的好处很多,但想要实现分片式存储,首先要解决的就是数据路由问题,写入时能自动根据规则落到某个分片节点存储,读取时能精准找到存储的分片节点获取数据。这和前面聊的请求分发算法类似,只不过数据分发时,需要保证同一数据的写入/读取落入相同的节点。

数据路由到具体的分片节点,有两个核心点,一是分片(路由)算法,二是分片(路由)键。数据分发和请求分发不同,因为要保证同一数据读写都在相同节点操作,光靠分发算法无法实现,所以每条数据需要有个标识,这个标识就是路由键,以ID字段作为路由键来举例:

集群有四个节点,选用取模分发算法,根据ID值取模节点数量,计算出数据最终要落入的节点。来看实际过程,假设要操作ID=1这条数据:

  • 写入:路由键ID取模四:1%4,结果为1,数据落入分片节点2
  • 读取:路由键ID取模四:1%4=1,从分片节点2中读取数据。

通过这种方式,就保证了读写数据的节点一致,避免"写入成功,无法读取"的尴尬现象发生。不过除了取模分发外,还有其他一些分发算法,如范围分发、哈希取模、一致性哈希、哈希槽......,像Redis-cluster集群中,就采用CRC16算法计算KeyKey即路由键),并结合哈希槽模式实现数据分发。

解决了数据分发问题后,分片架构其实还存在一系列问题,好比多节点的数据如何聚合等等。如果当前分片的存储组件属于关系型数据库,如何兼容事务机制?多表联查如何关联?如何实现多字段维度查询?当然,其实还有一系列头疼的问题,这里不做展开,感兴趣可参考《分库分表副作用篇》

经过上面的阐述后,大家一定对分片式集群有了基本认知,现在来聊聊分片集群的类型,总体可分两种:中心代理分片集群、去中心化分片集群

2.3.2、中心化分片集群

中心化分片集群,意味着集群中又存在不同的角色节点,最少有两种:

  • 中心节点:负责路由规则、数据节点的管理工作,以及处理数据分发请求,自身不存储分片数据;
  • 数据节点:具体存储数据的分片节点,负责处理中心节点分发过来的写入/读取数据请求。

上面的中心节点,就类似于之前的"负载均衡器",负责管理集群所有节点,以及具体的分发工作,这种模式也是早期最主流的分片集群方案,即:代理式分片集群

代理式分片,可以在官方不支持分片集群的情况下,自己来搭建分片集群,以传统关系型数据为例,MySQL官方并不支持分片存储,怎么办?如果工作年限较长的小伙伴一定清楚,之前有个大名鼎鼎的中间件叫:MyCat,示意图如下:

尽管MySQL不支持分片架构,但通过上述方案,可以将一个个独立的MySQL节点,组合成一个逻辑上的大整体,每个独立的MySQL节点,则代表着具体要存储数据的分片节点。MyCat则是负责节点管理与数据分发的中心节点,所有要操作MySQL的客户端,都会连接到MyCat,读写请求交给MyCat做具体分发。

PS:实际MyCat就是伪装成了一台MySQL,业务系统依旧和往常一样连接即可,屏蔽了客户端对分片集群的感知。

除开MyCat外,如同类型的Sharding-Proxy,又或者早期RedisTwemProxy、Codis等等,这都是代理式分片理念的产物。究其根本,还是由于早期官方并不支持分片式架构,因此部分业务庞大的企业迫于无奈,只能在更高的维度上,架设中心节点来分发数据到不同的节点中存储。

这种中心化分片集群的思想,直到现在依旧在使用,只不过如今很少有代理中间件,基本都由官方自己实现,如MongoDB分片集群,依靠路由节点mongos进行数据分发。

为啥我称呼这类分片集群为"中心化分片集群"呢?其实站在数据/分片节点的角度出发,节点之间相互平等,不存在之前主从架构里的Master概念。可是,虽然没有了Master,但在数据节点之上有了一种更高维度的节点,所有数据节点都得"听"它安排,这就是一种另类的中心化体现。

中心化分片集群有何劣势?特别明显,不管是第三方实现的代理中间件,还是官方自身研发的"高等级节点",毕竟所有请求都需经过"中心节点",为此,一旦这类节点故障,必然会造成整个分片集群不可用。

中心化分片集群,为了解决中心节点的单点故障问题,一般都需要对中心节点做高可用建设,如mongos集群式部署、MyCat做热备等等。

2.3.3、去中心化分片集群

与上阶段提到的中心化分片集群相反的,则是去中心化分片集群,典型的例子就是Redis3.x推出的Redis-Cluster集群,这是一种完全意义上的去中心化集群,所有负责读写数据的节点地位平等,并且没有"中心化"的路由节点。

相反,Redis-Cluster中采用了哈希槽的概念,总计16384个槽位,集群初始化时会平均分给每个节点负责管理(支持手动设置),比如现在有四个节点,哈希槽的默认分配如下:

  • A节点:0~4095槽位;
  • B节点:4096~8191槽位;
  • C节点:8192~12287槽位;
  • D节点:12288~16384槽位;

类似于之前0~1516个默认的库一样,每个槽中都可以存放多个Key,同时配备CRC16哈希算法,最终实现数据的分发,例如下述命令:

  • set name zhuzi

则会使用CRC16算法对name这个键名进行哈希,接着取模总槽数16384,从而得出具体要落入的槽位。假设name对应的哈希值是185272,最终落入槽位则是5047(185272%16384-1),即落入到B节点中存储。

既然没有中心化的路由节点,那CRC16+取模运算的工作谁来完成?集群内的所有节点 !在Redis-Cluster中,任意节点都具备CRC16取模的能力,来看个场景:

如果一个本该落入A节点的Key,在set时去到了C节点,那么C节点在经过CRC16计算后,会不会把该Key转发到A节点去呢?

答案是不会,而是会向客户端会返回一个重定向错误的消息,其中指示了正确的节点位置,告诉客户端去连接对应节点写入数据即可。

PS:实际项目中,并不会把CRC16+取模的工作交给Redis来做,因为这样有可能会出现一次重定向,通常都是客户端计算,而后直连正确的节点写入数据(如Java中主流的Redis客户端框架,都有实现CRC16的逻辑)。

好了,上述便是去中心化的大体逻辑,相信大家一定明白为啥叫去中心化分片集群,因为不存在中心节点,各个节点自身就具备数据分发计算的能力,即使集群内部分节点宕机,也只会影响部分数据,保证了BASE理论中的基本可用思想,并不会像中心化分片集群那样,一旦中心节点故障,就会造成分片集群不可用。

PS:分库分表领域的Sharding-JDBC,也是一种去中心化分片存储的体现,即各个客户端具备数据分发的能力,不需要依赖MyCat这类中间件实现数据路由。

相较于传统的主从集群,分片式集群的维度显然更高,当应对数据量级、并发规模非常大的系统时,分片式集群能够利用多台机器的共同满足数据需求,它能充分利用集群中每一台机器的资源,实现数据读写的并行处理,以及海量数据的分区存储。也正因如此,早期想实现分片式存储,需要自己研发中间件,反观如今,随着分布式各领域技术的发展,分片式集群成为了新趋势,官方基本都开始支持分片架构。

三、集群篇总结

OK,看到这里,我们一步步阐述了集群中的常用知识,也对集群的形态有了全面认知,但本文并没有涵盖集群的方方面面,实际上,不同类型的集群架构,总会带来各种各样的问题,而这些问题并未做展开,也包括集群与云平台的结合,本文也未曾说明,这些就留给诸位自行去探索啦~

相信看到这里的小伙伴,对集群方面的知识应该建立出了系统化、结构化的认知体系,不过经常阅读我文章的小伙伴会发现,本文中的很多概念,在以往的文章里都有提及,看起来会感觉异常熟悉,为啥?这就是我经常说的那句话:技术学到最后都是共通的,不同技术之间最后都能串起来

不管是业务系统,还是注册中心、定时任务、Redis、MQ、MySQL、ES......等各种组件,在实现集群方案时,底层逻辑都大差不差,本文则是各种技术栈集群方面的共通知识,当你在学不同技术栈的集群技术时,就会发现这些技术之间的共通性。

最后,也希望大家不管在学习什么技术,不要只把目光集中在表层的应用,闲暇之余,记得看看内层的实现思想,这样方能做到一通百通,才能让大家真正成为技术领域的"高手"。

PS:后续所有文章会陆续同步到个人公众号:竹子爱熊猫,想在微信上便捷阅读的小伙伴可搜索关注~

相关推荐
Francek Chen4 小时前
【大数据技术基础 | 实验十二】Hive实验:Hive分区
大数据·数据仓库·hive·hadoop·分布式
吾日三省吾码4 小时前
JVM 性能调优
java
弗拉唐5 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi776 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3436 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀6 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20207 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深7 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
shuangrenlong7 小时前
slice介绍slice查看器
java·ubuntu