分布式系统学习笔记

分布式系统架构的难点在于系统设计,以及管理和运维

分布式架构解决了“单点”和“性能容量”的问题,但却新增了一堆新的问题


架构设计变得复杂(尤其是其中的分布式事务)。

部署单个服务会比较快,但是如果一次部署需要多个服务,部署会变得复杂.

系统的吞吐量会变大,但是响应时间会变长。

运维复杂度会因为服务变多而变得很复杂。

架构复杂导致学习曲线变大。

测试和查错的复杂度增大。

技术可以很多样,这会带来维护和运维的复杂度。

管理分布式系统中的服务和调度变得困难和复杂。

 

SOA 架构是构造分布式计算应用程序的方法。

它将应用程序功能作为服务发送给最终用户或者其他服务。

它采用开放标准与软件资源进行交互,并采用标准的表示方式。

--可重用,粒度合适,模块化,可组合,构件化以及有互操作性。

--符合开放标准(通用的或行业的)。

--服务的识别和分类,提供和发布,监控和跟踪。

 

微服务架构比传统的SOA架构更松耦合。


每一个微服务都能独立完整地运行(所谓的自包含),后端单体的数据库也被微服务这样的架构分散到不同的服务中。
而它和传统 SOA 的差别在于,服务间的整合需要一个服务编排或是服务整合的引擎。就好像交响乐中需要有一个指挥来把所有乐器编排和组织在一起。


一般来说,这个编排和组织引擎可以是工作流引擎,也可以是网关。

当然,还需要辅助于像容器化调度这样的技术方式,如 Kubernetes。

 微服务的出现使得开发速度变得更快,部署快,隔离性高,系统的扩展度也很好,但是在集成测试、运维和服务管理等方面就比较麻烦了。

所以,需要一套比较好的微服务 PaaS 平台。

就像 Spring Cloud 一样需要提供各种配置服务、服务发现、智能路由、控制总线……

还有像 Kubernetes 提供的各式各样的部署和调度方式。

没有这些 PaaS 层的支撑,微服务也是很难被管理和运维的。

 

一个好的配置管理,应该分成三层:

底层和操作系统相关,
中间层和中间件相关,
最上面和业务应用相关。


于是底层和中间层是不能让用户灵活修改的,而是只让用户选择。
比如:操作系统的相关配置应该形成模板来让人选择,而不是让人乱配置的。
只有配置系统形成了规范,我们才 hold 得住众多的系统。

数据通讯协议
通常来说,作为一个协议,一定要有协议头和协议体。
协议头定义了最基本的协议数据,而协议体才是真正的业务数据。
对于协议头,我们需要非常规范地让每一个使用这个协议的团队都使用一套标准的方式来定义,

这样我们才容易对请求进行监控、调度和管理。

 

服务治理
服务治理不但需要我们定义出服务的关键程度,

还需要我们定义或是描述出关键业务或服务调用的主要路径。

没有这个事情,我们将无法运维或是管理整个系统。

数据库方面也需要做相应的隔离。也就是说,最好一个业务线用一套自己的数据库。
系统间不能读取对方的数据库,只通过服务接口耦合。这也是微服务的要求。
我们不但要拆分服务,还要为每个服务拆分相应的数据库。

分布式系统的问题

问题一:异构系统的不标准问题

问题二:系统架构中的服务依赖性问题

问题三:故障发生的概率更大

问题四:多层架构的运维复杂度更大

通常来说,我们可以把系统分成四层:基础层、平台层、应用层和接入层。
基础层就是我们的机器、网络和存储设备等。
平台层就是我们的中间件层,Tomcat、MySQL、Redis、Kafka 之类的软件。
应用层就是我们的业务软件,比如,各种功能的服务。
接入层就是接入用户请求的网关、负载均衡或是 CDN、DNS 这样的东西。

对于这四层,我们需要知道:
任何一层的问题都会导致整体的问题;
没有统一的视图和管理,导致运维被割裂开来,造成更大的复杂度。

分工不是问题,问题是分工后的协作是否统一和规范。这点,你一定要重视。

我认为,构建分布式服务需要从组织,到软件工程,再到技术上的一次大的改造,需要比较长的时间来磨合和改进,并不断地总结教训和成功经验。

 

