微服务的战争:按什么维度拆分服务
微服务,这三个字正在席卷着目前的互联网软件行业,尤其在近几年云原生迸发后,似乎人人都对微服务有了更广泛的使用和理解,张口就是各种各样的问号,有着强大的好奇心。
无独有偶,我有一个朋友鲤鱼在内部微服务的早期(每个业务组起步)就经常遇到下述的对话:
张三:为什么要拆现在的代码?
鲤鱼:因为 !@)&@!)!&)@!&!的原因。
张三:那即将要做的 “微服务” 是按照什么维度去拆分的服务?
鲤鱼:常见的一般根据 !@#*@!#&!(@&!@)#@ 的方式来拆分。
张三:照你这么说好像也不大对,我看每个业务组拆分的维度似乎都不大一样?
鲤鱼:嗯,每个业务组还有自己的见解,不会完全相同。
张三:。。。所以微服务的拆分维度到底是什么?
为什么想拆
为什么张三会有这个疑问呢,实际上是因为研发内部希望从原先的大单体,大仓库向微服务体系拆分转换,其原先大单体仓库结构,类 Monorepo:
但类 Monorepo 又有不少的问题,像是:
单个 Repo 体积过大:导致 Git 无法直接拉取。当你设置完再拉取时,在网速慢时还能去泡杯咖啡,并且在开发机性能不佳的情况下,IDE 会比较卡,代码运行起来也慢。
单个 Repo 存在公共函数/SDK:在代码仓库中,必然存在公共依赖。因此在解决代码冲突时,若遗留了冲突符,且在动态语言中,不涉及便运行正常。但其实在上线后却又影响到其他业务,可真是糟糕透顶,分分钟被迫抱着事故。
单个 Repo 模块职责/边界不清:在实际的软件开发中,涉及数十个业务组同时在一个大 Repo 下进行开发,没有强控边界的情况下,往往会逐渐模糊,即使在设计时管得住自己,你也不一定能 100% 防止别人模糊你的边界。
单个 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
推荐阅读
站长 polarisxu
自己的原创文章
不限于 Go 技术
职场和创业经验
Go语言中文网
每天为你
分享 Go 知识
Go爱好者值得关注