从我接触微服务以来,迄今也得有五六年了。断断续续要么从零开始,要么中途接手,也经历了 5 套微服务项目了。

从这些项目中的经验以及和同行交流来看,根据业务切分微服务的方法总的来说思路不复杂,但是落地总是出现了各种各样的问题。一直到现在,我也还在探索着最好的微服务落地的最佳办法。

在上一篇文章我也提过,一个服务一个数据库是微服务最基本的模式,也谈了为什么要搞微服务。今天这篇文章我想谈谈:

一个服务一个数据库这种最基本的模式落地,大体的做法是怎么样的。

一、搞微服务,可能是个政治问题

我第一次接触微服务的时候,真的是迫不得已。

公司有一套大型系统,这套大型系统当时是负责公司的主要盈利业务,非常非常重要。但是,正因为重要,所以它就成为了产品、业务团队的重点服务对象。这些人天天想着把这套系统的业务做出花来,不断对技术团队提出各种各样的需求。

提出需求不说,还要求技术能快速迭代。一旦不能及时上线他们的需求,产品经理们就会在各种会议上抱怨,说技术团队影响了速度,出现了让竞争对手迎头赶上的风险。

技术团队有口难言,因为系统太庞大了,改动那么大的系统真的很困难。至于原因,我也在上篇文章说了,不再赘述。

出于这些原因,我们决定采用微服务。

什么时候使用微服务?当你的交付时间不够应付产品团队,不够应付运营团队的时候,考虑考虑。还有,我后来搞其他新项目时,领导认为系统太简单,没有自己的技术特色。不得已,我又拿出了微服务,领导看了之后,眼前一亮,直说这个好。

所以,以我的经验看,有时候搞微服务,本质是个政治问题不是技术问题。

总的来说,对于微服务落地,不是特别大型的项目,微服务带来的好处不大,工作量反而增大了许多。

不管什么原因,我后续接触的微服务越来越多了,为了用好微服务,我真的是狠狠钻研了下微服务这套体系架构,也总结了一些自己对微服务分解实践的经验。

  • 首先,如果是预估到业务在飞速增长,那就别犹豫,一定要提前考虑微服务的拆分。
  • 其次,如果在设计架构的时候,发现需要很多异构的技术栈,那也要考虑下微服务。
  • 最后,如果公司技术基础设施非常完备,对应的业务起初就设计的非常复杂,那么也别犹豫,起手就上微服务。

二、迁移到微服务可以很粗暴,也可以很温柔

回过头来,继续说我当时第一次搞微服务的事情。

由于迁移微服务不是一蹴而就的事情,但是我又急需一些微服务的部署简单、开发快速的优点。所以,当时不得已,想了个折中的办法。

我把一些急需实现的业务需求分析了下,发现这些需求大体可以分为以下两类:

  1. 有些需求本身是一套独立的边缘业务
  2. 有些需求是集中在核心业务的边缘上

我后来想想,觉得这是理所应当的。业务和我们技术一样,如果动了核心业务的逻辑,万一出现了问题,他们是要背大责任的。但是他们又要体现自己的价值,那最保险的就是在核心业务的边边角角动些手脚。

知道了这些,那就好办了。

对于第一类独立的业务需求,我直接就设计出一套独立服务,让它和已有的老系统通过网络远程互联。这样的话,新搭建的服务很小,维护也简单。以前的老系统也成为新服务的服务。这样,一部分需求,就可以快速迭代了。

对于第二类需求,原有系统核心边缘的需求,我是这样做的。

  • 首先,我争取了领导的支持,优先对经常被提需求的业务模块做了剥离。这样,就剩下了一些不经常变动的业务模块还在老系统。其实这些时候,系统也没那么大了,也能满足业务偶然提出的业务变动需求了。
  • 然后,我会在后续的时间里,慢慢的抽空把剩下的业务模块没事儿就剥离一些出来。但是,优先级很低。
  • 这样,慢慢的抽丝剥茧,最后,我发现,核心业务我们都没有动,一套微服务体系就已经搭建出来了。

有人可能会比较好奇,你这样剥离,同时存在老系统和新系统。那外面的用户使用会不会受影响呢?

其实,这里还有个小技巧。就是我在拆微服务之前,先搭建了一个代理。这个代理就是专门路由外面用户请求的。每次上线服务的时候,都会对这套代理进行一次微调整。这样搞下来,用户是感知不到背后新老系统并存的状态的。

