查看原文
其他

我曾趟过微服务这条河,暗潮汹涌。

why技术 2022-09-10

The following article is from 四猿外 Author 四猿外

写在前面

你好呀,我是why。

这篇文章是从四猿外那边转载过来的,号主四哥是一家上市公司的技术总监,管理的技术团队一百余人。

关于微服务,四哥写了上、中、下三篇文章,分别如下:

“一学就会”微服务的架构模式:一个服务一个数据库模式(上)

我是如何把微服务的这个模式落地的:一个服务一个数据库模式(中)

硬着头皮写,硬着头皮搞:一个服务一个数据库模式(下)

看完之后我觉得这就是一个多年的从业者,从普通程序员到技术总监之路上学到的一些经验,踩到的一些坑,可以分享出来的一些事情。

在其中,我甚至看到了我自己过往经历中的一点点影子,颇有感受。

于是我将这三篇文章排版在了一起,供大家一气呵成的观看。

上篇

不管你喜不喜欢微服务,现在微服务无疑已经是程序员们绕不过去的话题了。无论你是想把目前的架构改成微服务,还是你要出去面试高级一点的岗位,需要深入理解微服务。

提起微服务,很多程序员对它是又爱又恨,想学微服务不知道如何开始,学了一点之后,又找不到地方去实践。总之就是感觉微服务遥不可及,又很难驾驭。首先要明白的是微服务是有套路的,而这些套路基本上解决了微服务结构面临的几乎所有重要问题。

这些套路就是微服务自己的架构模式

如果我们能深入了解这些模式的其来龙去脉,就可以理解了微服务绝大部分内容。学习快速,实用价值也极大。

微服务最基本的模式

这篇文章先来讲第一个最基本的模式,这个模式我估计需要三篇文章才能讲透,这是上篇。打算中篇写实践,下篇写问题。

希望大家能学的轻松。

微服务最基本的模式就是:

一个服务一个数据库

上图就是一个最简单的微服务模式了。

一个服务一个数据库这种模式,是微服务体系结构中的最基础也是最核心的模式。

看着简单,但是,这个模式蕴含着微服务的最基本的思想。

要弄清楚一个服务一个数据库这种模式,首先我们就需要问一下,为什么我们要搞微服务。

传统系统的问题

在谈及微服务的时候,和微服务对应的概念叫做单体系统( Monolithic application )。

简单说,微服务是为了解决单体系统的问题才衍生出来的。单体系统结构如下图:

那么这种单体结构出现了什么问题,导致现在大家必须开口闭口微服务了呢?

单体系统太大了

最首要的一个原因就是应用系统太大。而由于应用系统的过于庞大,如果仅是单体系统的话,就引发了各种各样的问题,体现在以下三个方面:

系统本身业务复杂,模块众多

系统随着时间的发展,业务需求越来越多。而为了满足这些需求,就导致整个系统的模块越来越多。而系统模块越来越多,就导致能理解整套系统的人变得越来越少,直到最后没有人可以理解整套系统。

系统的代码库非常庞大

代码量也会随着系统的增大而增大,代码量的庞大影响了整个开发流程,会导致整个开发成本变得很高。

首先,代码量大,依赖关系复杂,所以对新接手的开发人员来说,配置开发环境非常耗费精力。

其次,代码量大,加载这些代码和对应的依赖需要的内存就多,所以就会导致开发人员的 IDE 运行非常缓慢,导致编辑代码都很麻烦。

再次,代码量大,如果要把整个代码编译打包,需要的内存也很多,所以也会导致功能开发完成后,对系统的构建会非常缓慢,导致整个构建的时间非常漫长。

再有,代码量大,几乎没人能对整体代码有比较深入的了解,哪怕可能其中一个要改动的功能,都会因为过于复杂导致开发人员理解不深入。而这些不深入的理解又会让开发人员不能使用最佳的方式去做功能开发,从而导致隐藏的 bug。

技术团队变得非常庞大

由于功能模块越来越多,这就需要越来越多的开发人员去开发和维护这套系统。但是,这些开发人员都是面对的同一套代码库,虽然可以搞分支,大家各搞各的。可是一旦需要合代码,发布上线,就是场噩梦。

