从单机到百万并发:一个架构师的系统演进实战笔记
几年前,我接手了一个日活只有几千的社区应用。数据库是单点的MySQL,应用服务器就一台,所有文件都堆在本地磁盘。
一切都很简单,直到有一天,一篇帖子意外爆火。
服务器CPU飙到100%,数据库连接池耗尽,整个站点挂了半小时。那是我第一次真切地感受到,所谓的“高并发”和“高可用”,不是教科书里的概念,而是悬在头顶的达摩克利斯之剑。
从那天起,我和团队踏上了漫长的系统演进之路。今天,这套系统已经能平稳应对千万级日活和百万级的瞬时并发。
回头看看,这条路没有银弹,有的是一连串具体的选择、权衡,以及无数个填坑的夜晚。
起点:别在单点故障上跳舞
所有分布式系统的设计,起点都是同一个问题:消除单点故障。
我们的第一次重构,目标极其朴素:让网站别那么容易挂。
- 数据库:从单实例MySQL,迁移到“一主多从”。读写分离是第一步,用个简单的中间件或者直接在代码里根据SQL类型路由。别想得太复杂,先跑起来。
- 应用服务器:前面加个负载均衡器(Nginx或硬件F5),后面部署至少两台无状态的应用服务器。会话(Session)怎么办?立刻从本地移到Redis里。这是从“有状态”到“无状态”的关键一步,做不到这点,水平扩展就是空谈。
- 静态资源:图片、JS、CSS别再放服务器本地了。全部扔到对象存储(比如S3或OSS),用CDN加速。成本立竿见影地降,访问速度嗖嗖地升。
这个阶段,架构图看起来像个“三明治”。虽然简单,但你已经有了最基本的弹性。一台服务器宕机,流量可以切到另一台。数据库主库挂了,可以手动(或半自动)切换到从库。
坦白讲,80%的中小规模系统,做到这一步,已经能解决大部分可用性问题了。
当流量真的涌来:拆分与隔离
用户量上来后,你会发现所有功能都挤在一个巨无霸应用里,互相拖累。一个不重要的后台统计查询拖慢数据库,导致整个前端页面卡死。
是时候拆分了。
我们不是一上来就搞微服务那套复杂的治理体系。而是用了更务实的“垂直拆分”思路:
- 按业务领域拆库:用户中心、内容、订单、消息......每个领域有自己的数据库。避免跨库join,通过应用层代码或者API来组装数据。数据库的压力被隔离了。
- 按功能模块拆应用:把用户服务、内容服务、搜索服务拆成独立部署的应用。它们之间通过RPC(比如gRPC、Dubbo)或者简单的HTTP API通信。
拆分的核心原则是隔离与自治。一个服务挂了,尽量不影响其他服务。给核心服务(如支付、登录)分配更好的硬件和更独立的资源。
这里有个真实的教训:我们曾把“发帖”和“读帖”的流量混在一起。晚上一个热门话题出现,大量的发帖请求(涉及写入和推送)直接把服务打满,导致想读帖的用户也看不了。后来我们把“写流程”和“读流程”在服务层面就做了资源隔离,甚至用了不同的线程池和数据库连接池,问题才解决。
高并发设计,很多时候就是“隔离”的艺术。
应对峰值:缓存、队列与池化
真正的流量洪峰来临时,光靠拆分不够。你需要三件法宝:缓存、队列、池化。
缓存,但别乱用:Redis人人都在用,但用对很难。我们的策略是:
- 多级缓存:本地缓存(Guava Cache, Caffeine) + 分布式缓存(Redis)。热点数据先在本地扛一波,减轻Redis压力。
- 缓存模式:读多写少的数据,用Cache-Aside(旁路缓存)。写多或一致性要求高的,考虑Write-Through。
- 防雪崩:缓存大面积失效是灾难。给不同的Key设置不同的、随机的过期时间。或者用“永不过期+后台更新”策略。
- 防穿透:对于数据库中根本不存在的查询(比如不存在的用户ID),也在缓存里存个空值(或标记),别让请求直接打到DB。
- 队列,削峰填谷:秒杀订单、IM消息推送、日志处理......所有非实时、耗时的操作,都扔到消息队列(Kafka, RabbitMQ)里。让系统平滑地处理请求,而不是被瞬间脉冲击垮。队列也是服务解耦的利器。
- 池化,管理稀缺资源:数据库连接、HTTP连接、线程......全部池化。设定合理的上限和超时时间,避免一个慢查询耗尽所有连接,导致连锁故障。
更高阶的思考:弹性、观测与混沌
当系统组件多到画一张图都费劲时,架构师的工作重心会转移。
弹性设计:
- 服务降级:高峰期,关闭非核心功能(如推荐、皮肤),保障核心链路(如浏览、下单)。
- 熔断与限流:用Hystrix、Sentinel等工具,当一个下游服务失败率达到阈值,自动熔断,避免资源被拖死。在入口处做好限流,只放系统能承受的流量进来。
- 弹性伸缩:在云上,根据CPU、QPS等指标,自动扩容缩容实例。这是应对不确定流量的终极武器。
- 可观测性:日志(ELK)、指标(Prometheus/Grafana)、链路追踪(SkyWalking, Jaeger)一个都不能少。没有度量,就没有优化。 你必须要能快速回答:现在系统整体健康度如何?哪个服务慢了?这个API的错误率为什么升高了?
- 主动注入故障:是的,你没看错。我们开始定期做“混沌工程”实验。随机杀掉某个服务实例、模拟网络延迟、给数据库注入CPU压力......在可控的环境里提前暴露系统的脆弱点,这比在线上真实故障时手忙脚乱要强一万倍。
写在最后:演进,而非颠覆
回顾整个过程,最重要的心得是:分布式系统的能力是长出来的,不是设计出来的。
不要试图在第一天就设计一个能承载一亿用户的完美架构。那会过度复杂,拖慢你的业务迭代。
正确的做法是,根据当前和可预见的未来的压力,做出刚好够用的设计,但同时为下一步演进留好接口和可能性。 比如,在代码里,早一点把“用户数据访问”抽象成一个独立的模块或接口,这样未来把它拆成独立服务时,会容易得多。
架构师的价值,不在于画出多漂亮的架构图,而在于在每一次流量增长、每一次技术选型、每一次故障复盘时,带领团队做出那个当下最合理、并为未来负责的决策。
这条路没有终点。新的技术(如Service Mesh、Serverless)不断涌现,但底层逻辑不变:在复杂度、成本、性能和可用性之间,寻找那个动态平衡点。
你的系统,现在在哪个阶段?面临最迫切的挑战又是什么?