分布式系统系列

 

如果有人说分布式系统不难,很可能是他还不知道自己不知道。分布式系统是十分复杂的,实现一个分布式系统要解决的问题很多,因此对分布式系统要心存敬畏。。

 

个人经验总结

分布式系统的类型

离线分布式系统,一般与线上业务无关,如批处理,大数据分析平台等。

软实时分布式系统,宕机几分钟或几个小时不影响线上业务,如索引生成器。

硬实时分布式系统,也称为 请求/响应 服务,如 web 前端服务器,交易系统。最常见

一般“硬实时”要求在 200~500ms 内一个请求要返回,其它行业的业务可能要求更宽一些。一般硬实时系统跟终端用户直接相关,因此要求有很高的容错性。

 

分布式和集群的区别与联系

区别:

分布式:一个业务分拆多个子业务,部署在不同的服务器上(将不同业务放在不同地方);
集群:同一个业务,部署在多个服务器上(几台服务器集中在一起,实现同一业务,一个节点挂了,另一个能顶上)。

如一个任务包含10个子任务,每个子任务处理需要1小时,则在单台服务器上处理一个任务需要10小时。若用10台机器组成一个分布式系统,每个机器负责处理一个子任务,则分布式系统一个任务需要1小时;若用10台机器组成一个集群,每个机器负责处理一个完整任务,则每台机器处理一个完整任务需要10小时,若10个服务器同时工作10个任务同时到达,则处理完10个任务需要10小时,平均每个任务1小时。可见,当横纵坐标分别为任务数、处理耗时时,分布式、集群分别为 y=x、y=10* (1+x/10),即分别为线性函数、分段函数且前者耗时小于后者;而单节点串行的函数是 y=10x,显然分布式和集群都可提高处理效率。

从这看来,分布式和集群的作用都是提高并发处理能力,不同在于分布式是任务内(拆分的子任务间)的并行、集群是任务间的并行,故分布式提高单任务的处理速度、集群提高大批量任务的处理速度

联系:分布式中的每一个节点都可以做集群;而集群并不一定是分布式的,如单节点的Memcached冗余部署三套就不算是分布式的、Redis 主从复制部署架构也不是分布式的。

 

服务的可扩展模型

 《架构即未来》一书提出了一种可扩展模型,名为“AKF扩展立方体(Scalability Cube)”,该模型可用于指导微服务定义、服务切分、产品扩展等。这个立方体有三个轴线,每个轴线描述扩展性的一个维度。详情参阅此文章可理解为就是从【架构设计、业务类型、一业务内的数据】三个维度的扩展

X轴 —— 代表无差别地克隆服务和数据,工作可以很均匀地分散在不同的服务实例上。服务的集群部署属于此维度,可分为主备模型、主从模型、主主模型等。

非对等架构:

主从模型:主者提供写、从者只提供读。MySQL、Redis等数据库的主从架构读写分离常用此方案。

主备模型:主、备提供的服务无差别,但主者提供服务、备者不提供服务而是随时待命,等主挂了从备中选出一个新主顶上来提供服务。分布式系统中一个节点采用的集群部署通常用此方案,如Kafka的partition replication。

对等架构:主主模型:所有服务提供的服务无差别且都对外提供服务,一视同仁。一个微服务在部署时通常用这种方案;分布式系统中采用quorum机制进行读写的系统通常是这种模型,(因为强一致性要求确保写完一定个数副本才返回,quorum机制可确保不用所有副本都写),例如强一致性的分布式协调服务(例如Consul、Zookeeper)

主从模型主要用在读写分离场景,主主模型+主备模型 主要用在分布式系统中,如Redis Cluster模式

对等模式和非对等模式相比的一个优点是扩展方便不用迁移数据,除非需要进行负载均衡

Y轴 —— 关注应用中职责的划分,比如数据类型,交易执行类型的划分。分布式系统中不同节点承担不同的业务(如微服务划分)属于此维度。

Z轴 —— 关注服务和数据的优先级划分。如分地域划分、Redis 的集群模式中按数据的哈希值进行分散存储。

 

提高数据写磁盘效率的措施

为提高数据持久化效率,措施有:

1 架构/数据分区使得可并行化,包括:多节点并行、节点内数据分区并行。例如MySQL的分库、库内分表、表内分区本质是就是这种,HBase数据的HRegion、Kafka topic分partition等都是这种。

2 利用内存,包括利用内存来延迟持久化、批量持久化。