各种代码冲突,代码丢失,都可能在上线的时候发生。

不仅如此,由于顾虑代码丢失和冲突,就需要在上线前,进行足量的测试,而这些测试又需要投入巨大的时间成本。

但是,现在都讲敏捷开发,很可能在还没上线的时候,后续的业务需求又接踵而至,简直要命。

业务需求的个性化

搞微服务,还有一个很重要的原因是业务需求的个性化和颗粒化。

随着业务的发展,不管是由于市场竞争还是本身发展的需要,势必需要对本身业务模型的深度挖掘以及提高用户使用系统的各种体验。而基于此类种种,就势必要把系统的各个功能模块做深做透。

这又会引发几个新的问题:

系统功能模块可能变得更多更杂

系统功能模块可能被不断拆分成了更细碎的模块,以致可能碎成了颗粒。而由于功能变得更碎更颗粒了,就会让产品经理们更容易的提出一些非常细致的业务需求。

这些非常细致的需求,很可能会造成频繁的功能修改和上线要求。而这些无穷尽的快速需求相对整体庞大的系统上线和开发人员的疲于奔命形成了最激烈的冲突。

功能模块对系统的技术要求出现了冲突

比如,不同的功能模块,订单模块和支付模块。订单模块就希望系统能尽可能的能同时处理大量的订单,甚至可以有一定的容错性,出问题了砍单就可以了。

但是支付模块则不一样,支付模块希望系统能尽量的稳定,并且必须对准确度要求极高,几乎没有容错的空间。

同样的,在同样的支付模块中(根据系统模块划分而定),可能同时存在本地账户转账和三方渠道支付,本地账户转账可能需要即时,要求极高的响应时间。但是对于第三方支付,则可以有一定的响应时间容忍度。

如果系统本身是个单体系统,就势必要求开发人员对整套系统做一定的妥协,对冲突的技术需求做出一定的权衡。而这种权衡很可能影响的就是系统整体的体验度。

系统模块对服务器的要求出现了冲突

由于功能的深耕细作,则势必会出现性能上的不同需求。

比如,系统的订单模块,个人下单可能会被频频访问,此时,就需要系统的集群多一些,去处理这些大规模的访问。但是,同样的功能模块里,可能还存在一些企业团购需求,他们没有那么大的访问量,就不需要那么多的服务器集群。

又比如,用户评论截图,可能需要大量的数据存储。但是,同样的,针对用户的个性化推荐就可能需要大规模的密集运算。

除了上面说的,系统庞大引发的问题带来的一些附属问题。

故障的连锁反应问题

单体系统从技术上,各个模块是耦合在一起的。在实际运行里,很可能就会出现一处故障导致整个系统崩盘的现象。

比如,不常用的一个 XX 功能出现了内存泄露,导致整个系统全部不可用了。

系统的技术锁死问题

坦白来说,你得承认在编程里,没有一种语言是完美的,也没有一个数据库是万能的。

比如,Java 做科学计算就没有 Python 那么方便高效。比如,我们需要存储很复杂的对象关系的时候,MySQL、Oracle 就不如任何一种图形数据库。

所以,系统越复杂,需要不同技术的概率就越高。但是又由于系统的复杂,引入新技术的风险也就越大。所以,新技术的使用非常困难。

同时,系统庞大后,如果一些组件,甚至语言 SDK 本身的问题如果需要升级,也是一件既繁琐,又充满风险的事情,所以,技术版本升级也非常困难。

综上,对于传统的单体应用来讲,系统庞大引发的技术问题,业务发展引发的需求冲突问题……都是无法单凭单体系统的架构思想就可以解决的。

那为什么 SOA 也不能解决这些问题呢?

SOA 的问题

咱们先来看看SOA的结构

可以看到 SOA 架构中有个 ESB(企业服务总线)。这个 ESB 就是专门用于 SOA 的服务和服务之间的互动,是 SOA 必备的基础技术设施。

