高并发系统设计:从理论到实践
从一次系统崩溃说起:我在高并发系统设计中的实战经验分享

大家好,我是老张。一名在后端开发领域混迹多年的“代码搬运工”。今天想和大家聊一聊我在实际项目中参与构建一个高并发系统时的经历。
背景故事
那还是我刚加入目前这家公司的时候,公司要做一个大型的直播电商平台,目标用户群体是全国范围内的中老年用户。他们对价格敏感,喜欢看直播抢便宜货。听起来是个不错的市场定位,但随之而来的系统压力也让我第一次深刻体会到了什么是高并发下的真实挑战。
当时的我还在用老一套的方式设计接口,结果上线当天就被打脸了——还没等到大促开始,系统的QPS就冲上了5000+,服务直接瘫痪。订单接口、库存更新接口全线报错,报警短信疯狂刷屏。那次事故让我意识到:传统的单体架构已经撑不住这样的场景了。
面临的问题与挑战
第一个问题:请求雪崩
我们的订单创建接口是一个典型的读写密集型操作,需要调用多个服务:用户验证、库存检查、优惠券核销、积分变动等等。在高峰期间,这些服务一旦有某个慢下来,整个链条都会拖垮,甚至导致线程池被耗尽,服务不可用。
// 最初的设计(简化版)
public Order createOrder(CreateOrderRequest request) {
User user = userService.getUser(request.getUserId());
Product product = productService.getProduct(request.getProductId());
if (!stockService.checkStock(product.getId())) {
throw new BusinessException("库存不足");
}
if (!couponService.validateCoupon(request.getCouponCode())) {
throw new BusinessException("优惠券无效");
}
// ... 其他逻辑
}

这个设计在平时看起来很干净、清晰,但在高并发下就成了定时炸弹。任何一个外部服务卡一下,整个流程都被block住。
第二个问题:数据库瓶颈
一开始我们用了MySQL来存储订单数据,没有分库分表,所有订单都放在一张表里。刚开始数据量不大还好说,等订单量上去之后,查询性能直线下降。特别是统计类的报表查询,动不动就把数据库资源占满。
第三个问题:缓存穿透 + 缓存击穿
为了提高响应速度,我们在商品详情页加了缓存,但没做防击穿的机制。每次缓存过期,大量请求直接打到DB上,导致数据库瞬间负载飙升。尤其是促销结束后的那一波请求,差点让DB挂掉。
解决思路与方案设计
分布式架构改造
首先,我们把原来的单体服务拆分成微服务架构:
- 订单中心(order-center)
- 库存中心(inventory-center)
- 用户中心(user-center)
- 优惠券中心(coupon-center)
每个中心各自独立部署,互不干扰。服务之间的通信采用gRPC,比HTTP快了不少,而且结构化传输更可控。
同时引入了服务注册与发现(我们使用的是Consul),这样服务之间可以自动感知状态,容灾能力更强。
异步处理与队列削峰
对于非核心路径的操作,比如积分变化、消息推送、通知客服系统等,我们统一走异步队列,避免阻塞主线程。我们选用了Kafka作为消息中间件,后来也试过RabbitMQ,最终选择了Kafka,因为它的吞吐量更适合我们这种大数据写入的业务场景。
举个例子,在订单创建成功后,我们需要通知积分系统增加用户积分,这部分通过Kafka异步发送即可,不需要等待对方确认。
public void send积分ChangeEvent(Integer userId, Integer points) {
kafkaTemplate.send("points-change-topic", new PointsChangeEvent(userId, points));
}
这样一来,主流程的响应时间大大减少,也缓解了其他系统同步依赖的问题。
数据库优化
我们采取了以下措施来解决数据库瓶颈:
水平分库分表 我们采用了ShardingSphere来实现自动分片,按照用户ID做哈希取模分成了4个库,每个库里又按时间做了按月分表。例如
orders_202403,orders_202404。冷热分离 把历史订单移到单独的历史库中,线上库只保留最近半年的订单,提升查询效率。
索引优化 + 查询缓存 对于高频查询字段(如userId、orderId)都加了组合索引。另外我们自己维护了一个基于Redis的二级缓存,缓存热点订单信息,比如最近下单最多的商品、热销榜单等。
缓存策略升级
面对缓存穿透和击穿的问题,我们引入了两级缓存 + 降级机制:
- 本地缓存(Caffeine)用于快速响应当前节点的请求
- Redis集群作为共享缓存,避免重复计算
- 使用布隆过滤器防止恶意穿透攻击
举个缓存重建的例子:
public Product getProductFromCache(Long productId) {
Product product = caffeineCache.getIfPresent(productId);
if (product != null) {
return product;
}
// 加锁,只让一个线程去查 DB
synchronized (productId) {
product = redisService.get("product:" + productId);
if (product == null) {
product = dbService.getProduct(productId);
redisService.setex("product:" + productId, product, 60 * 60);
}
caffeineCache.put(productId, product);
}
return product;
}
当然,这套逻辑后来被封装进了一个叫 cache-loader 的组件,对外提供统一的缓存访问API。
接口限流 & 熔断降级
我们采用Sentinel来做限流熔断。比如订单接口设置每秒最多1万次请求,超过的直接拒绝或排队。服务之间调用如果出现异常,会自动触发降级逻辑,比如返回默认值或提示“服务繁忙,请稍后再试”。
配置示例(Spring Cloud Alibaba + Sentinel):
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8848
dataId: order-service-flow-rules
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow

