我不是唱衰微服务

梁桂钊 2019-09-26 11:16:00
微服务架构现在已经成为了企业应用架构的必聊话题,本文沉淀了作者多年工作的所见所闻和实战思考,跳出纯技术的视角去思考架构,去看待微服务,保证利用现有的技术(工具)实现业务价值的最大化。

 

一、逃离单体系统,拥抱微服务?

 

单体系统和微服务的区别在于,一个单体系统是一个大而全的功能集合,每个服务器运行的是这个应用的完整服务。而微服务是独立自治的功能模块,它是生态系统中的一部分,和其他微服务是共生关系。

 

现在,业界对单体系统和微服务的普遍观点是:单体系统非常容易开发、测试、部署,但是单体系统面对的问题也很多,例如开发效率变低、维护成本增加、部署影响变大、可扩展性较差、技术选型成本高,而引入了微服务可以实现:

 

  • 每个微服务易于开发与维护,便于沟通与协作,很适合小团队敏捷开发与持续交付;

  • 每个微服务职责单一,高内聚、低耦合;

  • 每个微服务能够独立开发、独立运行、独立部署;

  • 每个微服务之间是独立的,如果某个服务部署或者宕机,只会影响到当前服务,而不会对整个业务系统产生影响;

  • 每个微服务可以随着系统规模的不断扩大,面对海量用户和高并发,独立做水平扩展与垂直扩展;

  • 每个微服务可以使用不同的编程语言以及不同的存储技术,使得我们更容易尝试新的技术;

  • 此外,对单个服务进行业务重构,也不会面临很大的业务负担与技术债券。

 

 

笔者对微服务系统的观点是,我们从单体系统向微服务系统改造的过程中,需要认真思考什么阶段使用微服务。微服务不是银弹,它对于设计和运维难度提出了更高的要求,同时也带来了一些技术的复杂度。

 

因此,我们需要思考与解决分布式的复杂性、数据的一致性、服务的管理与运维、服务的自动化部署等解决方案。

 

事实上,微服务通过拆分单体系统使其成为多个体积更小的服务来降低单个服务的复杂性,但是,我们从整体来看,这种方式造成了存在大量的服务,而服务之间的相互调用也会增多,从而导致整个系统架构变得更加复杂。

 

我们经常忽视业务价值和成本考量,而太过追求技术,那么可能会导致我们精心设计的分布式架构严重影响我们开发的速度和业务的快速迭代,并且随着业务的不确定性往往导致我们的架构在半年到一年之内就已经不完全适用,推倒重来。

 

此外,如果业务没有发展起来也会导致前期大量的服务器资源盲目的浪费了,这对于初创业务得不偿失。因此,我们在项目前期为了保证快速增长业务,保证系统尽量减少依赖且独立完整,减低引入微服务架构后的技术复杂度,例如它对于运维难度提出了更高的要求,因为好的微服务架构需要稳定的基础设施。

 

随着业务发展良好,系统规模会不断扩大,它的扩展性、伸缩性、可用性和性能都限制了我们的业务发展,此时,我们怀着明确的业务思考和投入更多的资源再来考虑微服务改造。

 

微服务架构使用服务作为模块化的单元,那么,我们可以在前期设计的时候通过 Maven 的 module 模块化来初步隔离依赖,为我们之后的改造预留空间。

 

注意的是,微服务在生态系统中是共生关系。这里,不仅仅局限在它们可能存在链路依赖,同时它们的业务价值一定是共生的。因此,后期识别出单体系统的核心价值、关键功能,再把这些功能拆分成独立且自完整的模块。

 

这里,改造方案可以阅读笔者的《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud 和 Service Mesh》一书的第十二章 “遗留系统的微服务架构改造”。

 

总结一下,微服务通过拆分单体系统使其成为多个体积更小的服务来降低单个服务的复杂性,但是,我们从整体来看,这种方式有造成了存在大量的服务,而服务之间的相互调用也会增多,从而导致整个系统架构变得更加复杂。因此,我们不单单只关注技术,而需要考量投入产出比,保障当前阶段的利益最大化。

 

二、摆脱单体系统就远离大泥球?

 

单体系统让很多人诟病的是其服务内聚混乱,看起来就像一个大泥球。那么,服务化之后,就解决了这个问题了吗?

 