正因为 SOA 有了服务总线的思想,就注定 SOA 切分的服务不可能太细,因为服务出现的越多,这个服务总线就最终会变成一个整体系统的瓶颈。

SOA 的服务切分规模本身就受到了限制,这个限制就会带来以下的问题:

  • 切分不够细——我们说过,我们的主要问题根源是系统过于庞大,并且还堆在了一起。如果我们切分不够细,那么可能的结果就会变为,从一个很大的系统被切分为了寥寥几个也很大的系统,最终没有解决问题不说,还可能因为系统变成了不同的分布式服务,又引入了新的分布式系统本身所带来的问题。
  • ESB 本身就可能成为一个庞大无比的系统怪兽——ESB 作为 SOA 的基础设施,它本身就已经足够复杂,很可能由于业务的发展,它自己也变成了一个恐怖的系统怪物。从而让开发人员不仅需要维护原来的系统,很可能还需要为如何维护和修改ESB本身而伤透脑筋。

所以,可以看出来,SOA这种思维方式和架构实现本身不足以解决庞大单体系统带来的问题。

为什么需要服务

回到我们的微服务的话题。我们知道了问题的根源,我们就需要着手解决这些问题。

首先,既然问题是由于系统的庞大复杂引起的,那么我们就可以参考软件里很普遍的解决思想:分而克之。

无论一个系统有多大,如果我们将其拆的足够小,就可以把一个复杂的大系统拆分成许多个小系统,再让这分解出来的小系统通过对外提供服务的手段,将他们再聚合成一套大的完整体系,从结果上,就等价为了原来的复杂的大系统了。

而这,就是微服务的最朴实的思想。

所以,微服务思想核心有两个:

  • 把系统拆分成不同的部分
  • 这些部分要足够小

微服务这样做带来了几个好处:

  • 无论多大多复杂的系统,我只要能拆,会拆,就能把问题简化,从而不用惧怕系统变得复杂。
  • 拆分出来的服务只要足够小,那么无论开发、部署、运维上,都能得到无数原来因为系统庞大而无法获得的好处:修改代码可能变得简单了,测试和运行也变得容易了……
  • 拆分出来的服务能各自独立发展,不会互相制约。原来系统是单体系统的时候,模块之间由于技术上的耦合,导致无法自由自在的选用最适合当前功能模块的技术,也不能随心所欲的根据当前功能模块的负载情况去弹性的安排服务器。
  • 故障天然被隔离开了。我们把系统切分成了服务,每个服务都有自己的进程或者服务器,这样故障从物理层面就被隔离开了,从而避免了一处不重要的功能故障导致整个系统崩盘。我们只需要把核心的功能弄的足够健壮,即使非核心功能有了问题,也不会造成太大的损失。

所以,一套巨大的系统,由于本身的臃肿和复杂,就可能会要对其自身进行拆分。而这些拆分,根据一些指导原则,将其拆解的够小,够简单,那么,拆解后带来的效益是很可观的。

为什么需要拆库

服务已经拆了,已经获得那么大的好处了。

“但是为什么数据库也必须要拆?”——这其实是很多使用微服务的同学最疑惑的问题了。

数据库拆分不拆分本质上其实就是数据共享的问题。而一个服务一个库本身的观念,其实就是尽最大程度的避免数据的共享。

数据共享会带来如下几个问题:

技术实现依然可能耦合

因为没有拆分数据库,所以,很可能一个本来应该独立出来的服务模块,必须依赖于另外的服务模块,而这和我们拆分服务的初衷出现了冲突。

比如,订单服务和个性化推荐服务,很可能都需要访问订单相关数据。此时,如果不拆数据库,则很可能由于订单业务需求导致的订单表结构的修改,倒逼个性化推荐服务也要跟着修改。

底层数据的过度暴露

还是上面订单服务和个性化推荐服务的例子,个性化推荐很可能只是需要一些用户 id、订单类别之类的东西,但是由于数据库是共享的,很可能开放的就是订单表的全部数据,而这些数据有很多算是敏感数据,应该被隐藏的,现在则被暴露出去了。

无必要的数据访问竞争