延迟持久化——WAL:写内存+写日志,即数据写内存就返回客户端说已经持久化完成了,为免内存数据丢失还写log。比如MySQL bin log 和 redo log 写时的两阶段提交。

批量持久化——内存缓冲再批量写。比如MySQL redo log 的写,再比如LSM tree的内部原理。

3 写磁盘的策略:append(追加写)、append only(只写不删)、零拷贝等。比如 Kafka Topic 数据的内部写用到了两者,HBase内部一个HFile内数据是按key有序的且append only。

数据多版本策略:数据再内部存储上有版本标识,使得可只增不减,从而可以纯append only。

可以说是万变不离其宗,分布式系统中几乎都会有这些策略,特别是后两类。

常说Kafka吞吐量很高、HBase适合海量数据写入,其背后其实就是因为采用了上述策略

 

WAL(Write-Ahead Log)技术

WAL(Write-Ahead Log),预写式日志。

为提高读写效率,写数据时通常不立即持久化而是只写内存就返回,由数据系统内部去异步持久化。这样对数据系统的使用者来说读写内存即可,从而效率很高。但此时有个问题:数据系统服务器故障会导致内存数据丢失,如何解决?写内存的同事以append only的方式写日志,客户端请求写数据时,服务端先写内存再写日志完成后就返回响应给客户端。这就是所谓的WAL(Write Ahead Log)技术,该技术在分布式系统中很常用,例如:MySQL的redo log,Zookeeper数据的持久化,HBase 一个列族存在一个Store内、在内部先写MemStore 再写HLog 等。

当然,同样地也可对日志写进一步优化,即先写内存、攒一定量后再持久化。持久化的策略:达到一定量持久化、后台开线程周期性持久化等。

MySQL的数据写实际上就是上述两种方式的结合,详情可参阅 MySQL的日志原理-MarchOn

 

数据多版本技术

很多数据库或分布式系统中都采用数据多版本策略,这样虽然对使用者来说数据有删除功能但实际上数据在系统内部是只增不减的!!!比如 Git 内部数据的管理、HBase 每次增删改数据都是生成一个新timestamp的数据、MySQL 的MVVC 利用undo log等。

这种策略主要有两个目的或优势:

1 提供历史数据追溯功能。例如 Git 的增删改在内部都是生成新的 blob/tree/commit 对象而不会真正删,HBase类似。

2 提高数据增删改的效率,因为内部只增不减就不用去做数据删除后的数据移动等操作了。比如 HBase 每次增删改数据都是生成一个新timestamp的数据,正因此故适合大数据量的频繁更新。 

 

分布式系统或集群中的一些问题

借助网络通信的问题

分布式系统中涉及到多个节点,节点间通过网络通信。对于两个节点来说,一次网络通信至少有8个步骤,每个步骤都可能出错(参阅文章 分布式系统相关挑战):

 

  1. POST REQUEST:Client 将消息放到网络上,过程中可能因为网络问题或 SERVER 拒绝而发送失败
  2. DELIVER REQUEST:网络将消息发到 Server,可能 Server 接到消息立即崩溃而失败(Server 没收到)
  3. VALIDATE REQUEST:Server 验证请求有效性,可能由于数据包损坏,版本不兼容或其它错误而失败
  4. UPDATE SERVER STATE:Server 根据请求更新内部状态,可能由于 Server 内部问题出错
  5. POST REPLY:Server 向网络发送响应,可能由于网卡或其它问题导致失败
  6. DELIVER REPLY:即使网络在上面的步骤中正常工作,这时也可能无法正确将响应传递给 Client
  7. VALIDATE REPLY:Client 验证响应的有效性,Client 可能判定响应无效
  8. UPDATE CLIENT STAT:Client 根据响应更新自己的状态,可能由于消息不兼容或其它问题而失败

 

 

 

 

脑裂问题(brain split)