分布式系统关键技术:全栈监控

这个监控系统需要完成的功能为:

全栈监控;
关联分析;
跨系统调用的串联;
实时报警和自动处置;
系统性能分析。
多层体系的监控


所谓全栈监控,其实就是三层监控。

基础层:监控主机和底层资源。比如:CPU、内存、网络吞吐、硬盘 I/O、硬盘使用等。
中间层:就是中间件层的监控。比如:Nginx、Redis、ActiveMQ、Kafka、MySQL、Tomcat 等。
应用层:监控应用层的使用。比如:HTTP 访问的吞吐量、响应时间、返回码,调用链路分析,性能瓶颈,还包括用户端的监控。

关注于整体应用的 SLA
主要从为用户服务的 API 来监控整个系统。

关联指标聚合
把有关联的系统及其指标聚合展示。主要是三层系统数据:基础层、平台中间件层和应用层。其中,最重要的是把服务和相关的中间件以及主机关联在一起,服务有可能运行在 Docker 中,也有可能运行在微服务平台上的多个 JVM 中,也有可能运行在 Tomcat 中。总之,无论运行在哪里,我们都需要把服务的具体实例和主机关联在一起,否则,对于一个分布式系统来说,定位问题犹如大海捞针。

快速故障定位
对于现有的系统来说,故障总是会发生的,而且还会频繁发生。故障发生不可怕,可怕的是故障的恢复时间过长。所以,快速地定位故障就相当关键。快速定位问题需要对整个分布式系统做一个用户请求跟踪的 trace 监控,我们需要监控到所有的请求在分布式系统中的调用链,这个事最好是做成没有侵入性的。

如何做出一个好的监控系统


1. 服务调用链跟踪

这个监控系统应该从对外的 API 开始,然后将后台的实际服务给关联起来,再将这个服务的依赖服务给关联起来,直到最后一个服务(如 MySQL 或 Redis),这样就可以把整个系统的服务全部都串连起来了。这个事情的最佳实践是 Google Dapper 系统,其对应于开源的实现是 Zipkin。对于 Java 类的服务,我们可以使用字节码技术进行字节码注入,做到代码无侵入式。

2. 服务调用时长分布。

使用 Zipkin, 可以看到一个服务调用链上的时间分布,这样有助于我们知道最耗时的服务是什么。

3.服务的TOP N 排行榜视图

所谓 TOP N 视图就是一个系统请求的排名情况。一般来说,这个排名会有三种排名的方法:a)按调用量排名,b) 按请求最耗时排名,c)按热点排名(一个时间段内的请求次数的响应时间和)。

4.数据库操作关联

对于 Java 应用,我们可以很方便地通过 JavaAgent 字节码注入技术拿到 JDBC 执行数据库操作的执行时间。对此,我们可以和相关的请求对应起来。

5.服务资源跟踪。

我们的服务可能运行在物理机上,也可能运行在虚拟机里,还可能运行在一个 Docker 的容器里,Docker 容器又运行在物理机或是虚拟机上。我们需要把服务运行的机器节点上的数据(如 CPU、MEM、I/O、DISK、NETWORK)关联起来。

这样一来,我们就可以知道服务和基础层资源的关系。如果是 Java 应用,我们还要和 JVM 里的东西进行关联,这样我们才能知道服务所运行的 JVM 中的情况(比如 GC 的情况)。

有了这些数据上的关联,我们就可以达到如下的目标。

当一台机器挂掉是因为 CPU 或 I/O 过高的时候,我们马上可以知道其会影响到哪些对外服务的 API。

当一个服务响应过慢的时候,我们马上能关联出来是否在做 Java GC,或是其所在的计算结点上是否有资源不足的情况,或是依赖的服务是否出现了问题。

当发现一个 SQL 操作过慢的时候,我们能马上知道其会影响哪个对外服务的 API。

当发现一个消息队列拥塞的时候,我们能马上知道其会影响哪些对外服务的 API。

总之,我们就是想知道用户访问哪些请求会出现问题,这对于我们了解故障的影响面非常有帮助。