因为是同一个数据库,这势必会造成对共享数据的竞争性访问,而这些竞争性访问则会大大影响业务模块的弹性部署。比如,订单模块很可能由于个性化推荐的一些定时批量查询,被影响了其能承载的并发数据量。

所以,看出来了吧,分库是必须要考虑进微服务整个体系结构的。

上篇-小结

每一个服务对应一个数据库这种模式,是微服务中的最核心最基本的模式,它体现了微服务最核心的思想:

拆分与解耦

一般来说,微服务大部分时候,都会尽量采用一个服务一个数据库的模式。

这里只说了为什么要使用一个服务一个数据库,而如何去分服务,如何去分数据库,它们是否还存在一些实践上的妥协?

这些问题会在中篇里仔细解析。

中篇

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

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

前面我也提过,一个服务一个数据库是微服务最基本的模式,也谈了为什么要搞微服务。中篇我想谈谈:

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

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

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

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

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

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

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

什么时候使用微服务?

当你的交付时间不够应付产品团队,不够应付运营团队的时候,考虑考虑。

还有,我后来搞其他新项目时,领导认为系统太简单,没有自己的技术特色。

不得已,我又拿出了微服务,领导看了之后,眼前一亮,直说这个好。

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

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

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

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

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

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

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

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

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

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

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

对于第一类独立的业务需求,我直接就设计出一套独立服务,让它和已有的老系统通过网络远程互联。

这样的话,新搭建的服务很小,维护也简单。以前的老系统也成为新服务的服务。这样,一部分需求,就可以快速迭代了。

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

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

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

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

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

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

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

传统业务划分

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

步骤如下:

第一步:划分功能模块

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

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

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

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

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

第三步:对方法进行分类

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

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

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

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

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

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

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

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

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

我们肯定不能任其这样频繁的网络调用。对这种情况,就会有两个办法处理:

  • 把服务之间的交互改成批量处理的方式;


  • 干脆就不拆服务。


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

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

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

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

  • 设置超时;


  • 搞成异步方式处理。


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

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

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

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

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

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

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

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

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

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

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

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

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

领域驱动工具

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

当我看了领域驱动设计之后,我明白了,这玩意儿就是换个思路就好了。领域驱动设计其实没啥特殊的地方,但是它引入了一个子域和限界上下文的概念。

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

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

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

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

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

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

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

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

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

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

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

中篇-小结

我在这篇文章里说我自己如何拆分微服务的经历。

但是呢,微服务并不是想象的那么完美的,它其实还引出了许多新的问题需要解决。

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

下篇

在我最初接触微服务的很长一段时间里,有两类问题都困扰着我和团队,这是让我印象最深的两类问题:

  • 没有配合微服务理念的团队
  • 没有配合微服务理念的基础设施

后来,在和一些搞了微服务的同行多次交流后,发现他们当初也面临和我类似的问题。

这次就写写我最早搞微服务遇到的问题。

有些问题放到现在来说,已经有解决办法了,已经算不上问题了。但是无论怎样,这些问题如果能提前意识到,早做准备,会为将来搞微服务的同行们省下许多的力气。

所以,这篇文章我会着重谈下这两类问题。

没有配合微服务理念的团队

当年,我还是一个小开发团队的组长,组里将近 10 个程序员,维护着一个庞大的单体系统。

那时微服务刚出来不久,各种评估后,我们认为把系统拆分成微服务可以带来更大的好处。

但是,对于微服务中提到的团队自治这点,由于当时的职位和经验限制,也无法贯彻这一理念,结果,最后就是把自己折腾了底儿掉。

在谈团队自治的问题之前,我先说说拆分微服务的时候,我们当时的整体交付流程是什么样的。

整体流程很简单,业务提需求给我们开发团队,然后开发团队收到业务需求后开发、测试,最后上线。

我们上线流程也比较传统,先是开发人员把应用打包然后上传到一个运维团队规定的路径,然后才由运维团队发布到生产服务器上。

这种模式开始没什么大问题,可是随着我们拆分服务越拆越多,问题出现了。

我们负责的系统很重要,本身上线比较频繁。再搞了微服务之后,因为服务多了,服务器也多了,使得上线部署变得更繁琐。

