本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
架构解析系列-架构演进的基本流程,以 H 项目为例
本篇是基于笔者接手的一个实际案例为背景,在其发展的一年多的过程中,随着内外部业务需求、业务环境、应用场景的变更为主线,来阐述一个项目架构演进的过程。这个过程对于大多数公司来说可能是都经历过的,从笔者的角度来看,这个演进过程要晚于主流架构至少 5-10 年 ,其基本思路是典型的单体应用迁移微服务的过程。本篇文章可能不是偏向于向读者解读如何将单体应用转换成微服务体系,更多的是在这个演进过程中,针对不同场景、不同业务诉求背景下的一些技术思考。
为了最大限度的业务保密性,本篇将此项目名约定为:H
背景
H 项目最初是由一家外包公司编写,使用的是 go 语言。主要包括一个核心业务系统,一个管理后台系统,项目是前后端分离的;部署方式是使用 docker-compose 将业务系统及其依赖的中间件服务(redis、mysql 等)一起打包发布。整体来说,这个版本还不能支撑正常的业务使用,这里暂且将其定义为 v1.0 版本。其架构示意图如下:
v1.0 版本是从 2023 年5 月份被我们团队接手,前期主要的工作是为了配合试点应用进行一些业务改造。大多是接口层面的,因为试点应用的用户基数本身不大,业务修修改改的过程持续了差不多1个多月。
从 1.0 到 1.5 的架构演进及其原因
1、错误的技术选型带来的效能问题
这个问题想必很多人都有共识,当然该踩的坑谁也绕不过去,学费总归是要交的。前面提到,H 项目的 v1.0 版本是使用 go 语言编写的,而当时我们团队除了我之外,其他人都没有写过 go 的代码,都属于 java 系的。但是作为一个技术负责人,在前期业务相诉求相对宽松的情况下,秉着拓宽技术团队技能的想法,要求团队成员开始使用 go 语言来开发,并着手拆分核心业务系统,其中就包括将数据分析服务从核心业务系统中拆分出来。
对于工程师来说,切换编程语言其实不算是什么大的事情,除了 c 或者 rust 这一类的语言相对入门难一点之外,其他的还算容易上手;但是需要注意的是,在工程实践方面,切换编程语言意味着这些编程语言背后生态的切换,以及工程师编程思路惯性的切换,这是要命的。
团队差不多用了 1 个多月的时间,将数据分析中心和大屏的功能使用 go 语言实现了,这个过程可以用磕磕绊绊来形容,大多数工程师在这个过程中是一边学go,一遍用刚学过的语法来实现接口。当然从后来视角去看,我们没有逃过"墨菲定律"的支配。
如果说在时间宽裕的情况下,边学边写我觉得并不是什么大的问题,但是免不了突如其来的第一个快速发展阶段带来的阵痛。差不多 6-8 月份这个过程,团队基本上是疲于奔命的状态,一方面应对试点的全面铺开,一方面是不断的需求变更。这也为后面我决定要再次重写项目埋下了一个伏笔。
2、固化的登录
这里的固化的登录其实就是从 前端-后端-db 的过程,因为这对于 v1.0 这样一个单体应用来说,这套体系是够用的;但是在随后的迭代过程中,我们发布了小程序、也将项目整合到了另外一个大的平台上去;这样就涉及到了对接微信小程序的账号登录授权以及大平台的账号体系。
这个过程在内部也有一个过渡阶段,即我们用了三套登录方式来支持,因为在此之前,内部的 sso 还在研发阶段,还没有正式使用。庆幸的是这件事情已经在做了。在 v1.0 到 v1.5 版本,摒弃原有的登录方式,切换到统一的登录授权系统也是一件非常关键的任务,因为这对于后面我们承接更多的三方平台以及统一鉴权、授权做好了充分的准备。
3、发布
前面提到 v1.0是使用 docker-compose 将所有服务和组件一块拉起来的,这符合成套产品快速输出的理想想法。在实践中,这种方式带来的问题是无法接受的。
- 1、一个服务或者组件变更就需要重启全部的服务和组件
- 2、快速迭代过程中的频繁变更和发布进一步放大了 1 的问题
- 3、缓存数据丢失的风险问题被放大
- 4、因缓存数据丢失带来的 session 失效问题被放大
面对这些问题,我们第一想法是拆,将数据层面的组件一一拆分出去,将服务以独立的包进行发布;但是受限于团队其他成员对于容器的不熟,加上前面技术战切换带来的阴影,我们摒弃了容器化发布的方式,取而代之原始的命令启动方式。那这里带来的问题就是我们在发布这个事情上又有了额外的精力投入,随着对高可用的诉求不断提高和服务数量的增加,这个消耗也不断在被放大,也为 从 v1.5 到 v2.0 的升级提供了"有力的因素"。
4、琐碎的 nginx 代理
即使到 v1.5 阶段,我们仍旧是通过 nginx 来进行路由分发;当时已经拆分出了 3 个子系统,在加上一些三方组件的请求,差不多有好几个不同根 path。而因为我们在拆分服务的过程中又忽略了这一点,导致后面在配置 nginx 代理时出现了路径冲突的问题。除此之外就是前端同学也不得为了适配不同的环境和访问不同的后端服务,在代理中配置多个后端服务的地址。
5、数据
数据问题这里不单单是业务系统本身的数据问题,还包括了数据库索引的优化问题、三方数据对接进来之后的数据标准、数据转换问题、面向不同三方提供的 api 接口问题、数据备份问题等等。
- 1、数据库索引的优化问题:在 v1.0 版本中,因为外包团队在开发的过程中没有实际的业务场景去支撑验证,所以很多表结构都忽略的索引设置的问题;尤其是我们开发数据分析中心和大屏需要做各种基于数据库的指标统计时,这种性能问题就很突出。
- 2、三方数据对接进来之后的数据标准、数据转换问题:项目需要对接很多三方平台过来的数据,这个逻辑一致在核心业务系统中,因为它离核心最近,而这些数据就是核心之一;因此在此之前,我们在核心业务系统中耦合了大量这样的对接和转换问题,特别是三方数据中有很多我们库表标识为非空的字段,往往给过来的就是空亦或者是压根就没有。
- 3、数据备份问题:业务关系,业主对于数据的敏感度很高,要最大可能的保障数据不丢的问题。最开始我们都是基于业主云平台提供的虚拟机做的部署,数据库也是和其他业务共享的,所有的服务都挂在一个规格非常大的虚拟机上,这从部署架构本身来看就无法确保我们的数据和服务的安全性问题,因为有太多的不可控因素在里面。这也牵涉到了后面我们基于业主的机房环境,自己采购了物理机器做了自己的 DC。
- 4、面向不同三方提供的 api 接口问题:这其实是 2 的延伸,本质上就是在没有数据标准的前提下,我们为了适配更多的三方做了很多没有什么实际技术含量的重复性的工作。这里为我们在 v2.0 版本发布自己的数据集成服务提供了需求支撑。
上述差不多是我们 v1.0 版本所存在的一些主要问题,针对这些问题,我们在窗口期迅速的组织了 v1.0 到 v1.5 版本的架构升级。这里包括几个核心点:
- 1、数据问题,要确保数据的多副本保存以及独立的生产和测试库
- 2、将一些繁杂且非核心业务流程中的模块从核心业务系统中拆分出来
- 3、统一交互入口和统一用户体系
- 4、基于自建 dc 的业务系统及数据迁移
v1.5 版本的架构图
相比较来说,在 v1.5 我们基于不同的业务系统属性,从网络层面做了隔离;数据层面从单机走向了主从;业务层面从单体走向了多体。从现阶段来看,v1.5 虽然解决了 v1.0 中的一些问题,但是并没有从根本上解决,只是从不同的角度做了一些权衡和过渡。
PS: 架构过程的本身就是一种权衡的过程。
这个过程中:
-
通过引入 minio 和 nacos 来填补了前一个版本中在分布式文件存储、统一配置管理上的一些问题,这些调整让团队在业务快速支撑和架构的扩展性上看到了一些新的希望;
-
使用 java 重写了数据分析中心和后台管理系统及拆分重写其他非核心业务子系统,释放掉团队在语言技术栈上的包袱,将生产力聚焦到业务和基础设施建设上去。
从 1.5 到 2.0 的架构演进及其原因
本质上 v1.5 就是个过渡期的版本,它需要解决的问题就是增加业务和架构扩展可能性的问题,在这个版本中统一账号体系和鉴权、发布问题、多机房数据备份问题等还依旧存在,也一直悬在研发同学的头上。
PS: 在团队持续发展的过程中,除了不同业务系统的支撑外,笔者也在积极筹备基础设施的建设,包括 k8s 体系建设、网关建设、sso 建设等等。这些 infra 的积累促成了 v1.5 到 v2.0 的快速演变。
从 v1.5 到 v2.0 这里笔者将其分为两个部分,一块是业务,一块是 infra。
基础设施体系
基础设施体系从之前的不可控到可控,我们用了差不多 6 个月的时间;包括同城双机房 DC 建设、基于 k8s 的部署和运维、引入网关来实现统一路由和鉴权、引入 SSO、引入 技术中台服务等;在当前业务体量不是很大的情况下,左手业务支撑、右手基础设施完善,对于我们团队来说也是充满各种挑战的。
图:双机房系统拓扑
核心数据域机房主要承担数据的持久化和多数据备份能力,包括 数据库和分布式文件系统;这里提到的 gpu 和 其他三方子系统因不在我们主业务体系之内,不使用 k8s 来托管,因此也放在了机房a。
核心业务域机房主要承担所有子系统的 k8s 托管(网关和业务数据库除外);网关主要是因为我们没有对外统一的 lb 层,因此在这个阶段还是将其直接部署在物理机上(固定ip,作为流量入口)。使用 helm + gitlab 基本上实现了一套基础版的 devops 体系结构,一定程度上解决了 v1.0 和 v1.5 的发布问题。
业务架构演进
在前面基础设施体系的基础上,业务架构上也基于 v1.5 向前走了一大步。
- 1、引入了网关 + SSO 账号中心,解决流量路由、统一鉴权和统一账号体系问题;其中包括调整了所有业务子系统的 context-path 以便于网关的路由转发规则设定;统一账号体系中,内部基于 oauth 2 协议做了一套实现,将所有三方账号体系与业务内部的账号体系进行关联整合;将鉴权逻辑挪入统一的 sdk 中去,通过网关解析 token 之后将用户信息携带到业务系统中。
- 2、引入数据集成子系统,解决所有三方数据集成的出入口问题,并且依托既定的数据标准实现所有出入口数据的标准化接入和标准化输出。
- 3、引入技术中台服务,提供对接所有三方 api 、 dfs 标准化接口以及一些公用的服务能力。
v2.0 架构图如下:
PS: 这里做了一些省略,整体架构逻辑基本围绕微服务那套来的。
实际上,从整个演进过程来看,前期架构的灵活性很大程度上受限于基础设施层面。v2.0 中最大的变化,对内是将网关和账号中心与内部业务系统的整合,通过 sdk 和脚手架和固化一些常规逻辑;对外是引入数据集成服务系统,来承接掉之前核心业务系统中的数据出入口及数据转化等问题,并基础一套统一的数据标准提供一组内外交互的 api。
因为应用服务全部托管 k8s,在这套体系上我们基本无成本的构建了一套监控告警体系,实现了对在线业务系统的指标可视化。
总结
历时 1 年左右的时间,H 项目从一个单一体系架构演变成了常规的微服务体系,整个过程,从 coding 到人肉发布运维、从接网线到双dc 搭建、从线上黑盒运行到指标可视化、从一个服务到一组服务;架构的演进受到了多方面因素的影响,但是总归是向着更省成本,更加灵活的方向前进的。
作为一个软件开发工程师,其在向架构转型的过程中,从笔者自身角度来看,网络这块是比较大的瓶颈,实际的场景中网络环境可以直接影响到了整体架构的可行性;其次是对于业务架构发展的前瞻性,在这个案例中,账号中心和网关服务是完全独立于业务系统之外的,当然笔者萌生这两个非业务系统的初衷也并非是仅服务 H 项目,事实证明,在后续的项目中,也确实可以直接复用这类基础设施型的服务。
最后结合这个项目,再回过头去看待过去几年各互联网公司都在搞的研发效能这个事情,从个人经历来看,研效更多的是在基础设施层面,不管是中台架构、传统的 paas + 镜像化到 k8s 、还是 service mesh 再到云原生体系,基本上都是侧重在基础设施领域。
业务架构的变革,源头有两个,一个是业务驱动的自上而下的变革,一个是由基础设施的驱动的自下而上的变革。H 项目的架构演进从本质上讲也就是贴着这两个源头发展的。