业务真的需要微服务吗
要说过去十年最火热的软件体系是什么,个人认为莫过于“微服务架构“了。从一线互联网架构师,到刚接触计算机软件不久的学生几乎都或多或少的了解过”微服务“相关知识了,其中在最出名的微服务体系要数 spring cloud 了。
在以众多互联网大厂为代表的微服务的宣传下,微服务架构的洪流也席卷到了各个中小厂商。但在部分场景下,微服务架构仅仅是因为有大厂背书,业务开发团队为了致敬大厂而选择了微服务架构,但恐怕有时候真的没有多维度对比过微服务架构和MVC 架构对各自业务场景的匹配度,因此现在部分团队在小范围实践“去微服务化”。说到这里,大家是不是也想到了最近两年呼声逐渐高涨的“下云”和“去中台化”,真可谓铁打的营盘,流水的技术。
回到正题,下面笔者会结合自己的工作体来分析一下微服务架构中大家容易忽视的部分,以帮助开发者更好的评估业务系统架构是否真的微服务化。
微服务概览
要理解微服务,首先需要了解微服务软件架构的演变。
单体架构
在早起的软件中,无论是前端还是后端,所有的功能都是放在一起的,这称为单体架构,也叫做巨石架构。顾名思义,整个软件系统就是单一的整体,仿佛一台一体化的机器。尽管软件内部也是模块化的设计,但在最终打包部署时是一个应用,如下图所示:
这里也可以想到,软件的功能越多,单体架构就会越复杂,很多缺点也随之暴露出来:
- 所有功能耦合在一起,难以管理
- 新功能迭代或 bug 修复,即使只修改一行代码,也需要重新构建和部署整个系统,成本非常高
- 因为软件是一个整体,因此很难为每个功能开发和测试,只能整体开发和测试,导致基本必须采用瀑布式开发模型
- 此外,最大的问题遍是当该系统庞大后,任何单个开发者几乎不可能完全搞懂这个系统,扩展性很低、可靠性很低。
总之,单体架构的大型软件,不仅开发速度慢,而且会形成难以维护和升级的复杂代码,成为开发者的沉重负担。
面向服务架构
为了解决单体架构所遇到的问题,很早就有人提出必须打破代码耦合,拆分单体架构,将软件拆分成一个个独立的功能单元,每个功能单元可以以远程“服务”的形式提供,因此诞生了“面向服务架构”(service-oriented architecture,简称 SOA)。
"面向服务架构"就是把一个大型的单体服务,拆分成一个个独立服务,也就是较小的服务。每个服务都是一个独立的功能单元,承担不同的功能,服务之间通过通信协议连在一起。
如此看来这种架构有很多优点:
- 拆分后的每个服务功能单一,相当于一个小型软件,便于开发和测试,也可以独立部署和升级
- 各个服务独立运行,简化了架构,提高了可靠性
- 扩展性好,可以容易地为每个独立的小软件加机器、加功能,承受高负载
- 可靠性高,即使一个服务异常,不会影响到其他服务
此外,与单体架构不同,面向服务架构是语言不敏感的,不同服务可以使用不同的语言开发,也可以部署在不同的系统和环境。这意味着,面向服务架构默认运行在不同服务器上,每台服务器提供一种服务,多台服务器共同组成一个完整的软件系统。
微服务架构
“微服务”的定义是这样的:围绕业务功能构建的,且每个服务关注单一业务,服务间采用轻量级的通信机制,可以全自动独立部署,可以使用不同的编程语言和数据存储技术。
看到这里,很多人会问,这和上面提到的面向服务架构(SOA)不是一样的吗?实则不然,SOA 更像是一种理念,而微服务则是 SOA 的一种实践,或许未来还会出现 SOA 的其他实践,因此”微服务“相当于是 SOA 的子集。
但细细想来,这又诞生了一个新的问题,随着一个单体应用被拆分未十几个甚至几十个服务,且每个服务又需要部署多个副本,因此这种架构给运维团队带来了极大的工作量。即使有 Ansible 等一系列的自动化工具诞生,也难以完全满足几何倍数增加的服务数带来的运维成本。直到 2014 年,Docker 的出现,彻底改变了软件世界的面貌。它让程序运行在容器中,每个容器可以分别设定运行环境,并且只占用很少的系统资源。此后,随着 Kubernetes 容器编排系统的出现,微服务架构也逐渐火热起来了。
微服务的陷阱
Fred Brooks 在30年前写道,“there are no silver bullets”。但凡事有利就有弊,微服务架构也不是万能的。细细想来,他的缺点也是非常明显的:
-
服务粒度划分,划分太细则服务太多,单个服务的复杂度确实下降了,但整个系统的复杂度却上升了
-
微服务应用是分布式系统,开发者不得不使用 RPC 或者消息传递来实现服务间通信,还必须要写代码来处理服务间的鉴权、限流、消息传递速度过慢或者服务不可用等局部失效的问题
-
服务模块间的依赖,应用的升级有可能会波及多个服务模块的修改
-
测试微服务架构的应用是一项很复杂的任务
-
微服务应用中各服务调用链路相比单体架构提升巨大,故障排查耗时多,对运维基础设施挑战大
服务划分颗粒度
举个例子,一个微服务架构的商城应用会有多少服务呢?首先,模块要分为账号模块、商品模块、订单模块和数据分析模块等等,如下图所示。如果这么划分,也算是一个小型的微服务了,看起来问题是不大的。但随着业务的发展和用户量的增加,服务肯定会做不同程序的拆分:
- 账号:消费者账号和商家账号功能不同
- 商品:商品店铺和用户搜索、浏览、详情并发数肯定是不一样的
- 订单:实时下单和历史订单查询可用性要求不同
- 数据分析:离线分析和实时分析两种场景
久而久之,服务的数量会快速的增加,而不同的模块可能会由不同的团队负责开发,如果前期没有规范的业务模块,一个新功能开发后划分到具体哪个业务模块下是不明确的,而往往业务开发同学又仅仅关注功能的交付,久而久之,若后期涉及组织架构调整后,管理成本是很高的,甚至还会涉及一些互相扯皮的工作。
以鉴权和限流这两个分布式系统中最基本的功能为例,商品展示时会访问账号模块获取是否为会员,计算相应价格并展示,如果商品模块如果仅有一个服务,账号服务的鉴权和限流相关逻辑是十分容易实现的,即使账号模块有多个服务,我们也可以在框架的 filter 或 middleware 中实现,但当商品功能模块拆分为十几个微服务时,账号模块的认证限流信息即使能在 filter 活 middleware 中实现,其维护的各鉴权信息也是数量很多的。这还只是十几个,如果还需要维护成百个其他业务模块的鉴权信息时,成本是非常大的了。这里可能会想到,如果商品模块中不管多少个服务,统一使用一个认证鉴权密钥是否可以呢,当然可以,但这就又证明了服务模块的划分是非常重要的了,如果前期没有合理的划分,后面很多基础功能的实现成本都会很高。
那么如何合理的划分业务模块呢?其实如果业务前期用户量不大,我们并不一定要采用微服务架构,仅做前后端分离即可,同时保证后端各逻辑间解耦,当用户量增长后,能够快速将各子功能拆分未独立服务。同时每个业务模块应该只有一个统一的被调服务的入口,像 Kubernetes 的 APIServer 一样,与控制平面交互的入口只能有一个,被调用后,再由其将请求具体的逻辑服务。
全链路超时控制
单体应用时代,请求发送到了后端服务,剩余的逻辑基本是内部处理了,如果超时,基本不会涉及大批量的重试等逻辑。但分布式系统中,会存在大量的服务间调用,产生很多的网络消耗,因此对带宽的要求会变得很高,同时超时时间设置的不合理,甚至可能会导致整个系统雪崩。举个例子,现在有 ABCD 四个服务,调用关系、超时时间、重试次数均如图所示:
假设 D 服务自身处理请求耗时 25s,当 A 收到请求并调用 B 时,是会超时并发起重试的,B 同理,虽然 C 访问 D 不会超时,但上游已经超时并发起重试了,而且这里的重试次数是 6 次,D 则会处理 6 次相同的请求,在高并发场景下,这会对 D 造成很大的负载,而越处于链路下游的服务越是基础服务,一旦雪崩会造成极大的生产事故。
因此建议在系统开发初期就确定合理的全链路超时控制的机制。
测试复杂性
微服务架构中,单个服务的变更可能会影响上下游多个服务的稳定性,因此上线前不能仅仅测试该服务的功能,更要和上下游服务联调测试。
故障排查成本
最后,在单体架构时代,故障排查登录少数几台服务器即可,但分布式系统中调用关系复杂,涉及服务众多,如果没有完善的运维平台,如合理且完善的格式化日志、分布式链路追踪等工具,排查问题将会非常耗时,成本很高。