这逐渐导致运维团队本就不富余的人手更加捉襟见肘,他们只能加班。结果就是,996 成了家常便饭,运维人员对此颇有怨言。

频繁上线不但连累了运维兄弟,还拖累了其他团队——由于运维人手不足,又导致了我们团队和其他团队项目上线经常出现冲突。

冲突发生后,因为我们的项目是公司核心项目,很自然的,优先级我们会占一些便宜。

其他团队的上线只能不断的调整去和我们错开,或者加班等我们上线后,再由运维安排他们的项目上线。

可见,在我搞微服务初期,微服务划分的快速迭代始终因为团队划分和微服务本身的理念不匹配,导致处处受挫。

如果你要全权负责一套微服务项目的时候,一定要万分注意。因为你本身的团队和微服务理念中的团队自治是不匹配的,这会导致你自己微服务项目的维护出现各种各样问题。

对于这类问题,我建议在初期你就要有所考虑。因为这个风险,或许是技术人员不可控的。

没有配合微服务理念的基础设施

在一开始,由于我们的微服务是从单体项目逐渐剥离开来的,所以,在这个时候,服务只有 3、4 个。

但是随着对业务理解越来越深入,开发人员也对服务落地越来越熟悉,服务划分的速度也越来越快了。在很短的时间内,服务一下子从三四个,猛增为了近二十个。

这时候,面对猛然暴增的服务,我一下子不知所措了。

除了上面说到的运维上线的问题,还出现了很多我从未经历过的问题。

定位问题成了一件很奢侈的事

通过采用上线模板和其他工具,好不容易缓解了运维问题。还没轻松几天,紧接着,故障处理又出现问题了。

开始的时候,服务数量少,定位问题,大概稍微琢磨下,就能判断出来。但是,随着服务越分越多,定位问题就很麻烦了。

例如出问题后查日志,原来的服务数量少,查日志直接上服务器就查了。但是,现在服务有接近二十个,还没算集群。挨个服务器查日志定位问题,那几乎不可能。

不能再这样了,不然失败就在眼前

后来,我下了决心,在解决这些问题之前,坚决不能再拆新的服务了。

我主动去和领导沟通了这些问题,得到了领导的支持。然后,又拉着领导和业务团队磨了好几次,终于也让他们同意暂时降低一段时间提需求的频率。

总算能腾出精力解决问题了。

关于问题的思考

这些问题,我统统归类为基础设施的问题。其实,那时候虽然微服务生态还没有完全清晰,但是,我本身大概也总结出了一些套路。

我把需要的基础设施分成了两个类别:

  • 部署发布
  • 日常运维

然后,我分别对这两类基础设施的需求又做了进一步的细化。下面把当时我做的最紧急的一些基础设施需求列了出来:

我控制不了这些问题

但是,这里依然还有问题。

比如,这些工具理论上是属于基础设施,是不是需要运维团队来维护?可是运维团队已经对我们的各种上线需求不胜其烦了。

又比如,这些工具当时不成熟,我们还得自己开发改进,而这又要靠谁呢?

当时没有办法,我只能咬牙自己带了几个人,把这些额外的工作承担了下来,由我们几个人专门开发和维护这些工具。

一直到后来,市面上有了成熟的工具链,我本身也升职,可以对技术团队整体去贯彻 DevOps 理念了,才真的从这些任务中解脱开来。

基础设施决定上层建筑啊,同志们

我知道很多公司是技术自己提出来微服务的,提出来的时候,你一定要清楚,微服务这套体系本身,

把以前单体系统的复杂度转移到了技术基础设施上。

很多工作其实是需要自动化的。在踏进微服务这个神坑前,一定要考虑清楚:公司有没有合适的基础设施?

落地需要妥协的问题

微服务理论看上去是很完美的,但是,在现实落地,其实还会有许许多多不太可能马上完美贴合微服务理论的问题。

我大概列举几个重要的:

数据库划分的问题