事实上,微服务通过拆分单体系统使其成为多个体积更小的服务来降低单个服务的复杂性,让单个系统看起来更加的职能清晰,但是,整个系统架构变得更加复杂。事实上,生产环境的多服务之间的调用可能如图所示的场景。

 

 

通常情况下,生产环境的微服务生态比上面的案例复杂的多,可能存在几十个到几百个的服务。那么,对于我们而言,如何系统地梳理服务之间的依赖关系和链路关系就显得非常重要。尤其在大促的时候,需要对于核心链路进行强保障,这个工作就显得更加重要了。对此,我推荐通过 APM 的流量采集实现自动化链路梳理。

 

此外,我们在设计架构,每当有服务粒度的划分问题,例如新项目的创建,或者对于服务边界模棱两可的时候,我们需要对服务边界讨论清楚,尽可能让我们的服务保持内聚性。

 

三、迁移微服务能提升系统健壮性?

 

这里,还有一个主流的观点:一个单体系统是一个大而全的功能集合,如果某个服务出现故障,会对整个业务系统产生影响,然而使用微服务可以实现如果某个服务部署或者宕机,只会影响到当前服务,而不会影响到整个业务系统。

 

事实上,这个观点看起来非常正确,但是在真实的业务场景下,并不是推动我们改造的关键原因。首先,一个单体为了避免单点故障,肯定需要集群和负载均衡,注意的是,集群和负载均衡和微服务(服务垂直拆分)不是互斥关系,而是在高并发和分布式中的共存关系。

 

此外,为了解决服务部署,我们可以考虑通过滚动发布来实现服务的无中断。所以,单体系统不一定就是不健壮的。同时,引入了微服务之后,从整体来看,这种方式有造成了存在大量的服务,而服务之间的相互调用也会增多,从而导致整个系统架构变得更加复杂,某个链路上的某个节点出现故障的几率就大大的增加了,更多的依赖也意味着发生更多问题的可能。

 

此时,假设其中某条调用链路上某个微服务宕机而无法提供服务,那么对其强依赖的上游服务,如何保障其自身可用性?(我在这里特指强依赖调用,服务降级或者熔断机制可能会对业务有损,并不是解决的有效方案。)因此,很多场景下,某个服务宕机也可能会影响到整个业务系统。

 

因此,如果我们没有面向失败设计,并且构建一套服务治理的体系,反而会导致整体服务的不健壮性。

 

四、迁移微服务能提升系统性能?

 

那么,什么才是迁移微服务的主要动因?我觉得最主要的利益动因是服务的可扩展性和提升性能方面的考量。一个服务因为宿主机器的限制可能达到了瓶颈,为了更加进一步的压榨机器的资源,拆分服务是一个好主意。同时,我们还可以进一步实现自动缩扩容来调整机器的使用。

 

但是,迁移微服务一定能提升系统的性能吗?我的观点是真不一定。服务化后,调用链路变长,原本的一次 RPC 通信可能变成几次,性能损耗有所增加。

 

例如,异地多活主要挑战之一就是网络时延,跨城市一定会有延时的问题,假设跨地域网络延时可能在一百毫秒以内,一次 HTTP 请求涉及到一两百次跨城市 RPC 调用,那么整个响应时间会增加很多,所以延时带来的挑战非常大。

 

那么,阿里为了解决数据延迟的问题,最早提出了单元化的解决思路,即将让请求收敛到同一区域内完成,单元高内聚,不做跨区域访问,即“单元封闭”。此外,还存在跨服务调用的网络超时问题,通过重试也增加了同步阻塞的隐患性。

 

因此,服务化是牺牲了服务之间链路上的调用性能,来整体提高整个业务系统对于机器资源压榨来提升系统的性能。

 

五、微服务的可用性 = 服务提供的每次调用都是可靠且可用的?

 

对于微服务的可用性,很多人的理解是,服务提供的每次调用都是可靠且可用的。这个观点不太对。事实上,微服务保证其服务的整体可用性。

 

通常情况下,如果服务 A 调用服务 B,如果调用了 10 秒,那么后面的情况可能就会阻塞,间接地,导致了线程池撑爆,导致服务不可用。因此,我们就会采取超时机制来保障极短的时间内完成结果响应,尽可能不出现同步阻塞问题。

 

 

此外,如果服务 B 出现故障,所有调用依赖的服务都将出现阻塞,如果有大量的请求会导致线程资源会被消耗完毕,导致服务瘫痪。

 