但是,说到这里,我也要说一下,这个方法真的是比较粗暴的,是实在没办法才选择这种方法。

后来,我再搞微服务的时候,吸取了很多教训。总的方向还是需要优先划分出清晰的业务模块,然后再根据业务模块的划分搞出微服务来。

总的来说,后期我设计微服务架构需要分为两个时期。而在这两个时期,我又采用了不同的办法。我分别来说说。

三、土法炼钢的传统业务划分

在第一次被迫搞了微服务后,我对微服务这个架构开始了自己的研究。我知道了很多技术方面的细节,而如何划分业务,我承认当时自己有点疏忽。所以,后来再有了新项目,我搞微服务的时候,是用的传统业务划分方法搞得微服务。

步骤如下:

第一步:划分功能模块

功能模块划分清楚这事儿其实还好,如果是从零开始的系统,业务尚不复杂,所以模块也很容易划分清楚。

当年,我是如何把微服务落地的-LMLPHP

如果是已有的大项目,那还得看看系统的源码,根据源码和业务文档,把整体业务模块搞清楚。

第二步:梳理功能模块的方法

搞清楚业务模块了还不够,你还需要搞成分开的服务,所以,必定需要把服务之间的联系也给确定好。这时候,如果是从零开始就很好搞了,自己根据业务划分的情况,直接自行创建对应的方法就好。

当年,我是如何把微服务落地的-LMLPHP

如果针对已有项目拆分,那就不好搞了。非得仔细梳理源码,然后根据源码的类和方法,逐次清理出各个模块的之间的方法调用。非常麻烦。

当年,我是如何把微服务落地的-LMLPHP

第三步:对方法进行分类

把梳理出来的所有方法做一次分类,分成两类:功能模块直接对外部用户的方法,功能模块内部之间需要调用的方法。

当年,我是如何把微服务落地的-LMLPHP

第四步:模块映射服务,方法映射 API

方法梳理好了,分类完毕了,这时候得把功能模块映射成服务了,这个过程是必不可少的。功能模块映射成服务往往一开始其实很粗糙,就是先把一个功能模块和服务进行一对一的映射。

但是,就我的经验来说,这么简单的映射几乎是不可能的。总是有各种落地问题迫使你再调整。

当年,我是如何把微服务落地的-LMLPHP

好了,做出了业务模块和服务的一对一映射的假设,咱们也梳理了业务模块的方法调用了。那就把这些方法调用和服务的 API 方法做个一对一映射。当然,这个方法也是很粗糙的,几乎总是存在需要调整的问题。

第五步: 根据实际情况做调整。

最后,就开始根据咱们上面的假设开始微调了,业务模块和服务之间的映射被迫调整,主要因为以下几个原因:

1. 拆分后过多的网络交互引起性能下降

当我们拆分服务后,以前有些业务模块间频繁的方法调用,映射到服务之间,就变成了频繁的网络交互了。

当年,我是如何把微服务落地的-LMLPHP

我们肯定不能任其这样频繁的网络调用。对这种情况,就会有两个办法处理:1. 把服务之间的交互改成批量处理的方式;2. 干脆就不拆服务。

服务之间改成批量处理还好,一旦决定不拆,就影响了以前设计好的映射关系了。

2. 同步调用可能引起的阻塞

还有些时候,以前本地调用搞成同步的方式,其实无伤大雅。因为大家在同一个进程里,处理事件都可以忽略不计。

当年,我是如何把微服务落地的-LMLPHP

但是,如今分家了,搞成了服务之间的网络调用,那事儿可就来了。网络同步调用必须考虑容错和阻塞,所以,对于同步调用这种,也得从两个方面处理:1. 设置超时;2. 搞成异步方式处理。

如果一些同步方法搞成了异步方式,那服务的 API 和以前的方法映射关系可能就要调整了。

例如一个方法得对应两个异步 API:一个是访问,一个是获取响应。

当年,我是如何把微服务落地的-LMLPHP

3. 原来的数据一致性可能要重新考虑

划分服务后,最不好搞得就是数据一致性,而数据一致性这东西往往也避免不了。所以,微服务体系里专门会有套模式来解决这个问题。咱们放到以后的文章里说。

