2011-2012 年,我所在的团队正在给某国字号企业交付一个集团级的企业应用,这个应用覆盖了除 IaaS 之外的整个上层,层级和宽度上来说都算得上是个大家伙了。在当时,这个项目有几个新鲜的点,例如硬件全部采用通用的 x86 刀片服务器,全部基础软件都使用了开源软件——例如 LAMP、Nginx、MongoDB(是的你没看错,就是 MongoDB),使用 Java、PHP、ASP.NET 等多种平台的多个厂商协作交付;除此之外,还设计了和当时大行其道的 SoA 不太一样的一种架构,这个架构有几个特点:
- 因为其中几百个子系统都是为各自不同的业务(部门)服务的,换句话说,其实我当时的建设过程是多个建设方对应多个甲方的,因此我们采取的策略是以业务为核心,用高内聚低耦合的基本原则进行子系统的拆分。
- 提供一种基于 HTTP 的二进制传输协议,不同开发商依照约定接口各自用各自的开发平台和各自的数据库进行开发和交付。
- 部分全局能力,例如认证鉴权、门户、PaaS 级服务及其 SDK 由总集成商提供。
- 对于高负载应用,其横向扩展能力是首要考量标准(在那个年代,扩缩容是个复杂且高危的操作),在满足业务需求的基本条件下,对子系统中的模块,按照若干原则进行拆分为不同进程:
- 可扩缩和不可扩缩容的模块
- 扩缩容不同步的模块
- 负载规模不一致的不同模块
总的说来这次交付是相当成功的,然而无损创新是不太符合历史规律的,这种架构带来的问题同样挠头:
- 数据流转: 前面提到,很多子系统会使用自己的数据库,所以有些数据需要在不同子系统之间进行流转,就有可能造成数出多头的问题。
- 监控要求高: 刀片服务器和小型机相比,数量大增、性能和稳定性又稍有不足,因此对硬件和系统进行监控的需求就陡然提高,另外因为进程之间、节点之间通信次数和通信量的暴涨,对于网络连通性、磁盘和网络 IO 等的监控需求也是水涨船高。
- 运维难度大: 不同的应用、不同的平台、大量进程,还有更要命的不同的数据库,对运维团队的要求极高(事实上离开项目一年之后我还能听到来自该项目运维团队的骂声。 )
- ….
回到《灾难》一文,其中提到的很多更加具体的点,摘抄几点,一起休闲一下。
灾难一:服务太小
20 名工程师组成了维护 50 项服务的小组。一人负责一项服务还不够!:我一直认为,微服务是一个向现实妥协的过程,这个现实应该是全面的,它不仅是业务的现实、也是团队能力的现实,这个语境下所提倡的全能小团队,其能力虽然宽泛,但即使是业务软件也是有其服务上限的,不尊重业务负载和不尊重团队负载,并没有什么区别。量入为出是个基本要求。
Another smell was when someone told me that deploying a new feature in service A also needed a deployment — at the same time — in service B.(有人要求我把一个新功能同时部署到两个不同服务之中):这个例子很有代表性,这里的 Someone 同时是 Service A 和 Service B 两个不同服务的所有者或者部分所有者。所以这一点就面临几个问题:
- 这两个服务应该分开的么? 为什么会共享同一个功能?
- 新功能是不是应该拆分开来,成为第三个服务?
- Someone 到底是谁?
与仅仅在 IDE 中查看一个项目不同,人们需要一次打开多个项目才能了解所有这些混乱的情况。:其实即使是一个单体应用,只要它规模太大,在外人来说也是很难突然就能够 “make sense of all that mess” 了——别人的代码在功能和非功能层面满足服务要求,没有在边界外造成不良影响,按照契约进行开发和测试,根据讲定的边界做好各种限制各种观测,为啥非要把手伸那么长呢,是职责不清还是拆分有误?
灾难二:开发环境
Mobile developers not developing a feature before it was in a development environment or backend developers who wanted to try their service didn’t break any business flow. It was also problematic if someone wanted to test the whole flow in a mobile app before production.:这个问题涉及到的是依赖服务之间的协作开发的问题,实际上所有不同实体之间的调用,不管是内部的函数调用、还是古代的 COM 、CORBA,后来的 WebService/RESTful 等等,都面临同样的环境和上下游依赖问题。至于后续的若干的问题,实际上都是全局角度上的微服务治理问题——其实不管有没有微服务,协作单元多了一定会出现这种情况,像 Grafana Stack、ES Stack、Skywalking 等观测技术,以及 Service Mesh 等的网格技术,都是为了解决这样的难题的。十二要素、云原生等方法也给出了相对具体的设计、部署和运行方法的支持。
灾难三:端到端测试
这个——非常特别同意。
灾难四:巨大的共享数据库
这个其实应该属于典型的人祸了,到现在应该没人会认为共享数据库的多个进程能够称之为微服务了。
灾难五:API 网关
Suddenly, you have your “API gateway” being a single-point-of-failure — because people find it easier to handle authentication in a single place — and with some unintended business logic inside it. Instead of having a monolith getting all of the traffic, now you have a home-made Spring Boot service getting all of it!:网关和服务网格这样的产品,发展下来经常会扩展成具备大量功能的超级工具,这给人一种联想——上了工具之后写写配置就有功能用了这实在是太棒了。然而 Java 开发者或者 YAML 工程师都会知道,配置 这事太难了,以至于出现了 OPA、PIPY 这样直接让配置工程师撸代码的“反潮流”工具。
事实上采用一个开源/第三方软件或者库,因其规模不同,对应的评价工作量是完全不同的,尤其对于 Kong、Istio 这样的大家伙来说,比起“用不用”的问题来说,“用多少”和“怎么用”的问题可能更加复杂,动辄“全面拥抱”可能是一个非常冒险的行为。
灾难六:超时、重试和弹性
这个似乎是服务间调用的普遍情况,我不确定这是微服务的锅。
结论
在实施转型或者说改造的过程中,难免会遇到这样那样的问题,然而微服务其实并没有什么特别的——就特别多、特别碎。如果能在全局层面做好观测、做好治理,练好基本功,让每个服务都能各司其职又不互相妨碍,是不是微服务又能有什么关系呢。微服务是为业务服务,同现状和解的,抛开目的和现实,单纯为微服务而微服务,很可能除了话题,一无所获。
参考阅读
- 我曾目睹的微服务灾难
- Disasters I’ve seen in a microservices world: https://world.hey.com/joaoqalves/disasters-i-ve-seen-in-a-microservices-world-a9137a51
- Segment:为什么微服务适合我们
- Segment 放弃了微服务