从我接触微服务以来,迄今也得有五六年了。断断续续要么从零开始,要么中途接手,也经历了 5 套微服务项目了。
从这些项目中的经验以及和同行交流来看,根据业务切分微服务的方法总的来说思路不复杂,但是落地总是出现了各种各样的问题。一直到现在,我也还在探索着最好的微服务落地的最佳办法。
在之前的文章我也提过,一个服务一个数据库是微服务最基本的模式,也谈了为什么要搞微服务。今天这篇文章我想谈谈:
一个服务一个数据库这种最基本的模式落地,大体的做法是怎么样的。
一、搞微服务,可能是个政治问题
我第一次接触微服务的时候,真的是迫不得已。
公司有一套大型系统,这套大型系统当时是负责公司的主要盈利业务,非常非常重要。但是,正因为重要,所以它就成为了产品、业务团队的重点服务对象。这些人天天想着把这套系统的业务做出花来,不断对技术团队提出各种各样的需求。
提出需求不说,还要求技术能快速迭代。一旦不能及时上线他们的需求,产品经理们就会在各种会议上抱怨,说技术团队影响了速度,出现了让竞争对手迎头赶上的风险。
技术团队有口难言,因为系统太庞大了,改动那么大的系统真的很困难。至于原因,我也在上篇文章说了,不再赘述。
出于这些原因,我们决定采用微服务。
什么时候使用微服务?当你的交付时间不够应付产品团队,不够应付运营团队的时候,考虑考虑。还有,我后来搞其他新项目时,领导认为系统太简单,没有自己的技术特色。不得已,我又拿出了微服务,领导看了之后,眼前一亮,直说这个好。
所以,以我的经验看,有时候搞微服务,本质是个政治问题不是技术问题。
总的来说,对于微服务落地,不是特别大型的项目,微服务带来的好处不大,工作量反而增大了许多。
不管什么原因,我后续接触的微服务越来越多了,为了用好微服务,我真的是狠狠钻研了下微服务这套体系架构,也总结了一些自己对微服务分解实践的经验。
首先,如果是预估到业务在飞速增长,那就别犹豫,一定要提前考虑微服务的拆分。
其次,如果在设计架构的时候,发现需要很多异构的技术栈,那也要考虑下微服务。
最后,如果公司技术基础设施非常完备,对应的业务起初就设计的非常复杂,那么也别犹豫,起手就上微服务。
二、迁移到微服务可以很粗暴,也可以很温柔
回过头来,继续说我当时第一次搞微服务的事情。
由于迁移微服务不是一蹴而就的事情,但是我又急需一些微服务的部署简单、开发快速的优点。所以,当时不得已,想了个折中的办法。
我把一些急需实现的业务需求分析了下,发现这些需求大体可以分为以下两类:
有些需求本身是一套独立的边缘业务
有些需求是集中在核心业务的边缘上
我后来想想,觉得这是理所应当的。业务和我们技术一样,如果动了核心业务的逻辑,万一出现了问题,他们是要背大责任的。但是他们又要体现自己的价值,那最保险的就是在核心业务的边边角角动些手脚。
知道了这些,那就好办了。
对于第一类独立的业务需求,我直接就设计出一套独立服务,让它和已有的老系统通过网络远程互联。这样的话,新搭建的服务很小,维护也简单。以前的老系统也成为新服务的服务。这样,一部分需求,就可以快速迭代了。
对于第二类需求,原有系统核心边缘的需求,我是这样做的。
首先,我争取了领导的支持,优先对经常被提需求的业务模块做了剥离。这样,就剩下了一些不经常变动的业务模块还在老系统。其实这些时候,系统也没那么大了,也能满足业务偶然提出的业务变动需求了。
然后,我会在后续的时间里,慢慢的抽空把剩下的业务模块没事儿就剥离一些出来。但是,优先级很低。
这样,慢慢的抽丝剥茧,最后,我发现,核心业务我们都没有动,一套微服务体系就已经搭建出来了。
有人可能会比较好奇,你这样剥离,同时存在老系统和新系统。那外面的用户使用会不会受影响呢?
其实,这里还有个小技巧。就是我在拆微服务之前,先搭建了一个代理。这个代理就是专门路由外面用户请求的。每次上线服务的时候,都会对这套代理进行一次微调整。这样搞下来,用户是感知不到背后新老系统并存的状态的。
但是,说到这里,我也要说一下,这个方法真的是比较粗暴的,是实在没办法才选择这种方法。
后来,我再搞微服务的时候,吸取了很多教训。总的方向还是需要优先划分出清晰的业务模块,然后再根据业务模块的划分搞出微服务来。
总的来说,后期我设计微服务架构需要分为两个时期。而在这两个时期,我又采用了不同的办法。我分别来说说。
三、土法炼钢的传统业务划分
在第一次被迫搞了微服务后,我对微服务这个架构开始了自己的研究。我知道了很多技术方面的细节,而如何划分业务,我承认当时自己有点疏忽。所以,后来再有了新项目,我搞微服务的时候,是用的传统业务划分方法搞的微服务。
步骤如下:
第一步:划分功能模块
功能模块划分清楚这事儿其实还好,如果是从零开始的系统,业务尚不复杂,所以模块也很容易划分清楚。
![](https://i-blog.csdnimg.cn/blog_migrate/25fbb593c0ec6403d86463d0dd6c5094.png)
如果是已有的大项目,那还得看看系统的源码,根据源码和业务文档,把整体业务模块搞清楚。
第二步:梳理功能模块的方法
搞清楚业务模块了还不够,你还需要搞成分开的服务,所以,必定需要把服务之间的联系也给确定好。这时候,如果是从零开始就很好搞了,自己根据业务划分的情况,直接自行创建对应的方法就好。
![](https://i-blog.csdnimg.cn/blog_migrate/91fa681b5ab46a1fe8052b018e9a02c6.png)
如果针对已有项目拆分,那就不好搞了。非得仔细梳理源码,然后根据源码的类和方法,逐次清理出各个模块的之间的方法调用。非常麻烦。
![](https://i-blog.csdnimg.cn/blog_migrate/bb2398d1866f7668a162f5d0fe542afc.png)
第三步:对方法进行分类
把梳理出来的所有方法做一次分类,分成两类:功能模块直接对外部用户的方法,功能模块内部之间需要调用的方法。
![](https://i-blog.csdnimg.cn/blog_migrate/8c83588e7c298d560f23035913242685.png)
第四步:模块映射服务,方法映射 API
方法梳理好了,分类完毕了,这时候得把功能模块映射成服务了,这个过程是必不可少的。功能模块映射成服务往往一开始其实很粗糙,就是先把一个功能模块和服务进行一对一的映射。
但是,就我的经验来说,这么简单的映射几乎是不可能的。总是有各种落地问题迫使你再调整。
![](https://i-blog.csdnimg.cn/blog_migrate/e6326b534de4ae869bc63e05338d5122.png)
好了,做出了业务模块和服务的一对一映射的假设,咱们也梳理了业务模块的方法调用了。那就把这些方法调用和服务的 API 方法做个一对一映射。当然,这个方法也是很粗糙的,几乎总是存在需要调整的问题。
第五步:根据实际情况做调整。
最后,就开始根据咱们上面的假设开始微调了,业务模块和服务之间的映射被迫调整,主要因为以下几个原因:
1. 拆分后过多的网络交互引起性能下降
当我们拆分服务后,以前有些业务模块间频繁的方法调用,映射到服务之间,就变成了频繁的网络交互了。
![](https://i-blog.csdnimg.cn/blog_migrate/4e99d89a34df9c086f272dd1c2aa9551.png)
我们肯定不能任其这样频繁的网络调用。对这种情况,就会有两个办法处理:1. 把服务之间的交互改成批量处理的方式;2. 干脆就不拆服务。
服务之间改成批量处理还好,一旦决定不拆,就影响了以前设计好的映射关系了。
2. 同步调用可能引起的阻塞
还有些时候,以前本地调用搞成同步的方式,其实无伤大雅。因为大家在同一个进程里,处理事件都可以忽略不计。
![](https://i-blog.csdnimg.cn/blog_migrate/32198465cf1d46b7a12009d8ce5b8b1e.png)
但是,如今分家了,搞成了服务之间的网络调用,那事儿可就来了。网络同步调用必须考虑容错和阻塞,所以,对于同步调用这种,也得从两个方面处理:1. 设置超时;2. 搞成异步方式处理。
如果一些同步方法搞成了异步方式,那服务的 API 和以前的方法映射关系可能就要调整了。
例如一个方法得对应两个异步 API:一个是访问,一个是获取响应。
![](https://i-blog.csdnimg.cn/blog_migrate/6920eca67d6db9a4a44b3b0c3d93671c.png)
3. 原来的数据一致性可能要重新考虑
划分服务后,最不好搞的就是数据一致性,而数据一致性这东西往往也避免不了。所以,微服务体系里专门会有套模式来解决这个问题。咱们放到以后的文章里说。
4. 原来有些核心业务类可能和大部分业务紧密关联
一套复杂的业务系统,必定会有一些核心的业务存在。在代码实现里,往往就会是一个字段很多的业务类。
比如电商系统里的订单,这就是个很核心的业务类。它会在很多业务里用到。对于这种类,他们有个专业名词叫做 God 类。
![](https://i-blog.csdnimg.cn/blog_migrate/b987e51674d5b6e0f25fa5eeedf3ebd9.png)
God 类本身因为和太多的业务挂钩了,等到你分服务的时候,你才意识到,我跋山涉水都快走完拆分步骤了,猛然因为一个 God 类,把哥们儿整的都快不会了。
God 类字段太多,很多业务都需要。
所以,它真的阻碍了很多业务被拆分。而在此时,我尚没仔细领会到领域驱动设计的精髓,所以,没办法,此时我只能把这些个 God 类给单独拎出来弄成个微服务。
但是,这真的是很丑陋的。
首先,这纯粹是因陋就简搞的土法分微服务,它完全没有任何业务。
其次,由于没有业务,所以也就没有方向没有限制,到时候谁想加访问数据的 API 了,就随意加了。
最后,这些 God 类对应的微服务会被很多的微服务模块访问,它的压力非常大,还得为此搞一些集群,得不偿失。
四、换种思路解决难题的领域驱动工具
其实,一路走来,我使用传统的业务划分真没有遇到太多的问题。就是 God 类把我打击的不行,我总是想找个办法去解决它。
当我看了领域驱动设计之后,我明白了,这玩意儿就是换个思路就好了。领域驱动设计其实没啥特殊的地方,但是
它引入了一个子域和限界上下文的概念。
也就这两个概念对我拆分微服务帮助最大。
子域本身其实就是以前的传统手艺,就是拆分业务模块就好。但是呢,它还引入了个思想——不同子域之间的同样名称的专业术语,可能不是同一个东西。
而这,就是我解决 God 类拆分需要的办法。怎么解决的呢?就是配合着“限界上下文”这个概念来实现的。
子域和限界上下文听着很玄幻,其实就是传统的业务模块和业务模块对应的服务。只是限界上下文明确指出了,服务包含了实现的代码,他们统称限界上下文。
在领域驱动设计思想里,每个子域间的同名专业术语其实可能是不一样的。而这对应到实现里,就是把原来的 God 类给拆分了,在不同的子域里变成了不同的类,每个子域中的类都包含了以前 God 类中的某些字段。比如,
原来电商系统里的订单类,它以前可能包含了用户、订购的商品、用户地址、金额等等。
但是在支付子域,对应了支付限界上下文,同样有一个订单类,只需要用户,金额这两个字段。
而在物流子域,对应了物流限界上下文,同样也有个叫订单类,可能只需要商品和用户地址两个字段。
所以,通过这种思想,God 类阻碍微服务拆分的问题就被解决了。
但是,在实现上还有个问题没有解决。因为我们对用户来说是一套系统,所以,用户看到的展示信息可能还是对应着原来的 God 类包含的所有字段信息。
比如电商系统,对用户来讲,订单类信息就包含了许多别的信息:商品、金额(支付子域)、用户地址(物流子域)……
而这时候,其实微服务是有自己的 API 网关的,就需要通过微服务网关,把各微服务的数据聚合成用户看到的订单。
同时也是通过 API 网关,会把用户看到的订单转换成各个微服务之间需要的订单信息,在其中不停流转。而这种又是另外的模式了,以后的文章里会详细说到它。
五、还有很多问题
我在这篇文章里说我自己如何拆分微服务的经历。但是呢,微服务并不是想象的那么完美的,它其实还引出了许多新的问题需要解决。
在下一篇文章里,我会谈谈划分服务后,引发的一些问题。
我们下篇文章见。