规则文件内容大致如下:
[
{
"resource": "/api/order/create",
"count": 10000,
"grade": 1,
"limitApp": "default",
"strategy": 0
}
]
压测与监控体系建立
上线前我们用JMeter做了压测,模拟1万并发下单请求,观察各环节的响应时间和错误率。压测过程中发现了几个隐藏的性能瓶颈,比如某个日志写入接口没有做异步处理,导致CPU飙到98%,后来改成Logback的异步日志模式才解决问题。
监控方面,我们搭建了Prometheus + Grafana,结合SkyWalking做链路追踪,实时掌握系统运行情况。报警策略也做了分级,不同级别的问题触发不同的通知方式(邮件、钉钉、电话)。
踩过的坑和一些小插曲
记得有一次我们在做压测的时候,测试环境和生产环境的线程池配置一模一样,但就是跑不出预期的效果。后来发现是因为测试环境的机器资源不够,线程太多反而造成上下文切换频繁,性能反而下降。
还有一次,在高峰期出现了分布式事务的问题。我们最开始用的是Seata,但性能扛不住,特别是在订单退款这类多服务协调的场景下,容易死锁。最后我们改成了最终一致性方案,通过事件驱动的方式来保证数据一致,虽然不是强一致性,但胜在稳定可靠。
还有一点是关于数据库连接池的调参。我们最开始用的是HikariCP,默认的最大连接数是10,这在高并发下完全不够用。后来根据数据库的实际负载逐步调整到100,但也不能太大,否则数据库压力又会上升。这块参数调优真的是要结合压测结果和监控数据来做。
最终效果与收益
经过一系列改造后,系统的表现明显好转:
- QPS从最初的不到1000,提升到了稳定的15000+
- 请求成功率从68%左右提升到99.5%
- 数据库平均响应时间从800ms降到100ms以内
- 故障恢复时间从几小时缩短到几分钟级别
最重要的是,我们的团队养成了良好的性能意识和监控习惯。现在每次新功能上线前都会跑一遍全链路压测,确保不会因为一个小改动影响全局稳定性。
经验总结与建议
如果你正在做一个高并发系统或者正准备去做,我给你几点建议:
早做架构设计,不要边写边改 架构就像盖楼的地基,前期规划不好,后面修补起来代价极大。能一开始就设计成分布式的,就别犹豫,越往后拖问题越多。
限流、缓存、降级三板斧必须有 这些机制能在关键时刻救你一命。哪怕你的接口再快,也无法承受无限流量。保护系统的第一道防线就是限流。
数据层是核心战场 不管前端怎么优化,最终的数据压力都要落在数据库上。所以合理的分库分表策略、索引设计、缓存方案都非常关键。建议你花点时间学一下数据库调优和索引原理。
重视监控,做好预警 没有监控就等于盲人摸象。你要知道每个接口的响应时间、线程池的状态、缓存命中率、JVM堆内存变化等情况。推荐搭一套自己的监控告警系统。
团队协作很重要 高并发系统涉及的技术面广,不可能一个人搞定所有事情。前后端、运维、测试都需要配合。我们要做的不仅是写代码,还要推动技术落地、培训新人、制定规范。
持续学习和复盘 高并发的场景千变万化,不能靠一套模板包打天下。我们要不断从故障中学习,从压测中总结,才能做到心中有数。
如果你也在做高并发相关的工作,欢迎一起交流。我的微信公众号「码农老张」也会不定期分享类似的技术实践,有问题也可以留言讨论。愿我们都少踩坑,写出更高可用的系统!
写到这里,这篇文章也差不多结束了。希望我的经验和教训能对你有所启发。记住一句话:“架构是演进出来的,不是一开始就完美的。”加油!

评论 0