1. 可扩展系统的本质:从“能跑”到“跑不崩”的进化之路
我在做系统设计咨询时经常碰到这样的场景:某个系统上线初期只有几百个用户,单体架构跑得飞快,半年后用户量涨到几万,数据库连接池先被打满,紧接着应用服务器CPU持续100%,然后整个服务就像多米诺骨牌一样接连倒下。这时候大家才想起来问一个问题:当初设计系统的时候,怎么就没考虑可扩展性?
可扩展性这件事,本质上是在回答一个问题:当系统负载翻十倍、翻百倍的时候,你的架构还能不能撑住?撑不住的话,需要做多少改动才能撑住?改动成本是加几台机器就能搞定,还是需要把整个系统推倒重写?
先说一个常见的认知误区。很多人把“可扩展”等同于“用微服务”。其实微服务只是实现可扩展性的一种手段,而且不是银弹。我见过把单体拆成二十多个微服务、结果运维成本比业务价值还高的项目,也见过单机扛住日均千万请求的经典案例。可扩展性的核心不在于架构形式,而在于你是否遵循了若干底层原则。
这些原则包括:无状态设计、数据与计算分离、异步解耦、水平扩展优先于垂直扩展、缓存分层、容量预估与压测验证。后面我会逐一拆开讲,但先建立一个整体认知:可扩展系统不是设计出来的,是演进出来的。你不可能在项目第一天就设计出完美的可扩展架构,但你可以从一开始就避免做出“把自己堵死”的决策。
什么样的决策会把自己堵死?我总结了三类最常见的问题。
第一类是状态本地化。用户会话存在单台服务器的内存里,登录请求被负载均衡分发到别的机器就丢失会话。这种设计天然无法水平扩展,因为每台服务器都持有独有的状态,你加再多机器也只是让状态更分散。
第二类是数据耦合过深。业务逻辑直接读写数据库表,没有服务层封装,没有缓存层兜底。一旦数据量上来,你连“先加缓存还是先分库”的选择余地都没有,因为所有改动都会波及所有调用方。
第三类是缺乏容量意识。从来没做过压测,不知道系统瓶颈在哪里,等线上出问题才临时排查。这种“盲人摸象”式的运维方式,在系统规模变大之后必然翻车。
理解了这些底层问题,我们就可以进入正题:怎么设计一个真正可扩展的系统。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 架构层面的可扩展策略:分层、拆分与无状态
2.1 无状态设计与水平扩展的关系
无状态设计是可扩展系统的基石,这句话我再强调也不为过。所谓无状态,指的是应用服务器不保存任何与特定用户相关的业务数据。每个请求都是独立的,可以由任意一台服务器处理,处理完就丢弃所有上下文。
为什么要坚持无状态?因为只有无状态的服务才能被负载均衡随意分发。你今天有3台应用服务器,明天流量翻倍,再加3台就行,不需要迁移任何数据,不需要修改任何配置。这就是水平扩展的魔力——通过增加机器数量来提升系统容量,而不是把单台机器升级成更贵的机器。
用户会话状态怎么办?方案很多:存到Redis里,存到数据库里,或者用JWT之类的无状态令牌。我个人比较推荐Redis方案,因为它在读写速度和持久化之间取得了比较好的平衡。JWT虽然完全无状态,但会话失效和强制下线等功能实现起来比较别扭,适合对安全要求不高的场景。
文件上传这种场景也要注意。如果用户上传的图片存在应用服务器的本地磁盘,那这个应用服务器就有状态了——它持有其他人没有的数据。正确做法是把文件存到对象存储服务(比如MinIO、阿里云OSS)或者分布式文件系统里,应用服务器只负责转发请求和返回URL。
我这里要特别提一下数据库连接池的问题。即使应用本身是无状态的,数据库连接也是有限的资源。如果每台应用服务器都维护自己的数据库连接池,那么服务器数量增加会导致数据库连接数线性增长,很快数据库就会成为瓶颈。解决思路是控制连接池上限,并且使用数据库中间件(如ProxySQL、MyCat)来做连接复用。
2.2 分层架构:为什么说“整洁架构”是可扩展的前提
很多刚入行的同学觉得分层架构是老生常谈,Controller直接写SQL、Service层形同虚设、DTO和实体混用的代码到处都是。这种代码在小项目里确实能跑,但一旦需要扩展,你就知道苦头了。
举一个我实际遇到过的例子:某个项目在Controller里直接操作数据库,刚开始只有一种调用方,一切正常。后来需要对外开放API,又来了一个移动端要对接,紧接着上游公司要求通过消息队列异步推送
