微服务的战争:按什么维度拆分服务

Go语言精选

共 3803字,需浏览 8分钟

 · 2020-08-28

微服务,这三个字正在席卷着目前的互联网软件行业,尤其在近几年云原生迸发后,似乎人人都对微服务有了更广泛的使用和理解,张口就是各种各样的问号,有着强大的好奇心。

无独有偶,我有一个朋友鲤鱼在内部微服务的早期(每个业务组起步)就经常遇到下述的对话:

  1. 张三:为什么要拆现在的代码?

  2. 鲤鱼:因为 !@)&&#@!)&#!&)@!&!的原因。

  3. 张三:那即将要做的 “微服务” 是按照什么维度去拆分的服务?

  4. 鲤鱼:常见的一般根据 !@#*@!#&!(@&!@)#@ 的方式来拆分。

  5. 张三:照你这么说好像也不大对,我看每个业务组拆分的维度似乎都不大一样?

  6. 鲤鱼:嗯,每个业务组还有自己的见解,不会完全相同。

  7. 张三:。。。所以微服务的拆分维度到底是什么?

为什么想拆

为什么张三会有这个疑问呢,实际上是因为研发内部希望从原先的大单体,大仓库向微服务体系拆分转换,其原先大单体仓库结构,类 Monorepo:

但类 Monorepo 又有不少的问题,像是:

  1. 单个 Repo 体积过大:导致 Git 无法直接拉取。当你设置完再拉取时,在网速慢时还能去泡杯咖啡,并且在开发机性能不佳的情况下,IDE 会比较卡,代码运行起来也慢。

  2. 单个 Repo 存在公共函数/SDK:在代码仓库中,必然存在公共依赖。因此在解决代码冲突时,若遗留了冲突符,且在动态语言中,不涉及便运行正常。但其实在上线后却又影响到其他业务,可真是糟糕透顶,分分钟被迫抱着事故。

  3. 单个 Repo 模块职责/边界不清:在实际的软件开发中,涉及数十个业务组同时在一个大 Repo 下进行开发,没有强控边界的情况下,往往会逐渐模糊,即使在设计时管得住自己,你也不一定能 100% 防止别人模糊你的边界。

  4. 单个 Repo 包含了所有的源码:出现公司源代码泄露时,会导致整个 Repo 外泄,相当的刺激和具有教育意义。因为虽然开放和协同了,不属于你们组的业务代码你也有权限查看了。

当然,Monorepo 是否又完全不可行呢?实际上国外 Google,Facebook,Twitter 等公司都有在使用 Monorepo,并取得了一定的收益。

其实做 Monorepo 是需要相应的大量工具支撑,若单纯只是一个 Repo 塞多个模块,基本都做不好,甚至引火烧身。还不如早早拆开,至少能确保各业务线服务的相对独立性。

拆成什么样

张三在明白了拆的原因后,就出现了第二个问题,那就是 “微服务” 要按照什么样的维度去拆分服务?

张三公司内部对于这块的知识处于模糊不清的阶段,因此需要进行深入了解,便于后续的团队共识和方法论建立,理所当然,十万个为什么也就出现了。

大单体变独立服务

最常见的拆分的方式是按照业务模块进行服务的拆解,像是前文所提到的业务模块,在设计上边界非常清晰,这种情况直接拆成各个服务就可以了:

而在拆分后,又会遇到一个新的问题,也就是张三问第三个问题 “每个业务组拆分的维度似乎都不大一样?”。

因为在实际的执行过程中,严谨一些会由 SM 与 RD 一同开会探讨/规范初版的服务划分,而在持续的快速的迭代中,往往新服务的拆分都是由一线 RD 亲自操刀。

即使是架构师亲自操刀,在相对复杂的业务模型下,不同架构师划分出来的也有可能不完全一致,因此无论是哪种情况,你都会发现每个业务组拆分的维度多多少少都不一样了,毕竟人与人的思想都是不一样的,一千个人有一个千个哈姆雷特,因此张三的疑惑是正常的。

就像下图,核心是定义一只鱼,在不同人的眼中能演化出各种奇奇怪怪的鱼:

大数据库变独立数据库

在以前早期的大单体快速迭代中,往往是一个大数据库包含所有的业务数据库(甚至数据库账号都不分),这种时候就会带来各种问题。

像是某一天,你所负责的业务模块数据库莫名其妙出现了一些奇奇怪怪的值,你可能就要抓破脑袋去各种代码和 binlog 查了。更甚还有被网络攻击后,数据库配置被获取,直接跳板一拖直接整个脱裤,那可是糟糕透顶了。

因此在常见的应用设计中,应用程序在连接数据库时会指定连接特定的域名(例如:eddycjy-user),方便未来迁移。并且每个业务服务分别给予独立的数据库只读权限,进行软隔离。

而在业务量上来后,也会对业务数据库进行硬隔离,分配特定的 RDS 实例,就不会互相影响了。

环境隔离,独立