事实上,服务与服务之间的依赖性会导致级联传播,从而间接导致服务故障的“雪崩”效应,造成整个微服务系统不可用。为了解决这个问题,熔断机制就有了用武之地。

 

六、微服务中数据库是相互独立且透明? 

 

微服务的一个主流观点是,在每个服务都有自己的缓存和数据库,并且缓存和数据库是相互独立且透明的。因此,共享缓存与共享数据库是不对的。

 

那如果服务 A 需要获取服务 B 的数据怎么办?一般的做法是,服务 B 提供一个获取该数据的 API 接口,而服务 A 通过调用该接口进行业务组装。因此,微服务化之后,服务之间的数据交换都是通过接口来开展的,如果服务 A 越过服务 B 的业务逻辑之间访问服务 B 的数据,其会破坏了微服务之间的数据独立性。

 

 

此时,笔者需要泼一下冷水。凡事无绝对,有几种特殊的场景可能需要共享数据。

 

其一,那就是旧的服务过渡到新的服务的场景,新的服务复用旧的服务的数据库从而到达功能与数据过渡的需求;

 

其二,多个服务之间可能依赖于同一个数据源,例如报表的数据聚合。

 

这种情况下,如果我们单纯的依赖于 RPC 的接口调用很可能会导致偶发性的调用超时,从而导致故障发生的几率更大。那么,解决这个问题的常用套路就是共享数据:

 

  • 要么通过数据冗余的方式进行数据同步,然后基于本地的服务进行边界内的数据聚合;

  • 要么通过抽离数仓方案进行数据的集中化 ETL,然后再对外通过加工好的数据。

 

其三,更加现实的成本问题。事实上,更多的数据库会带来更多的经费成本。很多时候,我们也会从经费成本来考虑问题。我们选择复用原来的数据库表,等待业务价值明确之后,再考虑单独独立数据库。

 

同时,共享数据技术方案可避免数据之间的上下文不明确的情况下代价高昂且重复的数据迁移,并可在需要时更轻松地调整服务粒度,然后在服务粒度稳定之后再进行数据迁移。因此,我们要在两者之间寻求适当的平衡,尽可能遵守微服务的主流观点,充分利用微服务带来的好处。

 

七、组织保障微服务的实施?

 

微服务对于组织结构提出了新的要求,它建议将大团队拆分成为多个小团队,而每个团队各自运维开发和运维一个或多个服务,并且需要流程上持续交付、持续部署、DevOps。

 

 

不同的服务可能由不同的团队开发与维护,实际场景下,微服务的便利性更多的在于团队内部能够产生闭环,换句话说,团队内部可以易于开发与维护,便于沟通与协作,但是对于外部团队就存在很大的沟通成本与协作成本。

 

如图所示,团队 A 对于服务 C 的了解是一个黑盒,我们不知道它是单体服务还是微服务,它部署了几台服务器,需要依赖哪些下游服务,是否存在限流、熔断和降级策略,以及如何接入。如果我们需要确认这些问题,通常情况下,都需要人工协作和确认。

 

 

当然,这个是组织分工带来的不可避免的问题,那么我们尽可能保证我们自己团队内部的服务的内聚性,围绕业务模块进行划分,保证微服务具有业务的独立性与完整性,尽可能少的存在服务依赖,链式调用。

 

这里,又抛出了一个新的问题。微服务有多“微”?事实上,对于服务的拆分并非越小越好,甚至极端的案例是把一块功能拆分成一个服务,这种做法是不对的。

 

因此,拆分粒度应该保证微服务具有业务的独立性与完整性,服务的拆分围绕业务模块进行拆分。如果单独拆分成服务的业务价值 / 技术价值不明确,那么就让它耦合在这个单体系统中,在整个项目的生命周期里已经足够了。如果随着业务的发展与需求,我们可以随着调整系统源码层次上模块结构,并将其拆分成独立的微服务。

 

有的时候,团队对项目具有绝对的所有权,从而因为团队利益上的考虑而出现生产上的微服务是一个“半成品”。笔者相信这种情况并非个例,而是绝大多数的常态。

 

现在,我们来看一个案例。

 

