顾宇:成功的微服务应该具备哪些技术?

766 阅读21分钟
原文链接: mp.weixin.qq.com

本文内容源于是我在 2018 年北京 DevOps 国际峰会上的分享”成功的微服务应该是什么样”。PPT 可以在[这里下载] (https://pan.baidu.com/s/1jSleh_UxXpqI_oXOwcqf1w)

在上一篇文章里,我们介绍了如何定义一个微服务改造的成功,并介绍了落地成功的微服务组织结构有哪些特征。这篇文章我们来介绍一下成功的微服务的技术特征以及我们在微服务落地中总结的经验。

成功微服务落地的技术特征

很多情况下,我们看到的微服务案例往往是一个结果,但缺乏过程。使得我们认为做到同样的架构结果就算是成功了。

这是非常危险的想法,一个架构成功的关键一定是符合当前的组织结构并且让业务持续运转,无论是哪一种架构风格都不可能脱离这个基础。

所以,下面介绍的几点往往是我们判断微服务成功的标志。然而,这些微服务所具备的特征不一定会给你带来成功。一定要注意度量采用这些技术的场景和代价。

混合架构的微服务架构

如果需要我总结一条技术经验的话,就是不要迷信任何微服务带来的技术。而是通过度量,找到合适当前组织的技术。

下图就是一个微服务应用的示例,曾经是多个集成的单体应用的大型系统。一般而言我们会做一个分层的架构,我们会做一个分层架构,最上面是UI,最下面是操作系统。这些应用是采用不同的编程语言和框架做的混合架构架构。

我经历过的微服务案例没有百分之百的微服务架构,都是混合型的系统,也就是系统的一部分是微服务架构的。而微服务采用的比率取决于微服务的投入产出比。

我们可以做一个简单地估算,比如说我能用三个团队,一个月的预算,然后做这一件事情,我能带来多大的产出或者接受多大的成本。

你会发现有些微服务值得拆,有些微服务不值得拆,拆了之后反而会增加应用系统的维护成本。

如果你不惜代价,一定要把这个架构变成百分之百微服务架构,你要明白这些额外的开销。

我们做的微服务的架构有可能是微服务,有的可能不是微服务,它是分多种语言组成的。每一个应用就是一个代码库,这样管理起来非常的清楚。

我遇到的很多想采用微服务的团队往往纠结于自己“是不是”一个微服务架构。而我觉得你可以在架构中先“有一个微服务”,看看它带来的优点和缺点。再考虑如何扩展自己的微服务。

微服务平台

大型应用程序的分布式架构发展阶段如下图所示:

最早的单体应用(era1)往往是运行于 Java 中间件上的一个 JSP 应用,我们的应用当时也是这样,其中还混杂了一些 JSP。这是大部分遗留系统的架构。

后来随着移动端和分布式的需要。我们做了一个前后端分离,采用 Restful API 作为后台,向 PC 浏览器和手机 App 提供数据交互。这样,无需为移动应用单独编写后台应用,只需要复用之前写好的 API 就可以了。这就是当前很多应用进行微服务改造的第一步(era2)。

到了后期,我们发现有些需要很多 API 需要进行转化,所以我们当时做了一个叫做 Syndication API 的东西,它实际上一个一个 API 的集合。通过反向代理重新暴露后端的数据和接口。这当时是为了能够给 Mobile 端提供数据所准备的过度方案。

后来我们发现,完成一个功能需要给 Web 和 Mobile 端做重复的开发。所以,我们决定在前后端分离的基础上逐渐替换掉老的 Web 应用,即便它运行的很稳定。

为了降低风险,我们就再一次对 Syndication API 进行了拆分。把一个单体 API 集合根据 LoB 功能的依赖程度拆分成了 API 的组。

这样,我们可以在用户端无感知的情况下修改后台的部署架构。这时候,虽然没有做到微服务,但我们通过各自的 API 分离出来了完整的业务并在遗留系统之间创建了一个适配层隔离风险。以后我们只需要写代码替代原有的逻辑就可以了。

这个架构就比之前的架构更进一步。如果你采用了云或者基础设施即代码技术,就可以实现应用的自动化部署。这个就是微服务最早出现的样子。

后来,出现了 Docker。也就是上图中的第三代微服务技术栈(era3),我们采用 Docker 把应用通过容器封装,并统一暴露 Restful API。我们会把前后端应用分到容器里运行,这样从运维的角度来说就会叫方便。

但数据库的拆分还是风险比较大,尤其是你系统很大,而且数据库结构设计不是很好的时候。

到了第四代(era4),我们会做一个容器平台。采用容器来部署所有的应用。我们会在这个平台上去做前端、后端里面只有一点点代码我们把它放在平台上运行,所有的状态都是独立的。

但注意,一个容器平台仅仅完成了容器的生命周期,它离微服务的生命周期还有一定的距离。

因此,我们还需要在容器平台上做微服务的生命周期管理,这也是现在走在微服务改造比较前沿的公司所努力的方向。

独立存储/混合存储

成功的微服务的另一个特征就是数据库可以进行拆分和按需扩展,这样你可以独立维护。

但是如果你的数据库性能足够好或者你数据库结构并不是很好,你可以保持这种方式。

并不是说数据库的拆分是必须的。刚开始,我们往往会采用上图左边这种单一数据库,多微服务访问的形式。到后来,我们就会把它拆分成右边的形式。

在数据库拆分的时候,要注意数据的冗余和一致性问题。为了提升效率,独立数据库里的适当冗余是必要的。

但是,如果为了避免冗余,而不断的跨库,跨 API 做查询。很有可能你的微服务拆错了。

从数据库的查询频率和性能来进行数据库的拆分和重组也是拆分微服务的技巧之一。

一般的原则是:“先拆表,后拆库。

关联查询先拆分后合并”。

这会是一个反复校准的过程,很难一次成功。

另外,我比较推荐把关系写到应用逻辑里而不是数据库里,这样就可以减少一些底层上的依赖。可以隔离数据库管理集中带来的连锁问题。

轻量级服务间通信

微服务服务间通讯是避免不了的场景。然而,如果你的微服务之间的通信过于频繁或者性能很低,可能你需要重新考虑一下是不是需要拆分。

最早的时候我们采用 ESB 传输数据,后来我们发现 ESB 会带来额外的副作用。

后来我们就把 ESB 换成了 RPC 调用去从底层实现分布式系统的消息传递。

这时候我们发现 RPC 虽然帮我们解决了一些依赖的问题,但系统组件之间的关系更加复杂了。

RPC 相对来说比较底层,调试也相对困难,很多情况下它又都会演变成一种外部耦合。

有时候我们也会采用数据库的表来进行消息传递,这实际上和我们上述介绍的数据库拆分类似,也会变成一种底层的耦合。

从耦合效果上来说,和 RPC 的表现并无二致,有时候也会有一些数据库的优势,而 RPC 处理不好就会出现同步造成的阻塞。

微服务倾向于统一的数据交流格式和异步的调用,减少了阻塞的发生并提高了系统的性能。

于是我们就换成了消息队列和 API 调用。而消息队列和 Restful API Call 本身就算是一种系统拆分的重要方式。

它把系统的内部依赖转化到了外部。但即便是用了消息队列和 Restful API,也会出现阻塞的情况。

这里需要说明的是,单从性能和吞吐量来说。几种方法可能差不多,甚至底层的方法更好。

但这里的要点是,我们通过消息队列和 Restful API 的方式是把内部的依赖暴露到了外部,使得内部的“暗知识”可以充分暴露。减少了系统的风险和修复时间。从这个角度说,“文件接口”的吞吐量和稳定性也是一个可以采取的方案。

而如果你的微服务通讯太频繁了并带来了额外的风险和成本,有可能你的微服务就已经拆错了。

在这种情况下,你可能需要考虑合并微服务。这取决于系统的性能和稳定性,也取决于你的维护成本。

一个成功的微服务拆分,服务间的依赖会少。与此伴随着应用系统内的同步调用减少,异步调用增多。采用异步调用取代同步调用也可以看作是微服务拆分的一种方式。

另一方面,一个成功的微服务拆分也带来了开发的独立性,由于你的服务是异步调用的,应该可以独立部署和发布,不依赖其它流程。如果你的微服务拆分之后,开发的流程仍然很长,就要考虑组织流程上的拆分了。

微服务全链路监控

上图这是我们采用 NewRelic 做的微服务的监控,可以看到左边是一些业务系统,接着中间的微服务,最后右是数据库。为了保密,我把微服务的名字用蓝色的框覆盖了,你可以看到。这是一个 NodeJS + PHP 和 MySQL 的混合架构。

你的微服务运维也需要有微服务相互的健康检查的连接图。以前可能只需要关注一个应用的表现。而到了微服务架构后,你需要关注每一个节点。

只有一条依赖上的所有服务都健康了才算健康,但是中间如果有一个不可用的话,哪怕它的失败率很低,如果你的依赖链很长的话,你的应用的健康度就是这些节点的可用率相乘的结果。

比如你有三个依赖的微服务,他们的可用率都是99%,那么业务的可用率就是 99% 99% 99% = 0.970299,也就是 97.02%。但三个微服务如果不相互依赖,它们的可用率仍然是 99%。所以,服务间依赖越多,系统的风险越高。这也从数学的角度上证明了微服务的优势。

当然。我们在考虑的时候不能考虑单个应用的成功性,我们考虑的是这一块集群的失败率做整体监控,得到系统监控的可用率。

当原先的系统内部依赖暴露到外部之后,运维的工作就不仅仅是关注之前的“大黑盒”了,这就需要开发和运维共同合作。这也是微服务团队必须是 DevOps 团队的主要原因。

我们做微服务最简单的路径一般来说都是先做投入产出的分析,然后可能做前后端的分离,并做到前后端的持续交付。

这里的要点是需要采用自动化的方式做好基础设施治理。如果没有 DevOps 的组织这一点就很难。

我们通常的做法是给微服务单独开辟一套新的设计,并把微服务团队单独剥离,因为这是两种不同的文化和流程。最后做到全功能的 DevOps 团队和微服务模板。而这些基础设施的自动化是可以复制的。

我们就可以依据这个方式把成功的微服务不断地复制,就有了以上介绍的成功。

微服务落地反思

总结了这么多,以上仅仅是我在我所经历的微服务改造项目里的观察。然而,一切并没有看上去那么美好。我们在得到以上的结果中间也走了很多弯路,踩了很多坑,下面就给大家分享一下这些经验。

按需拆分微服务

我们的客户用了5年把整体系统的微服务化推进到了60%到70%左右的比率就不再继续了。是因为找不到进一步微服务化的理由。

一方面是进一步做微服务的投资收益并不显著,另一方面是因为其它的系统要么是由对应厂商在集成,要么在准备迁移到 SaaS 化的应用上。

无论如何,都是再降低应用系统的风险,减少维护系统正常运行的开支。

就像之前说的,你要做微服务的时候先不要急于你“是”一个微服务的架构。而应该在你的系统里先有一个微服务。然后看看看这个微服务带来什么样的效果,做好各方面的度量,它的投入是什么?收益是什么?你需要花更多的成本管理中间的问题?而且你要面对一些不确定的风险?

所以,如果你一开始做微服务的话,你可以考虑一下先成立一个小的微服务团队,然后感受一下。

微服务的合并

我的经验是,如果你开始做微服务的合并了。说明你的微服务实践已经到了一个成熟度。你开始考虑什么时候该拆,什么时候该合。

我们曾经有一个子系统,它因为组织变动的关系,会在两三次的变动中划给不同的部门。划完之后我们微服务就是拆了合,合了又拆,拆到最后一次拆,前后拆合了3次。

在做微服务拆合的时候心里要牢记康威定理:

你的系统结构跟你的组织结构是一一对应的。这样,你就可以从宏观的角度发现问题,也知道哪些是难以逾越的“音障”。

另外一种情况是由于性能或者保证事务完整性做的合并,这往往是由于在设计的过程中没有考虑部署架构。因此,为了避免这些问题,提前了解并规划好部署结构是十分重要的。

六边形架构

你能利用六边形架构绘制你的架构图吗?在领域驱动设计里,“六边形架构”又被称之为“适配器-端口”模式。如果你的团队去换架构图的时候,能用六边形架构去画你的架构图,那么你离微服务已经不远了,只是没有意识到或者没有自动化部署而已。

对齐“统一语言(UBIQUITOUS LANGUAGE)”

接下来我要说的这个案例就是对齐领域语言。我们的客户是做搜索引擎和网络广告投放的,它的收入来源就是广告。

在国外,对于客户来说,价格是含税的。而对于内部计算统计来说,报价是不含税的净收入。这个比率大概是在10%左右。然而,由于我们都采用统一的单词 Price 来作为系统之间交换数据的字段。因此,系统就把内部的不含税价格暴露给了客户。

当我们发现这个问题的时候,这个系统已经运行了三年。也就是说,这个系统在这三年里每年都损失了 10% 的收入帮客户缴税。

整个过程是因为我们在帮这些系统增添自动化集成测试案例的时候,发现这个自动化测试失败了,然后就发现了两个系统之间价格的不一致性,虽然都是价格(Price),但一个含税,一个不含税。

当你去和不同的系统进行集成的时候,你会发现对齐统一语言非常重要。这也是我们在做微服务架构的过程中会发现的一个问题。由于程序员不懂得业务,就会导致这样的问题。所以你需要有一个领域专家在不同的系统集成时对齐领域语言。

后来,我们把“价格”这个单词在不同系统里用两个不同的单词来表示。这样我们就知道在进行价格计算的时候需要重新考虑它是否含税。

然后,我们编写了一些自动化测试来保证计算正确。一旦税率修改了或者规则修改了,我们就知道在哪里去修改相应的业务逻辑。

不要过早的开发出统一化的工具

统一化的工具是一把双刃剑。

作为程序员总是喜欢不断重复造轮子来自动化,然后就会做一些工具加快自己的工作效率。但是当你去做统一化的工具的时候,你会要求每一个团队都使用这个工具,就会造成新的依赖。

而且,这个工具未必没有开源的成熟解决方案。如果你要开发一个工具,你需要投入一个人,甚至一个团队来维护它的时候,很可能就会进入“黄金锤”反模式。

而在微服务化的初期,通用场景识别的很少的情况下,它的场景是有限的。一旦场景出现变化,统一化工具就会变成一个阻碍。到后面场景越来越复杂的时候你需要不断修改工具,保障向前兼容的同时还要向后兼容。

你可能会发现你开发微服务的时间要远远小于你处理统一化工具问题的时间,你就要额外花费成本。

我们的客户就开发了一个结合 AWS 发布 Docker 应用的生命周期管理工具以及对应的微服务模板。

但是随着场景的增加,这个工具变得越来越复杂。很小的部署诉求都要增添很多无用的组件和配置。

于是我把这个工具去掉,采用最简单的 AWS 原生方式来部署应用。

如果你做了一个统一化工具,那你就把它开源推广。如果你做一个工具不去推广,你的工具会被其它成熟的开源工具取代,你的研发投入就浪费了。

如果你开源效果好,说不定成为了业界的某一标准,就会有很多人帮助你来维护这些工具。

我听说过某互联网电商大厂的一个故事:由于 Docker 化的需要,就自己研发出了一套很先进的容器管理平台,但是就憋在自己手里,不开源。

后来的故事就是,它们把自己辛苦研制的容器管理平台换成了 Kubernetes

所以当你一开始做这种工具,就要考虑它的研发成本和替换成本,减少这个成本的一个方式就是开源。

在全球范围内,我们去编写软件构造技术壁垒是没有意义的,因为总会有一个人把好的方案开源出来。然后成为大家竞相模仿的对象,而后形成事实上的标准。

在这种情况下,如果你的团队有想开发统一工具的一点点动向,请首先考虑业界有没有成熟的开源方案。造轮子、卸轮子和换轮子的成本都很高。软件行业发展这么多年,天底下并不会有特别新鲜的事情。

越来越厚的微服务平台

统一化工具的另一个方面就是微服务平台。我们的客户成立了“熊猫团队”(PandA,Platform AND Architecture 平台和架构团队)来降低微服务的部署和发布的门槛。这往往是由于 DevOps 的“二次分裂”产生的。

当 DevOps 的应用越来越广泛之后,开发和运维的边界从模糊再一次变得清晰。使得开发应用越来越快,越来越标准化。另一方面,运维部门会把所有的服务都 SaaS 化并提供统一的规范来降低管理的成本。

随之而来的是另外一个问题:当我们使用流程、工具,应用更多平台的概念的时候。我们会发现,整个工作流程可能变得不再不敏捷了。在敏捷里,我们说个体和互动高于流程和工具。在这个情况下,我们会增加越来越多的流程和工具,从而减少个体和互动。

我们在之前强调敏捷的时候,认为敏捷宣言的的左项和右项是对立的。但是现在我们回顾敏捷宣言,我们可不可以两者同时有?既提升个体和互动,又有流程和工具。从这个角度想的时候,我们就会创造新的工具、新的方法论解决它的矛盾。

这就是我所说的后 DevOps时代,我们运维团队变成内部的平台产品团队,它在给开发团队提供一个基础设施产品。而开发团队变成了一个外部的应用产品团队,它在给用户提供一个满足业务需求的产品。彼此独立但又保持 DevOps 生命周期的完整性。

最后

以上是我对这个客户 5 年来微服务改造过程中的部分观察,作为微服务改造的亲身经历和见证者,我有幸观察到了一个组织是如何通过微服务从内而外发生变化的。

在我的经历里,微服务是 DevOps 深入的必然结果。当我们的部署和发布遇到了瓶颈,就需要分离关注点和风险点,调整应用的架构以进一步提升 DevOps 的反馈。

当我们开始实践微服务的时候,首先得先统一微服务的认识,让大家对微服务有统一的理解。

其次是了解我们为什么要做微服务?微服务解决了你的什么问题,而不是陷入了盲目的技术崇拜中。

说明:本文为顾宇老师在 DOIS (DevOps 国际峰会)北京站“微服务与高可用架构”专场的分享整理而成,经顾宇老师审阅授权发布。

看完顾宇老师这篇文章DevOps 的重要性不言自明!期待顾宇老师下次精彩的演讲!

11月3日 JUCC,顾宇老师将会带来“云原生时代 DevOps 的新实践”精彩分享,点击阅读原文 ,速速订票!

点击阅读原文,立即订票