聊聊分布式系统架构

一、分布式系统的经典基础理论

1、分布式系统设计的两大思路:中心化和去中心化

  • 中心化:中心化的设计思想在自然界和人类生活中是如此的普遍和自然,它的设计思想也很简单,分布式集群中的节点按照角色分工,可以分为两种角色--“领导”和“干活的”,中心化的一个思路就是“领导”通常分发任务并监督“干活的”,谁空闲了就给它安排任务,谁病倒了就一脚踢出去,然后把它的任务分给其他人;中心化的另一个思路是领导只负责生成任务而不再指派任务,由每个“干活的”自发去领任务。
  • 去中心化:全球IP互联网就是一个典型的去中心化的分布式控制架构,联网的任意设备宕机都只会影响很小范围的功能。去中心化设计通常没有“领导”和“干活的”,角色一样,地位平等,因此不存在单点故障。实际上,完全意义的去中心化分布式系统并不多见,很多看起来是去中心化但工作机制采用了中心化设计思想的分布式系统正在不断涌现,在这种架构下,集群中的领导是动态选择出来的,而不是人为预先指定的,而且在集群发生故障的情况下,集群的成员会自发举行会议选举新的领导。典型案例如:zookeeper、以及Go语言实现的Etcd。

2、分布式系统的一致性原理

  • 在说明一致性原理之前,可以先了解一下cap理论和base理论。
  • 对于多副本的一致性处理,通常有几种方法:同步更新--即写操作需要等待两个节点都更新成功才返回,这样的话如果一旦发生网络分区故障,写操作便不可用,牺牲了A。异步更新--即写操作直接返回,不需要等待节点更新成功,节点异步地去更新数据,这种方式,牺牲了C来保证A。折衷--只要保证集群中超过半数的节点正常并达到一致性即可满足要求,此时读操作只要比较副本集数据的修改时间或者版本号即可选出最新的,所以系统是强一致性的。如果允许“数据一致性存在延迟时间”,则是最终一致性。
  • 如Cassandra中的折衷型方案QUORUM,只要超过半数的节点更新成功便返回,读取时返回多数副本的一致的值。然后,对于不一致的副本,可以通过read repair的方式解决。read repair:读取某条数据时,查询所有副本中的这条数据,比较数据与大多数副本的最新数据是否一致,若否,则进行一致性修复。此种情况是强一致性的。
  • 又如Redis的master-slave模式,更新成功一个节点即返回,其他节点异步地去备份数据。这种方式只保证了最终一致性。最终一致性:相比于数据时刻保持一致的强一致性,最终一致性允许某段时间内数据不一致。但是随着时间的增长,数据最终会到达一致的状态。此种情况只能保证最终一致性。著名的DNS也是最终一致性的成功例子。
  • 强一致性算法:1989年就诞生了著名的Paxos经典算法(zookeeper就采用了Paxos算法的近亲兄弟Zab算法),但由于Paxos算法难以理解、实现和排错,所以不断有人尝试优化算法,2013年终于有了重大突破:Raft算法的出现,其中Go语言实现的Raft算法就是Etcd,功能类似于zookeeper。
  • Base的思想:基本可用、柔性状态、最终一致性,主要针对数据库领域的数据拆分,通过数据分片(如Mycat、Amodeba等)来提升系统的可用性。由于分片拆分后会涉及分布式事务,所以接下来看一下如何用最终一致性的思路来实现分布式事务,也就是柔性事务。

3、柔性事务:

如何保证事务一致性的问题,目前并没有完美的解决方案:

  • 传统数据库的事务非常好的保证了业务的一致性,无论是单数据库的事务机制,还是分布式事务的两阶段提交机制,都在互联网场景下就暴露出数据库性能和处理能力上的瓶颈,效率不够高。
  • 采用CAP、BASE理论的柔性事务,在保证可用性、一致性的前提下,达成“最终一致性”。

CAP理论

  • 一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
  • 一致性:所有节点在同一时间的数据完全一致。
  • 可用性:用户在访问数据时可以得到及时的响应。
  • 分区容错性:指分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。
  • 强一致性的锁机制带来了数据层的性能瓶颈,可用性会严重影响用户体验,分区容错性影响系统的横向扩展能力。
  • 实际上分区容错性是分布式系统的固有属性,所以基本上我们在设计分布式系统的时候只能二选一:要数据一致性(C)还是系统可用性(A)?
  • 当集群中的节点数量持续增加时,一致性的成本非常高,所以很多时候只能选择可用性而放弃强一致性(当然提高可用性也意味着商业上少损失money)。Zookeeper、Hadoop之所以选择强一致性,是因为这些系统的节点相对来说还是少量。