一旦了解了这些信息,我们就可以做出调度。比如:
一旦发现某个服务过慢是因为 CPU 使用过多,我们就可以做弹性伸缩。
一旦发现某个服务过慢是因为 MySQL 出现了一个慢查询,我们就无法在应用层上做弹性伸缩,只能做流量限制,或是降级操作了。
所以,一个分布式系统,或是一个自动化运维系统,或是一个 Cloud Native 的云化系统,最重要的事就是把监控系统做好。在把数据收集好的同时,更重要的是把数据关联好。这样,我们才可能很快地定位故障,进而才能进行自动化调度。

服务的版本管理

当然,一般来说,在设计过程中,我们希望没有版本的依赖性问题。但可能有些时候,我们会有这样的问题,那么就需要在架构版本中记录下这个事,以便可以回滚到上一次相互兼容的版本。

要做到这个事,你需要一个架构的 manifest,一个服务清单,这个服务清单定义了所有服务的版本运行环境,其中包括但不限于:

服务的软件版本;
服务的运行环境——环境变量、CPU、内存、可以运行的结点、文件系统等;
服务运行的最大最小实例数。
每一次对这个清单的变更都需要被记录下来,算是一个架构的版本管理。而我们上面所说的那个集群控制系统需要能够解读并执行这个清单中的变更,以操作和管理整个集群中的相关变更。

一个好的操作系统需要能够通过一定的机制把一堆独立工作的进程给协同起来。在分布式的服务调度中,这个工作叫做 Orchestration,国内把这个词翻译成“编排”。

传统SOA 是通过 ESB(Enterprise Service Bus)——企业服务总线来完成的。
ESB 的主要功能是服务通信路由、协议转换、服务编制和业务规则应用等。
ESB 的服务编制叫 Choreography,与我们说的 Orchestration 是不一样的。

Orchestration 的意思是,一个服务像大脑一样来告诉大家应该怎么交互,就跟乐队的指挥一样。(查看Service-oriented Design:A Multi-viewpoint Approach,了解更多信息)。

Choreography 的意思是,在各自完成专属自己的工作的基础上,怎样互相协作,就跟芭蕾舞团的舞者一样。
在微服务中,使用更为轻量的中间件来取代 ESB 的服务编排功能。
简单来说,这需要一个 API Gateway 或一个简单的消息队列来做相应的编排工作。在 Spring Cloud 中,所有的请求都统一通过 API Gateway(Zuul)来访问内部的服务。这个和 Kubernetes 中的 Ingress 相似。

流量调度的主要功能
对于一个流量调度系统来说,其应该具有的主要功能是:

依据系统运行的情况,自动地进行流量调度,在无需人工干预的情况下,提升整个系统的稳定性;

让系统应对爆品等突发事件时,在弹性计算扩缩容的较长时间窗口内或底层资源消耗殆尽的情况下,保护系统平稳运行。

这还是为了提高系统架构的稳定性和高可用性。

此外,这个流量调度系统还可以完成以下几方面的事情。

服务流控。服务发现、服务路由、服务降级、服务熔断、服务保护等。
流量控制。负载均衡、流量分配、流量控制、异地灾备(多活)等。
流量管理。协议转换、请求校验、数据缓存、数据计算等。
所有的这些都应该是一个 API Gateway 应该做的事。

流量调度的关键技术
但是,作为一个 API Gateway 来说,因为要调度流量,首先需要扛住流量,而且还需要有一些比较轻量的业务逻辑,所以一个好的 API Gateway 需要具备以下的关键技术。

高性能。API Gateway 必须使用高性能的技术,所以,也就需要使用高性能的语言。

扛流量。要能扛流量,就需要使用集群技术。集群技术的关键点是在集群内的各个结点中共享数据。这就需要使用像 Paxos、Raft、Gossip 这样的通讯协议。因为 Gateway 需要部署在广域网上,所以还需要集群的分组技术。

状态数据调度小结
我们对状态数据调度做个小小的总结。

对于应用层上的分布式事务一致性,只有两阶段提交这样的方式。

而底层存储可以解决这个问题的方式是通过一些像 Paxos、Raft 或是 NWR 这样的算法和模型来解决。

状态数据调度应该是由分布式存储系统来解决的,这样会更为完美。但是因为数据存储的 Scheme 太多,所以,导致我们有各式各样的分布式存储系统,有文件对象的,有关系型数据库的,有 NoSQL 的,有时序数据的,有搜索数据的,有队列的……

