作者 | 晓土 阿里巴巴高级工程师
姊妹篇阅读推荐:《云原生时代,分布式系统设计必备知识图谱(内含22个知识点)》
导读:本文力求从分布式基础理论、架构设计模式、工程应用、部署运维、业界方案这几大方面,介绍基于 MSA(微服务架构)的分布式知识体系大纲,从而对 SOA 到 MSA 进化有着立体的认识;从概念上和工具应用上更近一步了解微服务分布式的本质,身临其境的感受如何搭建全套微服务架构的过程。
关注“阿里巴巴云原生”公众号,回复“分布”,即可下载分布式系统及其知识体系清晰大图!
随着移动互联网的发展和智能终端的普及,计算机系统早就从单机独立工作过渡到多机器协作,集群按照分布式理论构建出庞大复杂的应用服务,在分布式的基础上正进行一场云原生的技术革命,彻底打破传统的开发方式,解放了新一代的生产力。
关注“阿里巴巴云原生”公众号,回复“分布”,即可下载分布式系统及其知识体系清晰大图!
由于业务发展到一定程度后,需要对服务进行解耦,进而把一个单一的大系统按逻辑拆分成不同的子系统,通过服务接口来通讯。面向服务的设计模式,最终需要总线集成服务,而且大部分时候还共享数据库,出现单点故障时会导致总线层面的故障,更进一步可能会把数据库拖垮,所以才有了更加独立的设计方案的出现。
微服务是真正意义上的独立服务,从服务入口到数据持久层,逻辑上都是独立隔离的,无需服务总线来接入,但同时也增加了整个分布式系统的搭建和管理难度,需要对服务进行编排和管理,所以伴随着微服务的兴起,微服务生态的整套技术栈也需要无缝接入,才能支撑起微服务的治理理念。
传统的节点也就是一台单体的物理机,所有的服务都揉进去包括服务和数据库;随着虚拟化的发展,单台物理机往往可以分成多台虚拟机,实现资源利用的最大化,节点的概念也变成单台虚拟机上面服务;近几年容器技术逐渐成熟后,服务已经彻底容器化,也就是节点只是轻量级的容器服务。总体来说,节点就是能提供单位服务的逻辑计算资源的集合。
分布式架构的根基就是网络,不管是局域网还是公网,没有网络就无法把计算机联合在一起工作,但是网络也带来了一系列的问题。网络消息的传播有先后,消息丢失和延迟是经常发生的事情,我们定义了三种网络工作模式:
常用网络传输层有两大协议的特点简介:
慢速物理时空中,时间独自在流淌着,对于串行的事务来说,很简单的就是跟着时间的脚步走就可以,先来后到的发生。而后我们发明了时钟来刻画以往发生的时间点,时钟让这个世界井然有序。但是对于分布式世界来说,跟时间打交道着实是一件痛苦的事情。
分布式世界里面,我们要协调不同节点之间的先来后到关系,不同节点本身承认的时间又各执己见,于是我们创造了网络时间协议(NTP)试图来解决不同节点之间的标准时间,但是 NTP 本身表现并不尽如人意,所以我们又构造出了逻辑时钟,最后改进为向量时钟:
有了衡量时间的工具,解决顺序问题自然就是水到渠成了。因为整个分布式的理论基础就是如何协商不同节点的一致性问题,而顺序则是一致性理论的基本概念,所以前文我们才需要花时间介绍衡量时间的刻度和工具。
说到一致性理论,我们必须看一张关于一致性强弱对系统建设影响的对比图:
该图对比了不同一致性算法下的事务、性能、错误、延迟的平衡。
单机环境下我们对传统关系型数据库有苛刻的要求,由于存在网络的延迟和消息丢失,ACID 便是保证事务的原则,这四大原则甚至我们都不需要解释出来就耳熟能详了:
分布式环境下,我们无法保证网络的正常连接和信息的传送,于是发展出了 CAP/FLP/DLS 这三个重要的理论:
多数情况下,其实我们也并非一定要求强一致性,部分业务可以容忍一定程度的延迟一致,所以为了兼顾效率,发展出来了最终一致性理论 BASE。BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency):
分布式架构的核心就在于一致性的实现和妥协,那么如何设计一套算法来保证不同节点之间的通信和数据达到无限趋向一致性,就非常重要了。保证不同节点在充满不确定性网络环境下能达成相同副本的一致性是非常困难的,业界对该课题也做了大量的研究。
首先我们要了解一致性的大前提原则 (CALM):
CALM 原则的全称是 Consistency and Logical Monotonicity ,主要描述的是分布式系统中单调逻辑与一致性的关系,它的内容如下,参考 consistency as logical monotonicity。
然后再关注分布式系统的数据结构 CRDT(Conflict-Free Replicated Data Types):
我们了解到分布式一些规律原则之后,就要着手考虑如何来实现解决方案,一致性算法的前提是数据结构,或者说一切算法的根基都是数据结构,设计良好的数据结构加上精妙的算法可以高效的解决现实的问题。经过前人不断的探索,我们得知分布式系统被广泛采用的数据结构 CRDT。
参考《谈谈 CRDT》,A comprehensive study of Convergent and Commutative Replicated Data Types
了解数据结构后,我们需要来关注一下分布式系统的一些重要的***协议***HATs(Highly Available Transactions),ZAB(Zookeeper Atomic Broadcast):
参考《高可用事务》,《ZAB 协议分析》
最后要学习的是业界主流的一致性算法 :
说实话具体的算法我也还没完全搞懂,一致性算法是分布式系统最核心本质的内容,这部分的发展也会影响架构的革新,不同场景的应用也催生不同的算法。
这一节我们说完分布式系统里面核心理论基础,如何达成不同节点之间的数据一致性,下面我们将会讲到目前都有哪些主流的分布式系统。
单台计算机的存储始终有上限,随着网络的出现,多台计算机协作存储文件的方案也相继被提出来。最早的分布式文件系统其实也称为网络文件系统,第一个文件服务器在 1970 年代被发展出来。在 1976 年迪吉多公司设计出 File Access Listener(FAL),而现代分布式文件系统则出自赫赫有名的 Google 的论文,《The Google File System》奠定了分布式文件系统的基础。现代主流分布式文件系统参考《分布式文件系统对比》,下面列举几个常用的文件系统:
数据库当然也属于文件系统,主数据增加了事务、检索、擦除等高级特性,所以复杂度又增加了,既要考虑数据一致性也得保证足够的性能。传统关系型数据库为了兼顾事务和性能的特性,在分布式方面的发展有限,非关系型数据库摆脱了事务的强一致性束缚,达到了最终一致性的效果,从而有了飞跃的发展,NoSql(Not Only Sql) 也产生了多个架构的数据库类型,包括 KV、列式存储、文档类型等。
分布式计算系统构建在分布式存储的基础上,充分发挥分布式系统的数据冗余灾备,多副本高效获取数据的特性,进而并行计算,把原本需要长时间计算的任务拆分成多个任务并行处理,从而提高了计算效率。分布式计算系统在场景上分为离线计算、实时计算和流式计算。
缓存作为提升性能的利器无处不在,小到 CPU 缓存架构,大到分布式应用存储。分布式缓存系统提供了热点数据的随机访问机制,大大了提升了访问时间,但是带来的问题是如何保证数据的一致性,引入分布式锁来解决这个问题,主流的分布式存储系统基本就是 Redis 了。
分布式消息队列系统是消除异步带来的一系列复杂步骤的一大利器,在多线程高并发场景下,我们常常需要谨慎设计业务代码,来保证多线程并发情况下不出现资源竞争导致的死锁问题。而消息队列以一种延迟消费的模式将异步任务都存到队列,然后再逐个消化。
分布式系统从单机到集群的形态发展,复杂度也大大提高,所以对整个系统的监控也是必不可少。
分布式系统的核心模块就是在应用如何处理业务逻辑,应用直接的调用依赖于特定的协议来通信,有基于 RPC 协议的,也有基于通用的 HTTP 协议。
错误对应分布式系统是家常便饭,而且我们设计系统的时候,本身就需要把容错作为普遍存在的现象来考虑。那么当出现故障的时候,快速恢复和排查故障就显得非常重要了。分布式日志采集存储和检索则可以给我们提供有力的工具来定位请求链路中出现问题的环节。
前文我们提到所谓分布式系统,是迫于单机的性能有限,而堆硬件却又无法无休止的增加,单机堆硬件最终也会遇到性能增长曲线的瓶颈。于是我们才采用了多台计算机来干同样的活,但是这样的分布式系统始终需要中心化的节点来监控或者调度系统的资源,即使该中心节点也可能是多节点组成。区块链则是真正的区中心化分布式系统,系统里面只有 P2P 网络协议各自通信,没有真正意义的中心节点,彼此按照区块链节点的算力、权益等机制来协调新区块的产生。
上节我们列举了不同场景下不同分布式系统架构扮演的角色和实现的功能,本节我们更进一步归纳分布式系统设计的时候是如何考虑架构设计的、不同设计方案直接的区别和侧重点、不同场景需要选择合作设计模式,来减少试错的成本,设计分布式系统需要考虑以下的问题。
可用性是系统运行和工作的时间比例,通常以正常运行时间的百分比来衡量。它可能受系统错误、基础架构问题、恶意攻击和系统负载的影响。分布式系统通常为用户提供服务级别协议(SLA),因此应用程序必须设计为最大化可用性。
数据管理是分布式系统的关键要素,并影响大多数质量的属性。由于性能,可扩展性或可用性等原因,数据通常托管在不同位置和多个服务器上,这可能带来一系列挑战。例如,必须维护数据一致性,并且通常需要跨不同位置同步数据。
良好的设计包括诸如组件设计和部署的一致性、简化管理和开发的可维护性、以及允许组件和子系统用于其他应用程序和其他方案的可重用性等因素。在设计和实施阶段做出的决策对分布式系统和服务质量和总体拥有成本产生巨大影响。
分布式系统需要一个连接组件和服务的消息传递中间件,理想情况是以松散耦合的方式,以便最大限度地提高可伸缩性。异步消息传递被广泛使用,并提供许多好处,但也带来了诸如消息排序,幂等性等挑战
分布式系统在远程数据中心运行,无法完全控制基础结构,这使管理和监视比单机部署更困难。应用必须公开运行时信息,管理员可以使用这些信息来管理和监视系统,以及支持不断变化的业务需求和自定义,而无需停止或重新部署应用。
性能表示系统在给定时间间隔内执行任何操作的响应性,而可伸缩性是系统处理负载增加而不影响性能或容易增加可用资源的能力。分布式系统通常会遇到变化的负载和活动高峰,特别是在多租户场景中,几乎是不可能预测的。相反,应用应该能够在限制范围内扩展以满足需求高峰,并在需求减少时进行扩展。可伸缩性不仅涉及计算实例,还涉及其他元素,如数据存储、消息队列等。
弹性是指系统能够优雅地处理故障并从故障中恢复。分布式系统通常是多租户,使用共享平台服务、竞争资源和带宽,通过 Internet 进行通信,以及在商用硬件上运行,意味着出现瞬态和更永久性故障的可能性增加。为了保持弹性,必须快速有效地检测故障并进行恢复。
安全性是系统能够防止在设计使用之外的恶意或意外行为,并防止泄露或丢失信息。分布式系统在受信任的本地边界之外的 Internet 上运行,通常向公众开放,并且可以为不受信任的用户提供服务。必须以保护应用程序免受恶意攻击,限制仅允许对已批准用户的访问,并保护敏感数据。
前文我们介绍了分布式系统的核心理论,面临的一些难题和解决问题的折中思路,罗列了现有主流分布式系统的分类,而且归纳了建设分布式系统的一些方法论,那么接下来我们将从工程角度来介绍真刀真枪搭建分布式系统包含的内容和步骤。
巧妇难为无米之炊,我们一切的软件系统都是构建在硬件服务器的基础上。从最开始的物理机直接部署软件系统,到虚拟机的应用,最后到了资源上云容器化,硬件资源的使用也开始了集约化的管理。本节对比的是传统运维角色对应的职责范围,在 devops 环境下,开发运维一体化,我们要实现的也是资源的灵活高效使用。
过去软件系统随着用户量增加需要增加机器资源的话,传统的方式就是找运维申请机器,然后部署好软件服务接入集群,整个过程依赖的是运维人员的人肉经验,效率低下而且容易出错。微服务分布式则无需人肉增加物理机器,在容器化技术的支撑下,我们只需要申请云资源,然后执行容器脚本即可。
有了计算资源后,另外最重要的就是网络资源了。在现有的云化背景下,我们几乎不会直接接触到物理的带宽资源,而是直接由云平台统一管理带宽资源。我们需要的是对网络资源的最大化应用和有效的管理。
在系统故障的时候我们第一要务是系统恢复,同时保留案发现场也是非常重要的,资源调度平台则需要有统一的机制保存好故障现场。
在我们建设好分布式系统后,最先受到考验的关口就是网关了,进而我们需要关注系统流量的情况,也就是如何对流量的管理,我们追求的是在系统可容纳的流量上限内,把资源留给最优质的流量使用、把非法恶意的流量挡在门外,这样节省成本的同时确保系统不会被冲击崩溃。
负载均衡是我们对服务如何消化流量的通用设计,通常分为物理层的底层协议分流的硬负载均衡和软件层的软负载。负载均衡解决方案已经是业界成熟的方案,我们通常会针对特定业务在不同环境进行优化,常用有如下的负载均衡解决方案
负载均衡首当其冲的就是网关,因为中心化集群流量最先打到的地方就是网关了,如果网关扛不住压力的话,那么整个系统将不可用。
剩下的真实流量我们采用不同的算法来分流请求。
流量分配
流量限制在流量激增的时候,通常我们需要有限流措施来防止系统出现雪崩,那么就需要预估系统的流量上限,然后设定好上限数,但流量增加到一定阈值后,多出来的流量则不会进入系统,通过牺牲部分流量来保全系统的可用性。
所谓打铁还需自身硬,流量做好了调度管理后,剩下的就是服务自身的健壮性了。分布式系统服务出现故障是常有的事情,甚至我们需要把故障本身当做是分布式服务的一部分。
我们网络管理一节中介绍了网关,网关是流量的集散地,而注册中心则是服务的根据地。
服务编排的定义是:通过消息的交互序列来控制各个部分资源的交互。参与交互的资源都是对等的,没有集中的控制。微服务环境下服务众多我们需要有一个总的协调器来协议服务之间的依赖,调用关系,K8s 则是我们的不二选择。
前面我们解决了网络的健壮性和效率问题,这节介绍的是如何使我们的服务更加健壮。
发现资源管理那节我们介绍了从云平台申请了容器宿主资源后,通过自动化脚本就可以启动应用服务,启动后服务则需要发现注册中心,并且把自身的服务信息注册到服务网关,即是网关接入。注册中心则会监控服务的不同状态,做健康检查,把不可用的服务归类标记。
降级:当用户激增的时候,我们首先是在流量端做手脚,也就是限流。当我们发现限流后系统响应变慢了,有可能导致更多的问题时,我们也需要对服务本身做一些操作。服务降级就是把当前不是很核心的功能关闭掉,或者不是很要紧的准确性放宽范围,事后再做一些人工补救。
熔断:当我们都做了以上的操作后,还是觉得不放心,那么就需要再进一步操心。熔断是对过载的一种自身保护,犹如我们开关跳闸一样。比如当我们服务不断对数据库进行查询的时候,如果业务问题造成查询问题,这是数据库本身需要熔断来保证不会被应用拖垮,并且访问友好的信息,告诉服务不要再盲目调用了。
幂等:我们知道,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。那么就需要对单次操作赋予一个全局的 id 来做标识,这样多次请求后我们可以判断来源于同个客户端,避免出现脏数据。
数据存储最大的挑战就是数据冗余的管理,冗余多了效率变低而且占用资源,副本少了起不到灾备的作用,我们通常的做法是把有转态的请求,通过转态分离,转化为无状态请求。
分离状态至全局存储,请求转换为无状态流量,比如我们通常会将登陆信息缓存至全局 redis 中间件,而不需要在多个应用中去冗余用户的登陆数据。
数据横向扩展。
多副本冗余。
我们从资源申请管理的时候就介绍到 devops 的趋势,真正做到开发运维一体化则需要不同的中间件来配合完成。
全局配置中心按环境来区分,统一管理,减少了多处配置的混乱局面。
微服务分布式部署是家常便饭,如何让我们的服务更好地支撑业务发展,稳健的部署策略是我们首先需要考虑的,如下的部署策略适合不同业务和不同的阶段。
任务调度是系统必不可少的一个环节,传统的方式是在 Linux 机器上配置 crond 定时任务或者直接在业务代码里面完成调度业务,现在则是成熟的中间件来代替。
运维工作中很大一部分时间需要对应用进行重启,上下线操作,还有日志清理。
既然我们知道分布式系统故障是家常便饭,那么应对故障的方案也是不可或缺的环节。通常我们有主动和被动的方式来处理:
重试设计的关键在于设计好重试的时间和次数,如果超过重试次数,或是一段时间,那么重试就没有意义了。开源的项目 spring-retry 可以很好地实现我们重试的计划。
事务补偿符合我们最终一致性的理念。补偿事务不一定会将系统中的数据返回到原始操作开始时其所处的状态。 相反,它补偿操作失败前由已成功完成的步骤所执行的工作。补偿事务中步骤的顺序不一定与原始操作中步骤的顺序完全相反。 例如,一个数据存储可能比另一个数据存储对不一致性更加敏感,因而补偿事务中撤销对此存储的更改的步骤应该会首先发生。对完成操作所需的每个资源采用短期的基于超时的锁并预先获取这些资源,这样有助于增加总体活动成功的可能性。 仅在获取所有资源后才应执行工作。 锁过期之前必须完成所有操作。
由于分布式系统是由众多机器共同协作的系统,而且网络也无法保证完全可用,所以我们需要建设一套对各个环节都能监控的系统,这样我们才能从底层到业务各个层面进行监控,出现意外的时候可以及时修复故障,避免更多的问题出现。
基础层面是对容器资源的监测,包含各个硬件指标的负载情况
分布式系统接入了大量的中间件平台,中间件本身的健康情况也需要监控。
当故障已经发生后,我们第一个要做的就是马上消除故障,确保系统服务正常可用,这个时候通常做回滚操作。
应用回滚之前需要保存好故障现场,以便排查原因。
应用服务回滚后,代码基线也需要 revert 到前一版本。
整体回滚需要服务编排,通过大版本号对集群进行回滚。
性能优化是分布式系统的大专题,涉及的面非常广,这块简直可以单独拿出来做一个系列来讲,本节就先不展开。本身我们做服务治理的过程也是在性能的优化过程。
参考《高并发编程知识体系》
缓存是解决性能问题的一大利器,理想情况下,每个请求不需要额外计算就立刻能获取到结果时最快。小到 CPU 的三级缓存,大到分布式缓存,缓存无处不在,分布式缓存需要解决的就是数据的一致性,这个时候我们引入了分布式锁的概念,如何处理分布式锁的问题将决定我们获取缓存数据的效率。
多线程编程模式提升了系统的吞吐量,但也同时带来了业务的复杂度。
事件驱动的异步编程是一种新的编程模式,摒弃了多线程的复杂业务处理问题,同时能够提升系统的响应效率。
最后总结一下,如果有可能的话,请尝试使用单节点方式而不是分布式系统。分布式系统伴随着一些失败的操作,为了处理灾难性故障,我们使用备份;为了提高可靠性,我们引入了冗余。
分布式系统本质就是一堆机器的协同,而我们要做的就是搞出各种手段来然机器的运行达到预期。这么复杂的系统,需要了解各个环节、各个中间件的接入,是一个非常大的工程。庆幸的是,在微服务背景下,多数基础性的工作已经有人帮我们实现了。前文所描述的分布式架构,在工程实现了是需要用到分布式三件套 (Docker K8S Srping Cloud) 基本就可以构建出来了。
分布式架构核心技术分布图如下:
原图来源:https://dzone.com/articles/deploying-microservices-spring-cloud-vs-kubernetes
分布式技术栈使用中间件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rIC7zqVl-1571207099112)(https://yqfile.alicdn.com/6a124b49de67729fef04d4ce88b9fa60bafc5a7c.png)]
原图来源:https://dzone.com/articles/deploying-microservices-spring-cloud-vs-kubernetes
“ 阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术公众号。”