BASE理论

  • eBay架构师Dan Pritchett对于大规模分布式系统的实践总结:B(Basically Available)、S(Soft state)、E(Eventual Consistency)。
  • 基本可用:指分布式系统在出现故障的时候,允许损失部分可用性,保证核心可用。
  • 柔性状态:指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。比如,允许不同节点间副本同步的延时就是柔性状态的体现。
  • 最终一致性:指系统中的所有副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
  • 互联网应用的最核心需求是高可用性,高可用性=分布式系统=高性能。

传统分布式事务机制:

  • 两阶段提交:第一阶段是准备阶段,第二阶段是提交阶段,如果有任意一个参与者在第一阶段响应“未准备好”,则整个事务回滚;如果所有参与者在第一阶段都回答“就绪”,那这些资源就在第二阶段提交。
  • X/Open组织为基于两阶段协议的分布式事务处理系统提出了标准的系统参考模型(X/Open事务模型)以及不同组件间与事务协调相关的接口,使不同厂商的产品能够互操作。

柔性事务如何解决分布式事务问题:

  • 引入日志和补偿机制:通过日志来保证柔性事务的原子性,事务日志记录事务的开始、结束状态,可能还包括事务参与者信息,参与者节点也需要根据重做或回滚需求记录REDO/UNDO日志,当事务重试、回滚时,可以根据这些日志最终将数据恢复到一致状态。在互联网业界采用日志方式实现柔性事务的比例非常大,但因为这部分技术的实现并没有如XA这样的技术标准和规范,很多应用对这部分的实现非常的粗糙。
  • 可靠消息传递:由于“网络通信危险期”的存在,消息投递只有两种模式:1)消息仅投递一次,但是可能会没有收到;2)消息至少收到一次,但可能会投递多次。在业务一致性的要求下,只能选择第二种投递方式,由于可能重复投递这就要求消息处理程序必须实现幂等(即同一操作反复执行多次结果不变),这一要求跟传统应用开发相比是非常具有互联网特征的一种模式。幂等的实现方法--也是eBay给的一个简单思路,根据msg_id在目标端是否存在来判断是已经处理过。
  • 实现无锁:1)避免事务进入回滚,由于事务不会回滚也不会导致脏读;2)辅助业务变化明细表,比如对资金或库存进行增减处理时,可采用记录这些变化的明细表的方式,避免所有事务均对同一数据表进行更新操作,造成数据访问热点,同时使得不同事务中处理的数据互不干扰,实现对资金或库存信息处理的隔离;3)乐观锁,比如通过在资金表中增加版本号,在事务开始前获取版本号,在事务开始后对资金进行数据更新时可通过在执行最后的修改update语句时进行之前获取版本号的比对,如果版本号一致则更新否则放弃当前事务。

4、分布式系统的关键Zookeeper

  • 目标是解决分布式系统的几个问题:集群集中化配置,集群节点动态发现机制,简单可靠的节点Leader选举机制,分布式锁。
  • ZNode有一个ACL访问权限控制列表,提供对节点增删改查的API,提供监听ZNode变化的实时通知接口--Watch接口。
  • ZNode类型:持久节点(可以实现配置中心)、临时节点(和创建这个节点的客户端会话绑定,可实现集群节点动态发现,可以实现服务注册中心)、时序节点(创建节点时会加上数字后缀,通过选择编号最小的ZNode可以实现Leader选举机制)、临时性时序节点(同时具备临时节点和时序节点的特性,主要用于分布式锁的实现)。

二、分布式系统架构的主要内容

分布式系统架构的主要内容包括:

  • RPC和对象序列化
  • 分布式内存缓存技术、分布式内存计算
  • 分布式存储
  • 分布式计算
  • 全文检索
  • 消息队列
  • 容器