4. 原来有些核心业务类可能和大部分业务紧密关联

一套复杂的业务系统,必定会有一些核心的业务存在。在代码实现里,往往就会是一个字段很多的业务类。

比如电商系统里的订单,这就是个很核心的业务类。它会在很多业务里用到。对于这种类,他们有个专业名词叫做 God 类。

当年,我是如何把微服务落地的-LMLPHP

God 类本身因为和太多的业务挂钩了,等到你分服务的时候,你才意识到,我跋山涉水都快走完拆分步骤了,猛然因为一个 God 类,把哥们儿整的都快不会了。

God 类字段太多,很多业务都需要。

所以,它真的阻碍了很多业务被拆分。而在此时,我尚没仔细领会到领域驱动设计的精髓,所以,没办法,此时我只能把这些个 God 类给单独拎出来弄成个微服务。

但是,这真的是很丑陋的。

首先,这纯粹是因陋就简搞的土法分微服务,它完全没有任何业务。

其次,由于没有业务,所以也就没有方向没有限制,到时候谁想加访问数据的 API 了,就随意加了。

最后,这些 God 类对应的微服务会被很多的微服务模块访问,它的压力非常大,还得为此搞一些集群,得不偿失。

四、换种思路解决难题的领域驱动工具

其实,一路走来,我使用传统的业务划分真没有遇到太多的问题。就是 God 类把我打击的不行,我总是想找个办法去解决它。

当我看了领域驱动设计之后,我明白了,这玩意儿就是换个思路就好了。领域驱动设计其实没啥特殊的地方,但是

它引入了一个子域和限界上下文的概念。

也就这两个概念对我拆分微服务帮助最大。

子域本身其实就是以前的传统手艺,就是拆分业务模块就好。但是呢,它还引入了个思想——不同子域之间的同样名称的专业术语,可能不是同一个东西

而这,就是我解决 God 类拆分需要的办法。怎么解决的呢?就是配合着“限界上下文”这个概念来实现的。

子域和限界上下文听着很玄幻,其实就是传统的业务模块和业务模块对应的服务。只是限界上下文明确指出了,服务包含了实现的代码,他们统称限界上下文。

在领域驱动设计思想里,每个子域间的同名专业术语其实可能是不一样的。而这对应到实现里,就是把原来的 God 类给拆分了,在不同的子域里变成了不同的类,每个子域中的类都包含了以前 God 类中的某些字段。比如,

  • 原来电商系统里的订单类,它以前可能包含了用户、订购的商品、用户地址、金额等等。
  • 但是在支付子域,对应了支付限界上下文,同样有一个订单类,只需要用户,金额这两个字段。
  • 而在物流子域,对应了物流限界上下文,同样也有个叫订单类,可能只需要商品和用户地址两个字段。

所以,通过这种思想,God 类阻碍微服务拆分的问题就被解决了。

但是,在实现上还有个问题没有解决。因为我们对用户来说是一套系统,所以,用户看到的展示信息可能还是对应着原来的 God 类包含的所有字段信息。

比如电商系统,对用户来讲,订单类信息就包含了许多别的信息:商品、金额(支付子域)、用户地址(物流子域)……

而这时候,其实微服务是有自己的 API 网关的,就需要通过微服务网关,把各微服务的数据聚合成用户看到的订单。

同时也是通过 API 网关,会把用户看到的订单转换成各个微服务之间需要的订单信息,在其中不停流转。而这种又是另外的模式了,以后的文章里会详细说到它。

五、还有很多问题

我在这篇文章里说我自己如何拆分微服务的经历。但是呢,微服务并不是想象的那么完美的,它其实还引出了许多新的问题需要解决。

在下一篇文章里,我会谈谈划分服务后,引发的一些问题。

我们下篇文章见。


你好,我是四猿外。

一家上市公司的技术总监,管理的技术团队一百余人。

我从一名非计算机专业的毕业生,转行到程序员,一路打拼,一路成长。

我会通过公众号,
把自己的成长故事写成文章,
把枯燥的技术文章写成故事。
当年,我是如何把微服务落地的-LMLPHP

我建了一个读者交流群,里面大部分是程序员,一起聊技术、工作、八卦。欢迎加我微信,拉你入群
当年,我是如何把微服务落地的-LMLPHP

05-11 22:53