一、亚马逊的架构规定
最早实践分布式服务化架构思想的公司应该是亚马逊,它早在 2002 年就颁布了下列架构规定,这应该就是 AWS(Amazon Web Service)出现的基础:
1. 所有团队的程序模块都要通过 Service Interface 方式将其数据与功能开放出来。
2. 团队间程序模块的信息通信,都要通过这些接口。
3. 除此之外没有其它的通信方式。其他形式一概不允许:不能直接链接别的程序(把其他团队的程序当做动态链接库来链接),不能直接读取其他团队的数据库,不能使用共享内存模式,不能使用别人模块的后门,等等。唯一允许的通信方式是调用 Service Interface。
4. 任何技术都可以使用。比如:HTTP、CORBA、Pub/Sub、自定义的网络协议等。
5. 所有的 Service Interface,毫无例外,都必须从骨子里到表面上设计成能对外界开放的。也就是说,团队必须做好规划与设计,以便未来把接口开放给全世界的程序员,没有任何例外。
6. 不这样做的人会被炒鱿鱼。
前面提到过,分布式系统架构会带来很多问题,譬如:
1. 一个线上故障的工单会在不同服务和不同团队中转过来转过去;
2. 每个团队都可能成为一个潜在的 DDoS 攻击者,除非每个服务都做好配额和限流;
3. 监控和查错变得更复杂,除非有非常强大的监控手段;
4. 服务发现和服务治理也变得非常复杂。
面对以上问题,亚马逊多年的实践,使其可以运维和管理极其复杂的分布式服务架构。主要在于以下几点:
1. 分布式服务的架构需要分布式的团队架构
在亚马逊,一个服务由一个小团队(two pizza team,两张 pizza 就可以喂饱的团队)负责。从前端到数据,从需求分析到上线运维。按职责分工,而非按技能分工。
2. 分布式服务查错不容易
一旦出现比较严重的故障,需要整体查错。出现一个 S2 的故障,就可以看到每个团队的人都会上线。在工单系统里能看到,在故障发生的一开始,大家都在签到并自查自己的系统。如果没问题,也要在线待命(standby),等问题解决。
3. 没有专职的测试人员,也没有专职的运维人员,开发人员做所有的事情
开发人员做所有事情的好处是——吃自己的狗粮(Eat Your Own Dog Food)。自己写的代码自己维护自己养,会让开发人员明白,写代码容易维护代码复杂。这样,开发人员在接需求、做设计、写代码、做工具时都会考虑到软件的长期维护性。
4. 运维优先,崇尚简化和自动化
为了能够运维如此复杂的系统,亚马逊内部在运维上下了非常大的功夫。现在人们所说的 DevOps 这个事,亚马逊在 10 多年前就做到了。亚马逊最为强大的就是运维,拼命地对系统进行简化和自动化,让亚马逊做到了可以轻松运维拥有上千万台虚机的 AWS 云平台。
5. 内部服务和外部服务一致
无论是从安全方面,还是接口设计方面,无论是从运维方面,还是故障处理的流程方面,亚马逊的内部系统都和外部系统一样对待。这样做的好处是,内部系统的服务随时都可以开放出来。而且,从第一天开始,服务提供方就有对外服务的能力。可以想象,以这样的标准运作的团队其能力会是什么样的。
二、分布式系统中需要注意的问题
1. 异构系统的不标准问题
异构系统的不标准问题主要体现在:
-
- 软件和应用不标准
- 通讯协议不标准
- 数据格式不标准
- 开发、运维的过程和方法不标准
不同的软件、语言,自然有不同的兼容性和不同的开发、测试、运维标准。自然而然地,这会使我们用不同的方式来开发和运维,从而引起架构复杂度的提升。譬如有些软件修改配置需要改 .config 文件,有些则需要调用管理 API。
在通讯方面,不同的软件可能使用不同的协议,即使协议相同,数据格式也不一而足。不同的团队,采用不同的技术,开发和运维方式也不一样。这些不同会让整个分布式系统架构异常复杂。所以,分布式系统架构要有相应的规范。以网络通讯为例,很多服务的 API 出错,并不返回 HTTP 的错误状态码,而是返回正常状态码 200,然后在 HTTP Body 的 JSON 字符串中加入 error message。这就给监控造成很大困难。现在,应该使用 Swagger 规范了。
我们再以软件配置管理为例进行说明:很多公司的软件配置管理就是 key-value 的形式。这种方式很灵活,灵活到可以轻易被滥用——不规范的配置命名、不规范的值,甚至在配置中直接嵌入前端展示内容。
好的配置管理应该分为三层:底层和操作系统相关、中间层和中间件相关、最上层和业务应用相关。底层和中间层不能让客户灵活修改,而是要提供模板,让用户只能从中选择,而非胡乱配置。
再譬如数据通讯协议,一定会有协议头和协议体。协议头定义基本的协议数据,协议体则是真正的业务数据。我们要让每个使用此协议的团队都遵循协议头规范定义,才能易于对请求进行监控、调度和管理。
2. 系统架构中的服务依赖性问题
传统的单体应用中,一台机器挂了,整个软件也会随之挂掉。那么分布式架构是否就不会发生这样的事情呢?事实上,分布式架构下,服务是有依赖的。一个服务依赖链上的某个服务挂了,就有可能引起多米诺骨牌效应。
如上所述,分布式系统中,服务的依赖也会带来一些问题:
-
- 如果非关键业务被关键业务所依赖,那么这个非关键业务就变成了关键业务。
- 服务依赖链中,会有“短板效应”。整个 SLA 由最差的那个服务决定。
这就是服务治理的内容了。服务治理不仅要我们定义出服务的关键程度,还要我们定义或描述出关键业务或服务调用的主要路径。没有服务治理,就无法运维、管理整个系统。
很多分布式架构在应用层上做到了业务隔离,然而,在数据库结点上并没有。如果一个非关键业务把数据库拖死,那么会导致全站不可用。所以,数据库方面也需要做相应的隔离,最好一个业务线用一套自己的数据库。这就是亚马逊服务器的实践——系统间不能读取对方的数据库,只通过服务接口耦合。这也是微服务的要求。我们不但要拆分服务,还要为每个服务拆分相应的数据库。
3. 故障发生概率更大
在分布式系统中,因为使用的机器和服务会非常多,所以,故障发生的频率会比传统的单体应用更大。只不过,单体应用的故障影响面很大,而分布式系统中,虽然故障的影响面可以被隔离,但是因为机器和服务多,出故障的频率也会多。另一方面,因为管理复杂,而且没人知道整个架构中有什么,所以非常容易犯错误。对分布式系统架构的运维,堪比噩梦。
以下两条堪比金科玉律:
-
- 出现故障不可怕,故障恢复时间过长才可怕。
- 出现故障不可怕,故障影响面过大才可怕。
分布式系统的运维团队非常忙,几乎每时每刻都在处理故障。很多公司拼命给自己的系统里添加监控指标,这其实是费力不讨好——信息太多,等于没有信息。此外,SLA 要求我们定义出“Key Metrics”,即关键指标。不求质而重量,这是战术上勤奋,战略上懒惰的做法。
上述都是“救火”,而非“防火”。我们在设计或运维系统时,就要考虑如何减轻故障(Design for Failure)。如果无法避免,也要用自动化的方式来恢复故障,减少故障影响面。
当机器和服务数量越来越多时,瓶颈就变成了人固有的缺陷——人无法对复杂的事情做到事无巨细的管理,只有及其自动化才能帮助我们。
4. 多层架构的运维复杂度更大
我们通常把系统分为四层:
-
- 基础层:就是我们的机器、网络和存储设备等;
- 平台层:就是我们的中间件层,Tomcat、MySQL、Redis、Kafka 之类的软件;
- 应用层:就是我们的业务软件,比如,各种功能的服务;
- 接入层:就是接入用户请求的网关、负载均衡或是 CDN、DNS 这样的东西。
任何一层的问题都会导致整体问题。没有统一的视图和管理,导致运维被割裂开来,造成了更大的复杂度。
很多公司都是按技能分工的,他们按照技能把技术团队分为产品开发、中间件开发、业务运维、系统运维等子团队。这样的分工导致的结果就是大家各管一摊,很多事情完全连不在一起。整个系统会像 “多米诺骨牌”一样,一个环节出现问题,就会倒下去一大片。因为没有一个统一的运维视图,不知道一个服务调用是如何经过每一个服务和资源,也就导致在出现故障时要花大量的时间在沟通和定位问题上。
分工不是问题,问题是分工后的协作是否统一和规范。这点一定要重视。