团队 A 考虑到功能的复用性而开发了一个“互动组件”,其中包括 “评论模块”功能。此时,团队 B 并不知情也开发了一个类似的“互动组件”。而团队 C 也有这个需求,它知道团队 A 有这个“互动组件”,希望可以复用,但是由于这个“互动组件”在设计的时候更多地考虑了团队 A 的当前业务,没有很好的复用性,例如不支持“评论盖楼”功能,而由于团队 A 出于当前其他项目的进度原因无法马上提供支持,团队 C 评估后决定花一周时间自己开发一个符合自己业务需求的“互动组件”。此时,各个项目团队各自维护了一个“互动组件”。

 

这个案例中,由于团队之间的职责与边界导致了服务的复用存在局限性,甚至造成各自为战的局面,这种情况一般需要公司层面进行规划和统筹。

 

无独有偶,团队 A 和团队 B 都在做工单系统,但是两者需要融合,为了保证两个团队的既有利益,他们并不是将原来的架构打破进行融合,而是在原有的基础上确定领域边界。

 

此外,假设外部一个 RPC 接口不太稳定,一般的做法就是去分析不稳定的原因,但是跨团队合作的情况下,外部服务可能就是一个黑盒,并且团队之间可能就是一堵隐形的墙,那么沟通协作成本是非常大的,此时需要有人来全链路牵头让大家的利益达成一致。

 

那么,当下最高效的方式可能就是团队内部通过其他手段来规避这个问题,例如冗余缓存或者接口适配。因此,它也许就是当前组织结构和环境下业务价值的最大化的较优方案,我们需要适应当下,展望未来。说到接口适配,其实还有一个非常常见的微服务架构设计:适配器服务模式。

 

通常情况下,外部服务给我们的消息体格式和我们所需要的不一致,此时,我们去推动他们改造显得不太现实,那么,为了保障我们的业务逻辑不引入大量的业务适配逻辑,我们就会引入适配层 (适配器服务),它将外部服务的消息体适配成统一的标准格式,然后再向上暴露服务,例如退款适配、物流适配等。

 

因此,公司组织在很大程度上影响了服务边界的确定。通常情况下,我们在自身团队的边界内做领域划分,尽可能的满足业务需求,虽然从技术层面来看这个是一个“半成品”,但是它也许就是当前环境下业务价值的最大化的较优方案。

 

所谓分久必合,当大家看到它有足够大的价值后,在考虑进一步融合也是一个不错的主意。

 

写在最后

 

最后,我们再来谈谈引入新的技术,给项目带来技术红利。

 

一个新的技术需要考虑学习成本和维护成本,以及可用性保障和可运维性。

 

例如,我在公司在运维的护航下,我可以轻松自如的使用各种技术等,但是,我不一定敢在另外一个公司使用 MongoDB,因为我知道我并不是这方面的运维专家,如果出现问题,我可能没办法解决。那么,引入一个新技术可能存在的技术风险。

 

很多时候,我们要基于失败设计,这恰恰是初级工程师和资深工程师之间的差距。

 

例如,Redis 实现分布式锁,很多人都只想到来如何通过代码实现这套逻辑,但是,如果 Redis 集群中主服务挂了,直接切换到从服务,因为是主从异步同步,而分布式锁讲究的是一定是最新的锁数据才管用,就是在一瞬间才起作用,这时候丢了分布式锁数据,你的业务就会造成重复请求,而分布式锁如果应用在了业务中,必须是非常重要的场景,尤其是金融和支付,所以单点版 Redis 分布式锁不是好方法,不能使用,如果要用,就得解决稳定性问题。

 

引用自《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud 和 Service Mesh》作者:程超

 

这里,小小的偏题了下,回到正题,我们会经常发现新项目尝试使用新技术,而老项目更加保守,因为前者试错成本更低。有趣的是,新公司对技术的发展更加敏锐,例如很多小公司在云原生方面有诸多实践与落地。

 

此时,你可能大概明白了我表述的观点:通常情况下,技术栈的使用背后是公司的运维保障,以及对技术深度的把控力。所以,我们需要对新技术有提前的储备,以备随时上战场,但是绝大多数情况下,我们要保证利用现有的技术(工具)实现业务价值的最大化。

 

总结一下,本篇文章沉淀了很多我在工作以来的所见所闻和实战思考,核心观点并不是唱衰微服务,而是让大家保持独立思考,跳出纯技术的视角去思考架构,去看待微服务,要保证利用现有的技术(工具)实现业务价值的最大化。

 

作者丨梁桂钊,《高可用可伸缩微服务架构:基于Dubbo、Spring Cloud和Service Mesh》联合作者
来源丨高效开发运维(DevOpsGeek)
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
活动预告