老实讲,咱们这篇文章本来就是在说一个服务一个数据库的模式。那么按理来讲,严格符合这个模式是最好的。但是,实际落地来讲,中间有太多的弯弯绕绕了。

比如,我们的服务需要划分成四五十个服务,这个时候,数据库划分成同样的四五十个库就不合适了。因为这会引入如下的三个问题:

  • 数据库管理过于复杂——这个是很显然的问题,管理几个数据库和管理几十个数据库,需要投入的人力物力是完全不一样的。每一台数据库本身就是个很复杂的系统,数量越多,出问题的几率也越大,监控难度也越大。
  • 分布式一致性实现太过复杂——数据库数量上来了,因为业务需要,协调数据一致性从原先需要协调几个数据库的状态变成了需要同时协调几十个。复杂度一下子上去了,这也会造成很多不必要的技术问题。
  • 跨库查询相当不方便——这个问题也是一样的,当我们服务划分后,数据库如果也划分的过细,那么以前需要跨几个库查询的业务,就可能变成需要跨十几个库查询。

所以,就落地的时候来讲,还是需要有个业务域的概念。这也是为什么微服务总和领域驱动设计绑定在一起,因为人家天然有个业务域的概念。

这时候,就可以考虑某些业务域,共享一些数据库。比如,订单业务域可以每个服务对应一台数据库,但是,用户业务域可能就可以共享那么一台数据库。

开发框架的问题

我搞微服务比较早,所以,开始做的时候,就是用了 Spring 的框架,然后每个 Tomcat 后面放个服务。

那时候,维护起来真麻烦。因为 Tomcat 本身多了,又和应用不是一体的,同时维护 Tomcat 和应用,非常难受。

后来有了 SpringBoot,情况好了很多。再后来,我们也尝试使用了一阵子 Dubbo。

其实各有自己的不足。

SpringBoot 的不足主要是,用了 SpringBoot,很多时候就不得不用更多的 Spring 其他组件,哪怕它的一些组件很不让人满意。感觉项目中处处 Spring,非得走 Spring 那套规则不可。

Dubbo 的不足主要是能配合的组件很少,我们用 Dubbo 其实很早,但是为了和 Dubbo 配合,有些时候还得做很多额外的开发。比如,当时 Dubbo 本身服务跟踪,也没有通过 RabbitMQ 通信的组件,我们都需要自己开发。

所以,当你要选框架的时候,要考虑清楚,因为微服务本身是一大套生态。如果框架本身选用不合适,后期就得靠自己的技术能力去做硬调整。

一些关键技术何时引入的问题

有些关键技术,我们是逐渐引入的。

因为,引入一个新技术,对我们无论是开发还是维护,引入便利性的同时可能也会引入复杂性。

比如容器技术,我们就是搞了很久了才慢慢引入的。引入容器技术后,很多问题(例如网络、内存)我们就要多想一层,看看是不是因为容器导致了其他问题。

总之,对于一个正在运营的系统,我个人认为引入新技术需要谨慎评估。

下篇-小结

以上写的主要是我和团队的经历,可能你会觉得是一家之言。没关系,说的不对的,欢迎指正,虚心接受;说的对的,希望能让给大家一些借鉴。

对于一个服务一个数据库说到现在,我说了为什么要分服务,以及如何落地还有带来的一些问题。

对于这种模式,它引入的问题其实非常多,一本书可能都说不完,这里只是举了一些我遇到的一些我记忆里很深刻的问题。

那么,把一套系统改造成一套微服务就需要分服务和分库就完了吗?解决分服务和分库带来的一些问题就完了吗?

那可不是,因为有些问题非得引入一些新的模式才能最好最省心的解决,只有把多种微服务的模式配合起来,才能让微服务这个生态完全的运转起来去替代以前的单体项目生态。



推荐👍 :编程玄学,走近科学
推荐👍 :该不该裸辞?
推荐👍 :老爷子这代码,看跪了!
推荐👍 :当我看技术文章的时候,我在想什么?
推荐👍 :这个Bug的排查之路,真的太有趣了。
我是 why,一个主要写代码,经常写文章,偶尔拍视频的程序猿。
欢迎关注我呀。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存