前序
额,非常遗憾,这次并不是分享 BUG 了,所以不能让大家看到我出糗的样子了,而且,这次也没有太多技术性的内容,多少会显得有些枯燥乏味。不过呢,可能本次所波及到的我的项目迁徙拆分计划,在诸位看来也并非完满,所以各位还是有机会批评一波,娱乐一波。
《2020 最新 Java 根底精讲视频教程和学习路线!》
背景
话不多说,咱们先来谈谈这次这次我的项目迁徙拆分的背景。
经典模型
咱们先来看看目前大多数微服务框架的零碎架构,这里以 Dubbo 为 RPC 服务根底,并且用传统的电商业务模型(熟知我的观众必定晓得,我向来是爱护公司业务代码的,所以这里应用大家宽泛熟知的业务模型为例)。
简略解说下:
- 这是一件简略的电商业务模型,在微服务业务细分下,分为用户服务、商品服务、交易服务、仓储服务、售后物流服务、财务服务。(要是具体细分以及其余服务的就不在这里多说)
- 微服务架构采纳 Dubbo 作为服务治理框架,各个服务通过 Dubbo 进行 RPC 通信。
- 各个服务当中波及到的实体类、工具类、Dubbo 接口等放在公共接口服务当中(我也不晓得该怎么说,其实就是一个公共的 jar 包,不能说是一个服务,但当初权且这么称说吧),由各自业务服务 web 工程进行援用,其实就是在 maven 的 pom 文件当中援用。
以上可能是最简略的一个模型,当然在业务、架构上各个公司都有本人的计划以及扩大之类的,这就不再思考之内,然而其实外围还是围绕着这个最原始的模型。
以后现状
那么当初遇到了什么问题?咱们再来看看这样一个模型。各自的业务服务 web 工程都会对这个公共服务进行依赖,这自身也没什么问题,毕竟这外面放的都是一些 Dubbo 接口等,具体的实现类还是在各自的服务工程当中。然而问题来了,不晓得从什么时候开始,这个模型产生了变动,就是所有的实现类都写在了这个公共接口服务外面。
因为这自身就是多模块的 maven 工程,于是各自的 web 工程援用本人所须要的那一部分 module。于是所有的 web 工程只是一些不同环境 profile 下的配置文件。这个就与下面的模型的初衷南辕北辙。如图所示:
产生影响
那么这样会有什么问题:
我的项目构造的问题。所有人的代码都在这个公共接口服务当中,使得构造变得很凌乱,对于 web 工程甚至会援用不属于本人的 module,例如商品服务当中援用了财务的实现类,实质上走的是 dubbo 协定,web 工程反而引入了一些多余的 jar,甚至还会引起 maven 的依赖优先的问题,造成事变的危险点。(maven 的依赖优先指的是援用不同 module 然而蕴含了同一个 jar 的不同版本的状况,maven 采纳依赖最短门路优先准则,这个有机会再之后的文章中细说)
代码治理的问题。因为不同模块的代码都在这个公共服务当中,在合并代码的时候会产生大量抵触。例如商品服务的同学提交代码进行 Merge 的时候会产生产生大量的抵触,这些抵触基本上是非本人业务模块,或交易、或仓储、或其余业务模块等。在现实模型下,也就是在我第一个图片当中,那么进行 merge 产生的抵触也应该是各自业务模块的代码抵触,这个是免不了。(导致合并代码最初演化成 copy code 的模式,或者应用 Git 提供的 cherry-pick,然而实质上其实也是高级版的 copy code,而无奈应用失常的 merge)
部署治理的问题。既然大家都把代码合并在这个公共服务当中,那么在 CI 部署的时候(不论是 gitlab 上还是 Jenkins),编译打包这个过程就非常迟缓。本来这些编译打包的累赘能够落在各自业务 web 服务中,当初反而都集中在这一个服务当中。随着日后业务代码的一直增多,这个时耗将会变得越来越长,甚至一旦有代码出错导致整个部署公布都失败。
这个是目前察看下来最为头疼的几个痛点。这个事件我从去年始终一直反馈,这个公共服务须要进行拆分。然而一是因为一些部门层面上的起因,二是业务倒退吃紧,也没有人也没有这个工夫去主导或者去推动跟进这个事件,导致这个事件一拖再拖,于是就陷入了一种恶性循环的境地。
直到这次双十一,因为大促期间无奈公布,所以也就没有需要迭代。于是我再次提出这个事件,这次总算是有工夫去推动这个事件。(我还是认为,既然曾经发现诸多的痛点,还是应该想着提前去根治,否则这迟早是一把达摩克利斯之剑,要么在一开始就应该避免这种事件的产生)
施行前
那么当初想想,应该怎么去动手这个事件。(是不是感觉这个事件看上去如同没啥技术含量,然而忽然就不晓得怎么做,我过后还认为是大佬们去做这个事件,然而大佬们有其余优化,所以就变成谁提出,谁解决。当然,做程序员嘛,还是以解决问题为主)
剖析目标
咱们的目标是将业务具体的实现类(这里次要是 service 层的实现和 controller 层的接口)迁徙至各自的 web 工程。那么,首先想到的是是否能够间接从公共服务当中删去那些代码,而后新增到各自的 web 工程呢,或者通过 git 操作进行合并。那么我感觉,如果只是从做法上考量,两种答案当然都是能够。然而呢,要是这么简略的话,我明天也就没有必要写这篇文章了。咱们想想,要是真的就这么做了,会有哪些问题:
- 第一种,如果是删除代码,挪动代码的话,这种做法我感觉是成果最差的。因为尽管你代码是实现了转移,然而一并的也失去了 git 下面的 commit 记录,其次还无奈保留项目当中的各个分支。
- 第二种,仿佛成果是好了一点,然而因为工程构造的不一样,在通过 Git 操作之后会有大量的抵触。网上也有很多的相似操作,然而因为网上提供的 demo 业务场景比拟繁多过于简略,就像这种 git 如何合并两个我的项目?,而且实际上的操作也比这种 demo 简单且难施行,危险点过大不可控。
我的项目分支
可能很多人会奇怪,既然我是迁徙我的项目,为什么还要保留分支。首先,咱们要明确,如果是那种几个月前的开发分支,或者只是为了合并代码生成的合并分支,这种分支对于整个工程来说,意义不大。在迁徙的时候重要的分支其实就是分为两类:
- 现阶段正在开发的开发分支,例如 feature/abc;
- 具备重要里程碑意义的分值,例如灰度环境: gray/abc, 或者是非凡分支: vip/abc, 亦或者是预发分支: release/abc, 还有就是这种备份分支: remaster/abc 等等。
为此这类分支就要毫无保留的进行迁徙,保留对代码工程的维护性。
当然咱们公司还存在着另外一个特殊性,给这件事件造成极大的艰难。因为咱们公司是一家 To B 企业,所从事的业务也是面向企业的,所以对不同类型的公司就会进入到不同的环境,咱们称之为“灰度环境”(尽管集体认为称为这里灰度与实际意义上的灰度意思有点不符,我所认为的灰度可能更多还是偏向于小流量用户所处环境,且用于新功能测试阶段的灰度过渡,这个可能就是 To B 与 To C 的不同吧)。于是在咱们公司就会存在许多“灰度环境”,这个给推动这个我的项目迁徙工作带来极大的阻力。
我之所以在这里介绍这么多篇幅,是因为这个的确是造成了很大问题:
- 首先,既然是我的项目迁徙拆分,那么必定不是一个早晨全副改掉实现,想想有这么多环境的存在;而且就算通宵革新,那么就不怕呈现危险嘛!这个锅背不起。
- 其次,如果革新的话,那么公共接口服务当中具体的实现类必定都是要删掉的(git 上删除),剩下一个赤裸裸的公共接口,那么各位开发同学在开发的时候,必定会从这个赤裸裸的公共接口服务的 master 切出分支。失常状况下是一个环境一个环境的进行更迭,那么当有紧急 bug 须要修复的时候,从这个赤裸裸的服务中切出的 hotfix 分支合并到还未革新的环境的时候,就会把对应环境的你那个服务上的代码都删除了,然而那个环境因为还没有进行革新,而此时又产生删除状况,那么这显然就会出事。除非在某一时刻全副革新实现来防止这种状况,例如下面 1 中所说。如图所示:
实施方案
那么难道就真的杯水车薪了吗?起初我认真思考了下,既然一步到位的事件做不到,那么是不是就能够绕一下曲线救国。于是我寻思着,既然不能间接革新,那我是不是每一个业务服务能够从新镜像出一个公共接口服务,将依赖从原先的公共接口服务转移到这个公共接口服务镜像当中,因为各自业务都有本人的镜像,那么只须要对各自的镜像进行批改,这样改变与影响就小很多了。如图所示:
这样做有如下益处:
- 既然是复制镜像的做法,你那么整个 Git 工程当中的 commit 记录和所有的分支都保留。
- 每一个业务模块都能够对本人的镜像进行增删改,而不会影响到其余业务模块。
- 每个环境迭代公布,只须要将 maven 援用转移到公共接口服务镜像即可。
咱们来看看接下去的环境迭代:
这样就能够防止下面的问题,对于未迁徙的环境和曾经迁徙的环境,都能够很好的兼容。
最初的问题
然而这里还有一个问题,不晓得各位观众有没有留意到,那么原先的公共接口服务,该怎么解决呢。这里也会面临是否删除代码的问题:
- 如果删除代码,然而这样又跟方才说的第 2 点的问题是一样了;
- 如果不删除代码,那么原先的公共接口服务当中和镜像当中的实现类就会反复,这样两者进行编译的时候,后一个编译打包的就会笼罩前者,也会存在问题。
那么怎么办呢?这个还是主管给我提供了思路,因为原先公共接口服务的 maven 坐标和镜像的 maven 坐标是一样的,那么就须要将镜像的 maven 坐标进行改名,而后让 web 工程援用镜像的 maven 坐标,这样,即使同一个实现类在公共接口服务和镜像中都存在,然而 web 工程实际上加载的只会是镜像当中的 class,这样就能解决这种问题。
施行中
既然下面曾经将思路与计划都曾经论述分明,那么接下来进行施行,这个过程就绝对容易点了,就如同高楼大厦,架子曾经搭建好了,只须要搬砖即可。
因为各个业务都是由各个业务小组进行推动,业务迁徙拆分也有先后顺序,那么就以商品服务的拆分为例来开展:
- 告诉各个开发,次要是商品服务开发人员,在对立时刻进行分支提交,因为须要将公共接口服务进行镜像复制。这是很要害一点,不然当复制出镜像之后,尚有分支还在原先公共接口服务当中,那么这个分支上的性能代码的迁徙就会非常鸡肋。
- 在镜像当中只保留商品服务的实现类,包含 controller、service 实现类、business,dao 等,其余非商品服务代码都进行删除,commit 并且 push。
- 批改 maven 坐标,进行改名,这里的改名最快的办法就是批改 groupId,例如原先叫 com.abc.abc, 那么当初批改为 com.abc.xyz,而 artifactId 不变,仍旧放弃语义,例如 abc-item。
- version:set。这个就是在对应环境中进行部署打包,正式环境必定是 deploy。
- 在商品服务的 web 工程中,将波及到依赖商品实现类的 maven 坐标进行批改,其实就是批改 groupId 就好,这样 web 工程就援用到镜像而不是原先旧的公共接口服务。
- 公布,查看启动是否报错。当然,我是在镜像当中加了一个心跳申请 controller,来验证申请是否走到了镜像当中的代码。
那么对于其余开发同学的状况,咱们这边也须要进行考虑一下:
- 非商品业务开发: 原则上,这些开发的改变是不可能涉及商品业务的代码。然而因为之前都是在一个公共接口服务当中,这一点就很难保障。这种状况的话,要么予以驳回,要么就是配合 copy code 将这些代码复制到进行当中;
- 商品业务开发: 在切出镜像之前,有些同学曾经有了业务开发的分支,那么这种状况下,只须要将镜像的 master 分支进行反向 merge 即可,因为 master 分支是只保留了商品的业务代码,其余代码都曾经进行了 delete,在反向 merge 之后,也一并会将那些代码进行删除;而在镜像曾经切出来之后,那么如果须要开发新性能或者进行 hotfix 的话,那么只需从镜像 checkout 出新的分支即可。
施行后
在迁徙拆分之后,须要进行简略验证。这里我以商品服务为例,在镜像当中的 Controller 层写一个心跳申请:
@RequestMapping(value = "/healthCheck", method = RequestMethod.GET)
@ResponseBody
public Object healthCheck() {return successResponse();
}
那么如果能够申请到这个接口,阐明商品 web 服务工程曾经胜利援用了镜像外面的 module,其余业务模块也都能够用这个办法监测。
小结
总的来说,可能本次我的项目迁徙拆分的工程,难度更多的还是偏差治理方面的考量。尽管我不太分明当初其余公司的我的项目架构是否有遇到这样的瓶颈,但就我身边敌人所处小公司的我的项目,也多少能够窥见接下来的趋势也会遇到雷同的问题。其实也不难理解,在后期还是以倒退业务为主(必定是赚钱要紧,不然谁来发工资),只是我感觉,任何后期小型我的项目演化成一个巨无霸的时候,肯定要意识到我的项目对于当初团队建设所影响到的方方面面,因为细节决定成败不只是书上说说的情理。
另一方面,在咱们不能欲速不达的状况下,须要进行肯定水平上的曲线救国,通过引入中间环节来解决问题。正如下面所述的解决方案中,通过引入镜像以及更改 maven 坐标的形式,来间接实现迁徙拆分的成果,并且在这种多环境的场景中也能很好的实现兼容,并且在业务中将代码实现了隔离,大大加强了零碎的可维护性。