
当前,我们的系统在运行上保持了卓越的稳定性,受到了广泛的好评。每天的高峰时段,系统请求量已经稳定地达到了每秒1万次,而日活跃用户数(DAU)也已经增长到数十万。公司的CEO非常兴奋,计划继续改进产品功能,并展开新一轮的运营推广,以争取在下一个双十一达到超过百万的DAU。
此时,我们开始考虑如何通过技术优化来改进系统,以支持更高的并发流量,包括支持超过百万的DAU。
于是你重新审视了自己的系统架构,分析系统中有哪些可以优化的点。

目前,我们的系统仍然采用一体化架构,这意味着所有功能模块,如订单、用户、支付、物流等,都被打包到一个庞大的Web应用中,然后部署在应用服务器上。然而,你对这种部署方式感到有些疑虑,因此进行了一番Google搜索,发现在系统发展到一定阶段,都需要进行微服务化的拆分。你也看到了淘宝的“五彩石”项目对整体架构扩展性的巨大影响,这让你备感兴奋。
但一直困扰你的一个问题是:是什么因素促使我们将一体化架构拆分成微服务化架构?难道系统的整体QPS达到1万或2万就一定需要进行微服务化拆分吗?
一体化架构的痛点
首先,回溯一下为什么最初选择一体化架构。在电商项目刚刚启动的阶段,你主要是希望能够快速建立项目,以便更早地将产品投放市场,快速验证概念。在系统开发的初期,这种架构为你带来了很大的便捷,主要体现在以下几个方面:
简化开发:一体化架构使开发变得简单而直接,代码和项目都集中管理,这有助于高效的开发流程。
减少维护成本:只需维护一个工程,减少了维护系统运行所需的人力成本。这种方式更容易管理和维护。
目标性排查问题:在出现问题时,只需排查这一个应用进程,有助于更精确地定位和解决问题。
然而,随着系统的功能日益复杂和开发团队规模的增大,你逐渐开始感受到一体化架构的一些缺陷,主要在以下方面体现出来:
在技术层面,数据库连接数可能成为系统的瓶颈
数据库的连接是比较重的一类资源,不仅连接过程比较耗时而且连接 MySQL 的客户端数量有限制,最多可以设置为 16384(在实际的项目中,可以依据实际业务来调整)。
尽管数据库的最大连接数看起来很大,但由于系统采用了一体化架构,部署结构没有分层,应用服务器直接连接数据库,因此前端请求量的增加会导致部署的应用服务器扩容,从而数据库的连接数也会急剧增加。
让我用一个例子来说明这个问题。我之前维护的一个系统中,数据库的最大连接数被设置为8000,而应用服务器部署在大约50台虚拟机上,每个服务器会与数据库建立30个连接。然而,数据库的连接数却远远超过了30 * 50 = 1500。原因是,系统不仅需要支撑来自客户端的外部流量,还需要部署独立的应用服务以支持来自其他部门的内部调用,还需要部署队列处理服务以处理来自消息队列的消息。
所有这些服务都直接连接到数据库。加在一起,高峰时数据库的连接数接近3400。因此,一旦发生一些大规模的运营推广活动,服务器就需要扩容,数据库连接数也随之增加,系统基本上就会处于最大连接数的边缘。这就像一颗定时炸弹,随时可能影响到服务的稳定性。
举例来说,你的垂直电商系统团队可能会被分成不同的小组,如用户组、订单组、支付组、商品组等。当这么多小团队一起维护一套代码和一个系统时,会出现各种协作问题。
不同团队之间的沟通有限,如果一个团队需要实现发送短信的功能,有些开发同学可能会认为最快的方式是自己写一套,而不是询问其他团队是否已有可用的解决方案。这种方式并不合适,因为它会导致功能服务的重复开发。由于所有代码都部署在一起,每个人都提交到同一个代码库,代码冲突是不可避免的。
此外,功能之间的紧密耦合可能导致即使只是对小逻辑进行微小更改,也可能使其他功能无法正常运行,从而需要对整个功能进行回归测试,延长了交付时间。模块之间相互依赖,一个小组中的错误可能影响其他团队维护的服务,对整体系统的稳定性造成重大影响。
最后,一体化架构也会对系统运维产生重大影响。想象一下,在项目初期,你的代码可能只有几千行,一次构建只需要一分钟,这使得你可以非常敏捷和灵活地频繁上线、进行变更和修复问题。但当系统扩展到几十万行甚至上百万行代码时,一次构建过程包括编译、单元测试、打包和上传到生产环境,可能需要十几分钟,而且任何小的修改都需要构建整个项目。上线变更的流程变得不太灵活。而这些问题可以通过微服务化的拆分来解决。
如何使用微服务化解决这些痛点
在之前的社区业务中,我们一开始采用了一体化架构,数据库已经进行了垂直分库,将用户库、内容库和互动库分开。此外,我们还将工程拆分为不同的业务池,包括用户池、内容池和互动池。然而,随着前端请求量的不断增加,我们注意到无论是哪个业务池,用户模块都是请求量最大的模块,而用户库也是请求量最大的数据库。
这一现象可以理解,因为不论是内容还是互动,都需要查询用户库以获取用户数据。因此,尽管我们拆分了业务池,但实际上每个业务池都需要连接到用户库,而且请求量都很大。这导致用户库的连接数比其他库多一些,容易成为系统的瓶颈。

那么我们怎么解决这个问题呢?
实际上,我们可以将与用户相关的逻辑部署为一个单独的服务。其他业务池,包括用户池、内容池和互动池,可以连接到这个服务来获取和更改用户信息,这意味着只有这个服务可以连接到用户库,而其他业务池不再直接连接用户库获取数据。
由于这个服务只处理与用户相关的逻辑,因此不需要部署太多实例来处理流量,这有效地控制了用户库的连接数,提高了系统的可扩展性。因此,我们还可以将内容和互动相关的逻辑分别独立出来,形成内容服务和互动服务。
通过按照业务需求进行横向拆分,我们成功解决了数据库层面的扩展性问题。
举例来说,在社区业务中,多个模块需要使用地理位置服务,将IP信息或经纬度信息转换为城市信息。例如,在推荐内容时,需要结合用户的城市信息进行附近内容的推荐,同时在展示内容信息时也需要显示城市信息等等。如果每个模块都单独实现这套逻辑,将导致代码复用性不足。
因此,我们可以将将 IP 信息或经纬度信息转换为城市信息的逻辑封装成一个单独的服务,供其他模块调用。换句话说,我们可以将与具体业务无关的公共服务抽象出来,将其独立成单独的服务。
通过以上两种拆分方式,系统中的每个服务具有内聚性,维护人员职责清晰,新增功能时只需测试自身服务,而一旦服务出现问题,也可以通过服务熔断和降级的方法减少对其他服务的影响。这种模块化和服务化的架构方式提高了系统的可维护性和可扩展性。
外由于每个服务都只是原有系统的子集,代码行数相比原有系统要小很多,构建速度上也会有比较大的提升。
其实系统的 QPS 并不是决定性的因素。影响的因素我归纳为以下几点:
系统中使用的资源出现扩展性问题,尤其是数据库的连接数出现瓶颈;
大团队共同维护一套代码,带来研发效率的降低和研发成本的提升;
系统部署成本越来越高。
在架构演进的初期和中期,性能、可用性、可扩展性是我们追求的主要目标,高性能和高可用给用户带来更好的使用体验,扩展性可以方便我们支撑更大量级的并发。但是当系统做的越来越大,团队成员越来越多,我们就不得不考虑成本了。
欢迎交流:微信号 greencoatman