1、RPC和对象序列化

  • RPC设计的初衷是设计一套远程通信的通用框架,这个框架能够自动处理通信协议、对象序列化、网络传输等复杂细节,让开发者调用远程方法跟调用本地方法“看起来没什么区别”。
  • Java里经典的RPC实现方案是RMI,仅支持Java语言的客户端调用。
  • 人类历史上,支持多语言通信的第一次伟大尝试造就了功败垂成的CORBA技术,1991年CORBA1.1诞生,直到1994年底才完成了CORBA2.0规范。
  • CORBA失败的原因可能包括:规范巨大而复杂,很难学习,开发过于复杂使得CORBA程序员稀缺,商业CORBA费用昂贵,很多公司开始转向Web浏览器、Java和EJB的电子商务基础设施等。同时,XML技术的兴起加速了CORBA的没落,SOAP使用XML作为RPC新的对象序列化机制,IBM则发扬光大推出WebService整套方案。
  • SOAP严格意义上属于XML-RPC技术的一个变种,SOAP是第一次真正成功解决了多语言多平台支持的开放性RPC标准。
  • 但是SOAP报文复杂而且编码臃肿,由于它是面向机器识别的表达格式,最终导致了基于XML的SOAP协议和其上的WebService框架的末路,导致了基于JSON简单文本格式编码的HTTP REST通信方式的兴起。
  • HTTP REST慢慢侵占了RPC大部分领地,并导致了一度盛行的XML-RPC的灭绝;同时促进了正统RPC技术走向一个新的发展阶段,追求更高的性能及增加多语言多平台的支持,成为越来越多开源RPC框架的目标,比如Thrift、Apache Avro等开源框架。
  • 当年CORBA墙倒众人推时,最初参与CORBA的一帮技术专家另起炉灶打造了延续至今的RPC之王--ZeroC Ice,作为RPC领域的王者,ICE已经发展成一个很强大的微服务架构平台,在RPC通信领域里,性能第一,稳定性第一,多语言多平台支持。
  • 与一般的HTTP REST框架不同,一个可用的RPC框架不仅解决了远程调用问题,也提供了用于服务注册和服务发现的基础设施,比如RMI里的RMI Registry。服务注册、服务发现和服务监控后来成为通用分布式系统架构的核心和关键技术基础,也被赋予一个新概念--“服务治理框架”,最早的说法可能来自BAT的一些架构师。比如Dubbo就具备服务治理的能力,同时dubbo还提出了服务编排、服务降级、访问规则控制等,但实际上作为分布式系统最核心的仍然是:稳定、高性能的RPC通信及多语言支持。
  • 如果一个分布式系统具备如下特点,则可以称之为“微服务架构”:1、任何一个服务都由多个独立的进程提供服务,这些进程可以分布在多台物理机上,任何进程宕机都不会影响系统提供服务;2、整个系统是由多个微服务有机组成的一个分布式系统,换而言之,不是一个巨大的单体应用。
  • 当前主流的微服务架构可以分为三类:1、基于传统高性能RPC技术和服务治理的微服务框架,这个领域的王者是ZeroC IceGrid;2、以HTTP REST为通信机制的通用性微服务架构,最典型的为Spring Cloud;3、基于容器技术,没有提过特定的RPC通信机制,理论上任何分布式应用都可以运行在微服务架构平台上,言外之意就是要选择合适的通信协议,比如REST、Thrift、gRPC等,这个领域的王者是Google的Kubernetest。
  • 微服务架构下,是否还需要Spring Framework?是否还需要MyBatis、Hibernate等数据映射框架?这个问题的答案不是特别确定,但有不少人倾向于不再使用这些框架来开发微服务,理由如下:1、微服务系统通常很多时候是互联网应用,CRUD操作少,查询操作多,因此Mybatis和Hibernate框架的优势不明显;2、微服务是系统中的重要骨干,开发速度相对于微服务的实现品质来说并不重要;所以不少人倾向于直接采用原生JDBC来实现微服务的业务代码。
  • 在对象序列化这块,JSON虽然是简单文本格式编码,但存在占用空间大、性能低下等特点,于是与语言无关的高效二进制编码协议成为热点技术之一。首先诞生了开源二进制序列化框架--MessagePack,是模仿JSON设计的一个高性能二进制的通用序列化框架。除了MessagePack,Google开源的多语言支持的Protocol Buffers编码协议也是这方面的代表作品。在Protocol Buffers之后,Google又开源了FlatBuffers,在性能、序列化过程中内存的占用大小、第三方依赖库的数量、编译后生成的中间代码数量等方面都做了大幅改进。Apache也发布了多语言的顶级RPC项目Apache Avro。