(参阅这篇文章

分布式系统或集群中通常涉及到leader选举问题,出现多个leader的情形就称为脑裂。

例子:出现network partiton的情形——比如一个系统的若干个节点分布在两个机房(比如Consul就支持多数据中心),接下来由于某种原因两机房不能互相通信,此时两机房内会分别进行leader选举从而产生两个leader,一个集群就变成了两个。由于原本的一个集群变成了两个,都对外提供服务。一段时间之后,两个集群之间的数据可能会变得不一致了。当网络恢复时,就面临着谁当Leader、Follower连哪个leader、数据怎么合并、数据冲突怎么解决等问题。

network partition前:,network partition后: 

过半原则(Quorum 机制)

在leader选举中获得超过系统半数节点投票的节点才可成为leader(注意这里总节点数包括挂掉的节点而不单指剩余存活的节点)即决策意见的两派中一派比另一派人多,Zookeeper底层源码的过半机制:

public class QuorumMaj implements QuorumVerifier {
 
    int half;
    
    // QuorumMaj构造方法。
    // 其中,参数n表示集群中zkServer的个数,不包括观察者节点
    public QuorumMaj(int n){
        this.half = n/2;
    }

    // 验证是否符合过半机制
    public boolean containsQuorum(Set<Long> set){
        // half是在构造方法里赋值的
        // set.size()表示某台zkServer获得的票数
        return (set.size() > half);
    }
}
View Code

该机制的作用:

1 防止网络分区时脑裂现象——使得在网络分区的场景下整个系统中选出的新leader数要么为1要么为0,若选不出新的则系统不可用,比如上述图中的场景。即整体系统要么可用要么不可用,不会出现一部分可用一部分不可用 或者 两部分分别各自可用的情形。

2 实现快速的选举——因为过半机制不需要等待所有节点都投了同一个节点就可以选举出一个Leader,所以也叫快速选举算法。

3 不仅在leader选举,在强一致性场景下的读或写时通常也遵循quorum机制,优点是读写节点的个数尽可能少。比如9个节点的系统每个节点数据冗余6份,在写时写9/2+1=5份即可立马返回给客户端写入成功,剩下的1份让后台异步去同步即可。

leader假死(新旧leader争夺)

原leader假死接着选出新leader然后原leader复活,此时原leader就不会有“实权”了,因为系统会用值递增的epoch变量表示当前是哪个leader的统治期,选出新leader后产生新epoch、各follower只认新的epoch、epoch小于现任leader epoch的请求都会被拒绝。

假设某个Leader假死,其余的followers选举出了一个新的Leader。这时,旧的Leader复活并且仍然认为自己是Leader,向其他followers发出写请求也是会被拒绝的。

因为ZooKeeper维护了一个叫epoch的变量,每当新Leader产生时,会生成一个epoch标号(标识当前属于那个Leader的统治时期),epoch是递增的,followers如果确认了新的Leader存在,知道其epoch,就会拒绝epoch小于现任leader epoch的所有请求。

那有没有follower不知道新的Leader存在呢,有可能,但肯定不是大多数,否则新Leader无法产生。ZooKeeper的写也遵循quorum机制,因此,得不到大多数支持的写是安全的,旧leader即使各种认为自己是Leader,依然没有什么作用。

节点数为什么是奇数且不少于3

在Quorum机制下,若做不到少数服从多数(例如选不出leader)则系统不可用。故在能做到少数服从多数的前提下n个节点时的最多允许同时挂的节点数(即容忍度)为n-(n/2+1),发生在投票意见相反的两派中一派比另一派多一个节点的情形:

2个节点时最多允许挂(容忍度)0个节点

3个节点时最多允许挂(容忍度)1个节点

4个节点时最多允许挂(容忍度)1个节点

5个节点时最多允许挂(容忍度)2个节点

6个节点时最多允许挂(容忍度)2个节点

为何至少3个?因为少于3时无容忍性。因为做一个决策(主节点挂了需要进行leader选举等)通常要求少数服从多数,最极端的场景是决策意见相反的两方势均力敌(双方都是n个),为了达到少数服从多数,一方至少要多一个1,故总结点数至少要n+n+1=2n+1个,因n是正整数,故总节点数须至少3。

为何是奇数?可见2n+1、2n+2个节点时容忍性一样,为了节省资源、为了更加高效(尽可能少节点参与选举和通信)当然少部署一个节点好。

 

冗余的作用和带来的问题

分布式系统中,节点或数据冗余是实现系统高可用性、数据可靠性的重要手段,因为这样就允许系统有一定的节点或一定的数据损坏。有两类运行模式(可参阅前面服务扩展模型的X轴扩展一节):

对等模式:即主主模型。各节点地位对等、各数据副本低位对等。这种模式下通过quorum机制进行冗余数据的读写,即只要读写到众数节点或副本中即可而不用全部,因此增加节点时不需要复制数据。比如Consul、Zookeeper中的多节点。

非对等模式:各节点(各数据副本)中有些是leader有些不是。此模式下的冗余使得节点挂了备用节点可顶上、数据丢了备用节点的数据还在。这些冗余节点组成了集群,且通常是主从模式。其运行模式通常有两种:

  主从模型:一种是主可写从可读。比如MySQL主从复制、Redis主从复制架构。

  主备模型:另一种是主可读写从不可读写。比如Kafka 的 partition replacation,前面Redis 分布式模式下主从的关系。这种模式在分布式系统中很常用,从节点不提供读写,只作为主节点的备份,在主节点故障时顶上发挥作用。

比如Kafka中一个partition的多副本非对等且采用主备模型;而其改进者PulSar则采用对等模式,通过quorum机制进行数据读写。

对等模式和非对等模式比,优点之一是扩容时数据迁移更方便。采用对等模式还是非对等模式,很大程度上取决于分布式系统CA的选择,即是否支持强一致性。

非对等方案下节点冗余实现,必须思考的问题有:主节点选举、不同节点间的数据一致性等。

 

分布式系统或集群开发的一些共性

通过笔者自己的实践以及对Redis、MySQL、Kafka、JStorm、HBase等的了解后,觉得有至少有如下共性(不定期补充):

架构设计:分为master、worker、config server、client角色。

master 为管理节点,负责系统节点监控、管理、协调,读写请求调度、数据负载均衡等。

有的分布式系统中会有明确的管理节点,只负责管理不负责业务数据管理(后者是下面说的worker节点的任务);而更多的是将master的功能集成到worker节点中,这使得在使用者看来系统中的各节点都是平等的。比如Kafka:虽使用上看起来各Broker平等,但实际上内部会从众多Broker节点中选出一个作为Controller来额外负责管理任务。

worker 为干活节点,负责读写数据。

config server(zk等协调服务实现)负责存系统元数据,包括节点列表及节点地址、数据分区信息、数据副本leader/follower信息等。

client

通过zk获取元数据然后直接和master或worker交互,所谓的“客户端缓存加速访问”;例如Kafka中Producer内部就维护者集群中的分区信息,其发送数据时内部就是直接与目标topic下的leader partition交互。

面试题:怎样在有节点挂时尽快让client收到变更消息?适当减少心跳周期(周期到底要多少实际上不好确定)、节点graceful shutdown。

  另一方面,为了减少IO,client通常会缓存待发送数据到一定量后才真正批量发送。

CAP理论值的CA选择——系统可用性和数据一致性的选择:由于节点增减会涉及到数据在节点间迁移,迁移过程中是否允许对这些数据的查询?是则数据可能不准确、否则系统表现为短暂不可用。选哪者视系统设计时所针对的应用场景而定,例如zk是强一致性的故可能会短暂不可用、笔者曾经的分布式索引是选了可用性和最终一致性。

CAP理论值的P——数据分区:分布式系统的一个本质特征就是通过分区来让多节点来分别管理一部分数据,从而提供更大数据容量管理、提供多节点并行处理能力(分区的作用)。很自然的,数据如何分区是很重要的问题,分区策略应该考虑是否容易实现、数据是否均匀分区、数据倾斜下的负载均衡等。BTW,索引本质也是对数据的分区。

冗余——系统高可用和数据可靠性主要通过冗余实现,要么节点冗余、要么内部的数据partition冗余、甚至兼而有之。冗余方案通常有多副本、EC算法。运行模式上可以是:

主主模型对等模式。此模式下无需leader选举,通常借助quorum机制实现数据读写,用NRW理论来确保只要若干节点即可保证数据被读到。强一致性的分布式协调服务(Consul、Zk)是这种模式。

也可以是主从、主备模型这样的非对等模式。非对等模式下的可扩展性(节点冗余)实现须思考的问题有:主节点选举、不同节点间的数据一致性等

注:不同的CA选择会对冗余方案实现产生影响。选择强一致性时常用主主模型(如Consul、Zk)、弱一致性时(如Kafka、HBase、Redis Cluster等很多分布式数据库)常用主从或主备模型

数据模型及内部表示。比较关键的是,数据再内部表示上是否是多版本的,这关系到是否可有版本追溯、是否可append only写入。

安全。认证、鉴权等

运维管理。机器多、服务多、配置多,如何有效管理(部署、更新、排查、监控等)

做好前三者,一个能跑的分布式系统原型就差不多出来了。

上述架构的一些问题:

20230110 jerry.ai系统设计面试所问:

哪些会成为瓶颈?config server、master 这些角色。
client得到的目标节点信息滞后怎么办?Client(减少心跳检测周期、超时重连)、Server(节点graceful shutdown即直到告诉完config server自己要下线且所有client请求结束才关闭)。

 

数据存储系统的发展方向

回顾数据存储的两个“极端”发展方向:加快读(加索引如B+ Tree、Hash Index等。通常是OLTP如RDBMS)、加快写(纯日志型,不加索引,数据以append方式追加写入。通常是OLAP如NoSQL大数据存储)。

也有两者的结合,例如:

HBase 是适合写多读少的数据库,写入性能非常高。属于日志型,但其除了支持全表扫描还支持单数据查询和范围查询,就是通过将数据按RowKey分区来实现的。

更典型的是LSM Tree,其以第二种为基础再结合了第一种,其目标在于在尽可能保证高写入性能的同时提高查询性能。实际上,HBase、Cassandra、SQLite、Mongodb等内部都用了LSM tree

详见 LSM Tree简介-MarchOn

 

数据存储系统的架构类型

依据并行处理能力的支持能力进行划分,有:

Share Everything:单机,共用 Disk、Memory、CPU,并行处理能力差。典型的是 SQL Server

Share Disk:共享 Disk,各处理单元有自己的 Memory、CPU,在存储接口能力饱和前可通过增加节点来提高并行计算能力(仅计算,存储无)。典型的是Oracle Rac、HBase。后者是基于HDFS存储上构建了数据划分策略和划分元数据的管理,即在HDFS之上实现计算节点,故属于 Share Disk。

Share Nothing:无任何共享,各处理单元有自己的 Disk、Memory、CPU,可通过添加机器无限扩展并行能力。数据库架构中常用此模式,通过对数据进行逻辑上的水平分割,将数据划分到不同节点上分别独立管理,通常被成为数据库Sharding

 

 

分布式系统相关理论

专题,见 https://www.cnblogs.com/z-sm/category/758853.html

分布式系统架构设计遵循的三个原则:

高性能:相关指标为吞吐量 thoughput、时延(响应时间)lantency,通常前者用平均值、后者用TP分位点。例如 1 秒可处理 10W 并发请求,90%的请求接口响应时间少于 5 ms 等。注意,这里的吞吐量和时延是指请求成功下的指标(如果连请求都失败了,还谈吞吐量或时延有何意义)。

高可用:相关指标为平均故障间隔(Mean Time Between Failure)、平均故障恢复用时(Mean Time To Repair),前者可理解为系统连续正常运行的平均时间, 可用性(Availability)= MTBF / (MTBF + MTTR) * 100% ,故可用性刻画的是系统可连续正常工作的时间比例。例如若系统在每小时崩溃1ms,那么它的可用性就超过99.9999%。

易扩展 :系统在迭代新功能时能以最小的代价去扩展;系统遇到流量压力时可以在不改动代码的前提下去扩容系统。

其他:

可靠性,包括系统可靠性、数据可靠性

系统可靠性刻画的是系统发生故障的频繁程度。例如若系统在每小时崩溃1ms,那么虽其可用性超过99.9999%但系统高度不可靠;而若系统从来不崩溃但每年停机两周,则其是高度可靠的但可用性只有50/52≈96%。

数据可靠性刻画的是数据丢失或多副本数据不一致的可能性。

容错性

 

接触过的分布式系统/组件/应用

Zookeeper、Kafka、JStorm、Ceph、HBase、consul、MongoDB 等。

Zookeeper(分布式应用协调服务):https://www.cnblogs.com/z-sm/p/5691752.html

Kafka(分布式消息队列):https://www.cnblogs.com/z-sm/p/5691760.html

JStorm(分布式实时或流式计算引擎):https://www.cnblogs.com/z-sm/p/5691768.html

HBase(分布式列式数据库):https://www.cnblogs.com/z-sm/p/5897608.html

Ceph(分布式对象存储):https://www.cnblogs.com/z-sm/p/9531263.html

Consul(服务注册与发现组件):https://www.cnblogs.com/z-sm/p/10018713.html

分布式锁实现方案演进:https://www.cnblogs.com/z-sm/p/10053636.html

 

posted @ 2022-11-27 10:19  March On  阅读(166)  评论(0编辑  收藏  举报
top last
Welcome user from
(since 2020.6.1)