总之,我相信状态数据调度应该是在 IaaS 层的数据存储解决的问题,而不是在 PaaS 层或者 SaaS 层来解决的。

在 IaaS 层上解决这个问题, 一般来说有三种方案,一种是使用比较廉价的开源产品,如:NFS、Ceph、TiDB、CockroachDB、ElasticSearch、InfluxDB、MySQL Cluster 和 Redis Cluster 之类的;另一种是用云计算厂商的方案。当然,如果不差钱的话,可以使用更为昂贵的商业网络存储方案。

业务逻辑。API Gateway 需要有简单的业务逻辑,所以,最好是像 AWS 的 Lambda 服务一样,可以让人注入不同语言的简单业务逻辑。

服务化。一个好的 API Gateway 需要能够通过 Admin API 来不停机地管理配置变更的,而不是通过一个.conf 文件来人肉地修改配置。

 

软件工程的本质。

我认为,一家商业公司的软件工程能力主要体现在三个地方。

第一,提高服务的 SLA。

所谓服务的 SLA,也就是我们能提供多少个 9 的系统可用性,而每提高一个 9 的可用性都是对整个系统架构的重新洗礼。而提高系统的 SLA 主要表现在两个方面:

高可用的系统;
自动化的运维。
你可以看一下我在 CoolShell 上写的《关于高可用系统》,这篇文章主要讲了构建高可用的系统需要使用分布式系统设计思路。然而这还不够,还需要一个高度自动化的运维和管理系统,因为故障是常态,如果没有自动化的故障恢复,很难提高服务的 SLA。

第二,能力和资源重用或复用。

软件工程还有一个重要的能力是让能力和资源可以重用。其主要表现在如下两个方面:

软件模块的重用;
软件运行环境和资源的重用。
为此,需要我们有两个重要的能力:一个是“软件抽象的能力”,另一个是“软件标准化的能力”。你可以认为软件抽象就是找出通用的软件模块或服务,软件标准化就是使用统一的软件通讯协议、统一的开发和运维管理方法……这样能让整体软件开发运维的能力和资源得到最大程度的复用,从而增加效率。

第三,过程的自动化。

编程本来就是把一个重复的工作自动化的过程,所以,软件工程的第三个本质就是把软件生产和运维的过程自动化起来。也就是下面这两个方面:

软件生产流水线;
软件运维自动化。
为此,我们除了需要 CI/CD 的 DevOps 式的自动化,也需要能够对正在运行的生产环境中的软件进行自动化运维。

通过了解软件工程的这三个本质,你会发现,我们上面所说的那些分布式的技术点是高度一致的,也就是下面这三个方面的能力。(是的,世界就是这样的。当参透了本质之后,你会发现世界是大同的。)

分布式多层的系统架构。
服务化的能力供应。
自动化的运维能力。
只有做到了这些,我们才能够真正拥有云计算的威力。这就是所谓的 Cloud Native。而这些目标都完美地体现在 PaaS 平台上。

前面讲述的分布式系统关键技术和软件工程的本质,都可以在 PaaS 平台上得到完全体现。所以,需要一个 PaaS 平台把那么多的东西给串联起来。这里,我结合自己的认知给你讲一下 PaaS 相关的东西,并把前面讲过的所有东西做一个总结。

PaaS 平台的本质
一个好的 PaaS 平台应该具有分布式、服务化、自动化部署、高可用、敏捷以及分层开放的特征,并可与 IaaS 实现良好的联动。

 


下面这三件事是 PaaS 跟传统中间件最大的差别。

服务化是 PaaS 的本质。软件模块重用,服务治理,对外提供能力是 PaaS 的本质。
分布式是 PaaS 的根本特性。多租户隔离、高可用、服务编排是 PaaS 的基本特性。
自动化是 PaaS 的灵魂。自动化部署安装运维,自动化伸缩调度是 PaaS 的关键。

 

总结一下,一个完整的 PaaS 平台会包括以下几部分。