在服务拆分后,大多会采取独立部署的方式,将两者之间的环境隔离开来,互不干扰,互不影响:

像在云原生中,常见于在 Kubernetes 将一个业务服务作为一个 Service 部署发布,再根据实际的资源和调度情况进行 Pod 的扩缩容就可以了,资源也不会有直接干扰,且外部/内部调用都是有统一的入口管理。

拆分的阵痛

业务接口聚合

在服务拆分的过程中,总是会有阵痛出现。

例如在服务需要获取 “项目” 和 “房源” 信息时,到底是由谁来聚合这两个服务的信息。是不是应该由 BFF 来聚合:

或是应该新写一个胶水服务,用于聚合“项目” 和 “房源” 信息,保证其聚合性,减轻 BFF 的负担:

又或是在量级越来越多的情况下,是不是要怀疑一下,这两个服务拆分是不是有问题,“项目” 和 “房源” 在当前业务模型下是否应是一家:

显然在鲤鱼的经历中,这三种类型他都见过,不同的人总会在不同的思想和业务模型下选择了不同的解决方案,还真的没有绝对准确的准则。

分久必合,合久必分

随着对服务化的进程推进,常见的会遇到两种情况:

  • 刚接触服务化时:服务一个没有,偶尔会有一个新的小业务,居然能拆出好好几个微型服务,并扬言要把剩余业务直接抄底重构了,都拆掉,怎么劝都劝不回头。

  • 随着业务的不断发展:快速迭代,服务越来越多,工期压缩,多个 RD 交叉背好几个业务服务,有点力不从心,发现拆的好像有点问题,从最新的情况来看,某某几个服务似乎应该合在一起。

  • 业务阶段性稳定:。。。这,以前这块好像有点问题,也太难拓展了,不应该这么拆,谁调了我,我的上下游是谁。

大多数的情况都是第二和第三者,但在实际操作中也不见得会合并服务,大多数 RD 会选择吞进心里,因为服务变迁所带来的工期延长和影响面无法直接预估(且存在历史代码,人员可能已经离职多年)。即使是服务拓扑也只能查看到一定时间内的服务调用,不会看到全部,因此上下游均无法 100% 确定。因此综合来看,弊大于利。

在解决方案上,更多的是在下次新服务规划时控制划分变量(因为已经有更成熟的经验了)。

实在不行了,才有可能会新起聚合服务将原本的多个服务聚合,又或是采取版本号等方式进行新老分流。甚至下定决心,蚂蚁搬家,起新服务一个个板块重构,一个个挪,持续灰度,“彻底” 解决历史包袱,完成转化。

拆分准则

张三又发话了,你说的我都懂,内部微服务都发展好几年了,作为已经有丰富研发经验的人,能不能释出一套微服务拆分的准则呢,否则每一个人都要经历一遍,怎么办,有没有什么基本准则可以遵守呢,你看现在 DDD 那么火,能不能 DDD 一下,让核心一致呢?

机智的鲤鱼掐指一算,张三肯定想的是让所有业务组的拆分,都能依据拆分的核心准则走,实现你中有我,我中有你,看哪哪都有影子,核心不跑偏就行,建立一套完美的方法核心论:

这种建议右拐 Google “微服务如何拆分”,网上有超级多的指导资料,建议先培养在团队内的共识。毕竟在每次拆服务时让每一个人都对照着那一长串的 “微服务拆分准则” 是一件很不科学的事情,更多的工程师会依据自身的经验进行当前其认为的最合理拆解。

而准则,你认为的核心 A,在他人眼里并不一定是正确,他可能认为是 B,因此在事业部,业务团队中达成共识并把拆分思想融合进每位 RD 思想中,长期的共同分析现在的拆分情况,且让大家基本认同才是最重要的。

同时让全公司都依据一个准则来做,在服务拆分这种无法利用工具流程强控制的情况,本身就是一个伪命题,更多的会是人与人之间的妥协,基本上会变成一个少有人看的 “指导” 文档。

总结

在微服务中,服务的拆分总是能让人如此细细品味,本文并不是具体的讲某几个知识点,更多的是阐述在服务化发展的历程中的 “冲突点” 又或是 “矛盾点”,不同的人总有不一样的理解,希望能够给大家带来一些思考。

且在阅读微服务相关指南时,更建议看企业实践后拆分的经验分享,否则单纯看 “指南” 没有过多的意义,要看具体的公司/团队情况和业务模型。

推荐阅读

Monorepo:

  • Why Google Stores Billions of Lines of Code in a Single Repository

  • Monorepos: Please don’t!

  • Why might a project/company use a monorepo?

Microservices:

  • Nginx Refactoring a Monolith into Microservices





推荐阅读



学习交流 Go 语言,扫码回复「进群」即可


站长 polarisxu

自己的原创文章

不限于 Go 技术

职场和创业经验


Go语言中文网

每天为你

分享 Go 知识

Go爱好者值得关注


浏览 33
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报