2、分布式内存缓存技术、分布式内存计算

  • 缓存系统常用的缓存淘汰策略:1、Least Frequently Used(LFU)策略--计算使用频率,优先淘汰最不常用的缓存条目,CPU的cache所采用的淘汰策略即为LFU策略;2、Least Recently Used(LRU)策略--淘汰最近最少使用的条目;3、Adaptive Replacement Cache(ARC)策略--该策略由两个LRU组成,第1个LRU包含的条目是最近只被使用过一次的条目,第2个LRU包含的是最近被使用过二次的条目;4、其他还有一些基于缓存时间的淘汰策略,比如淘汰存活时间超过5分钟的缓存条目。
  • 分布式缓存都采用Hash算法进行数据分片,将数量庞大的缓存项均匀分布到集群中的每个节点上,比如Redis3.0开始实现的分布式集群功能就采用了Hash算法,将缓存项均匀分布到16384个Slot上去。以Redis2.x为基础改造的Codis是国内分布式缓存开源的一个典范,出自豆瓣网。Memcache本身并没有提供集群功能,但很多客户端Driver实现了Hash算法分配逻辑,因此也可以看成是一种分布式缓存的解决方案。
  • 内存计算产品:商业的SAP Hana、开源的VoltDB等。VoltDB是一种开源的高性能的内存关系型数据库,提供社区版和商业版,是一种NewSql,是一个借鉴并基于HSQL的分配内存数据库集群。

3、全文检索

  • Lucence Core:Java编写的核心类库,提供全文检索功能的底层API与SDK。
  • Solr:基于Lucence Core开发的高性能搜索服务,提供了REST API的高层封装接口,还提供了一个Web管理界面。
  • PyLucene:一个Python版的Lucene Core的高仿实现。
  • ElasticSearch:也是基于Lucence的分布式全文检索中间件。

4、消息队列

  • 第一代消息队列:J2EE时代的产物,强调企业级特性,比如消息持久存储与事务的要求,都遵循JMS规范,最著名的是开源Apache ActiveMQ。虽然当前基于J2EE架构的企业软件少了,但依然有不少商业软件仍然采用了企业级的J2EE架构。值得一提的是,ActiveMQ Artemis是ActiveMQ的下一代产品,它已经融合了多种MQ的特性,成为Java领域无法超越的MQ之王。
  • 第二代消息队列:制定了一个开放性、免费的消息中间件协议AMQP标准,第一个也是最重要的开源AMQP消息中间件产品RabbitMQ,同时ActiveMQ目前也支持AMQP协议,Apache还专门开源了Qpid这个基于AMQP协议的消息中间件产品。
  • 第三代消息队列:分布式系统设计理念,采用Zookeeper实现去中心化的集群管理,以Kafka为代表。

5、微服务架构

  • 当前主流的微服务架构可以分为三类:1、基于传统高性能RPC技术和服务治理的微服务框架,这个领域的王者是ZeroC IceGrid;2、以HTTP REST为通信机制的通用性微服务架构,最典型的为Spring Cloud;3、基于容器技术,没有提过特定的RPC通信机制,理论上任何分布式应用都可以运行在微服务架构平台上,言外之意就是要选择合适的通信协议,比如REST、Thrift、gRPC等,这个领域的王者是Google的Kubernetest。
  • 微服务架构的项目在实施过程中经常需要考虑的问题:引入自动化工具与集中运维管理工具,用于程序的编译打包、自动化部署和升级等工作;需要研究、测评大量相关开源工具并引入微服务架构中,原因是之前的很多中间件工具只适合于单体应用;团队重构,包括展现层和微服务层,建议团队中的骨干技术人员成为微服务层的开发主力;高质量的文档。
  • 基于消息队列的微服务架构:网易的蜂巢平台采用了基于消息队列的微服务架构,但基于消息队列的微服务架构案例少,没有知名的开源平台,因此实施成本高、风险大。

欢迎点赞+收藏

欢迎转发至朋友圈

原文发布于微信公众号 - 暴走大数据(zhouqiantanxi)

原文发表时间:2019-09-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券