PaaS 调度层 – 主要是 PaaS 的自动化和分布式对于高可用高性能的管理。
PaaS 能力服务层 – 主要是 PaaS 真正提供给用户的服务和能力。
PaaS 的流量调度 – 主要是与流量调度相关的东西,包括对高并发的管理。
PaaS 的运营管理 – 软件资源库、软件接入、认证和开放平台门户。
PaaS 的运维管理 – 主要是 DevOps 相关的东西。

PaaS 平台的生产和运维
下面的图给出了一个大概的软件生产、运维和服务接入,它把之前的东西都串起来了。

 

从左上开始软件构建,进入软件资产库(Docker Registry+ 一些软件的定义),然后走 DevOps 的流程,通过整体架构控制器进入生产环境,生产环境通过控制器操作 Docker+Kubernetes 集群进行软件部署和生产变更。

其中,同步服务的运行状态,并通过生命周期管理来拟合状态,如图右侧部分所示。服务运行时的数据会进入到相关应用监控,应用监控中的一些监控事件会同步到生命周期管理中,再由生命周期管理器来做出决定,通过控制器来调度服务运行。当应用监控中心发现流量变化,要进行强制性伸缩时,它通过生命周期管理来通知控制系统进行伸缩。

左下是服务接入的相关组件,主要是网关服务,以及 API 聚合编排和流程处理。这对应于之前说过的流量调度和 API Gateway 的相关功能。

分布式系统总结

传统的单体架构系统容量显然是有上限的。同时,为了应对有计划和无计划的下线时间,系统的可用性也是有其极限的。分布式系统为以上两个问题提供了解决方案,并且还附带有其他优势。但是,要同时解决这两个问题决非易事。为了构建分布式系统,我们面临的主要问题如下。

分布式系统的硬件故障发生率更高,故障发生是常态,需要尽可能地将运维流程自动化。
需要良好地设计服务,避免某服务的单点故障对依赖它的其他服务造成大面积影响。
为了容量的可伸缩性,服务的拆分、自治和无状态变得更加重要,可能需要对老的软件逻辑做大的修改。
老的服务可能是异构的,此时需要让它们使用标准的协议,以便可以被调度、编排,且互相之间可以通信。
服务软件故障的处理也变得复杂,需要优化的流程,以加快故障的恢复。
为了管理各个服务的容量,让分布式系统发挥出最佳性能,需要有流量调度技术。
分布式存储会让事务处理变得复杂;在事务遇到故障无法被自动恢复的情况下,手动恢复流程也会变得复杂。
测试和查错的复杂度增大。
系统的吞吐量会变大,但响应时间会变长。
为了解决这些问题,我们深入了解了以下这些解决方案。

需要有完善的监控系统,以便对服务运行状态有全面的了解。
设计服务时要分析其依赖链;当非关键服务故障时,其他服务要自动降级功能,避免调用该服务。
重构老的软件,使其能被服务化;可以参考 SOA 和微服务的设计方式,目标是微服务化;使用 Docker 和 Kubernetes 来调度服务。
为老的服务编写接口逻辑来使用标准协议,或在必要时重构老的服务以使得它们有这些功能。
自动构建服务的依赖地图,并引入好的处理流程,让团队能以最快速度定位和恢复故障,详见《故障处理最佳实践:应对故障》一文。
使用一个 API Gateway,它具备服务流向控制、流量控制和管理的功能。
事务处理建议在存储层实现;根据业务需求,或者降级使用更简单、吞吐量更大的最终一致性方案,或者通过二阶段提交、Paxos、Raft、NWR 等方案之一,使用吞吐量小的强一致性方案。
通过更真实地模拟生产环境,乃至在生产环境中做灰度发布,从而增加测试强度;同时做充分的单元测试和集成测试以发现和消除缺陷;最后,在服务故障发生时,相关的多个团队同时上线自查服务状态,以最快地定位故障原因。
通过异步调用来减少对短响应时间的依赖;对关键服务提供专属硬件资源,并优化软件逻辑以缩短响应时间。
你已经看到,解决分布式服务的吞吐量和可用性问题不是件容易的事,以及目前的主流技术是怎么办到的。衍生出来的许多子问题,每一个都值得去细化、去研究其解决方案。这已经超出本文的篇幅所能及的了,但的确都是值得我们做技术的人去深入思考的。

posted @ 2018-09-02 10:35  Appinn  阅读(8)  评论(0)    收藏  举报