高并发系统设计:从理论到实践
开篇:为什么我会去思考这个问题?

我在一家做内容推荐的互联网公司工作,主要负责后端架构和核心服务的设计与维护。这几年间,我亲身经历了一个从日活几万到千万级流量系统的演进过程。在这个过程中,高并发成了我们最常挂在嘴边的问题——不是因为喜欢它,而是因为它几乎每一步都在“找事”。
记得最早接手的一个项目,是一个用户行为埋点上报接口。当时团队觉得这不就是个 POST 接口嘛,能有多难?结果刚上线一天,就被打爆了。数据库连接池满了、消息队列堆积、服务器 CPU 疯狂飙升……那真是一次血泪教训。
从此以后,我对“高并发”这个词就格外敏感,也开始有意识地在设计阶段就把性能、容量、扩展性这些因素考虑进去。这篇文章我希望通过几个真实案例,结合我的经验和踩过的坑,带你一起走过一次从需求到落地的高并发系统设计之路。
问题描述:那些年我遇到的并发挑战

我们来看看几个典型的业务场景:
场景一:实时推荐系统
我们的主站首页采用的是千人千面的推荐策略,每个用户访问首页时,后端会调用一个推荐接口生成10条推荐内容。这个接口本身依赖于多个下游数据源(比如用户画像、商品信息、召回服务等),而且要求响应时间控制在300ms以内。
随着用户量的增长,接口TP99逐渐从250ms上涨到了480ms以上,用户开始出现流失迹象,产品经理天天跑来催:“推荐慢了一秒,DAU掉了好几千!”
场景二:活动报名抢购
在一次促销活动中,我们需要提供一个限时抢购接口。用户点击“立即抢购”按钮后,需要完成库存扣减、订单创建、优惠券发放等多个操作。刚开始预估并发量是1W QPS左右,实际开抢那一刻直接飙到了3W+,MySQL锁等待严重,很多请求超时,最后客服电话被打爆……
场景三:数据上报接口
我们在App中嵌入了大量的埋点数据采集代码,用户操作行为会实时上报到服务端。原本是直接写数据库的逻辑,在百万级请求下导致数据库连接池耗尽,进而引发整个服务崩溃。
这三个场景虽然不同,但背后都藏着相似的技术挑战:
- 高QPS带来的压力
- 长尾请求拖垮整体响应
- 资源竞争导致的瓶颈
- 突发流量难以预料
面对这些问题,单纯靠堆机器已经无法满足需求。我们必须从系统设计、架构分层、缓存策略、异步处理、数据库优化等多个角度去统筹规划。
解决方案:一步步打造高并发能力

下面我以“实时推荐系统”为例,详细讲讲我们是如何从零开始优化这个系统的。
第一步:性能分析,找到瓶颈所在
首先我们要明确系统性能瓶颈在哪。我们使用了如下几种手段进行排查:
- 监控告警系统:观察QPS、RT、错误率
- 链路追踪工具(SkyWalking):定位接口各个子调用耗时
- JVM监控(Prometheus + Grafana):看GC、线程、内存情况
最终发现,推荐接口的主要延迟集中在以下几个环节:
- 用户画像服务调用较慢(平均250ms)
- 多个数据源并行拉取合并效率低
- 数据组装和排序逻辑复杂且同步执行
- Redis穿透导致部分热点查询落到DB
第二步:拆解模块,提升并行度
为了解决这些问题,我们做了几项关键改造:
1. 抽象公共画像服务为独立组件
之前所有需要用户标签的服务都是直连画像服务,导致画像服务压力巨大,成为单点瓶颈。于是我们将画像服务抽象成统一网关,加了一层LRU本地缓存 + Redis二级缓存,并做多级降级机制。
2. 使用CompletableFuture实现多数据源并行获取
Java原生的CompletableFuture非常适合这种需要并行调用多个服务的场景。我们通过封装,让开发者可以用非常直观的方式组织多个异步任务,并指定超时时间、失败重试策略。
CompletableFuture<UserProfile> profileFuture = CompletableFuture.supplyAsync(this::fetchUserProfile);
CompletableFuture<List<Product>> productFuture = CompletableFuture.supplyAsync(this::fetchProducts);
// 合并两个future结果
profileFuture.thenCombine(productFuture, (profile, products) -> {
// 组装推荐结果
return recommend(profile, products);
});
3. 引入Caffeine缓存减少冗余计算
推荐算法中有大量重复计算,特别是对热门用户和商品组合。我们在服务侧引入Caffeine缓存,设置合理的过期时间和大小,大大减少了计算量。
LoadingCache<Key, List<Recommendation>> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(key -> computeRecommendations(key));
4. 异步化处理非核心流程
一些不影响当前推荐结果的数据收集任务,例如曝光埋点记录、推荐命中路径分析等,我们改用Kafka进行异步落盘处理,避免阻塞主线程。
// 异步发送曝光数据
kafkaProducer.send(new ProducerRecord<>("exposure", exposureData));
踩坑经验:这些坑我替你踩过了

高并发系统从来不是一蹴而就的,中间我们也踩了不少坑,这里分享几个典型例子:
坑点1:Redis雪崩 + 缓存击穿 + 穿透
我们一开始用了简单的Redis缓存机制,结果某个深夜凌晨两点,突然报警说数据库连接数暴涨。查下来发现是因为大批热点数据同时过期,导致大量请求直接打到数据库。我们后来加上了随机过期时间,并引入布隆过滤器防止非法请求穿透到底层。
解决方案示例:
String key = "user_profile:1001";
String cacheValue = redis.get(key);
if (cacheValue == null) {
if (bloomFilter.contains(key)) {
// 可能存在,走数据库查询
String dbValue = queryFromDatabase();
int expireTime = 60 * 5 + new Random().nextInt(60); // 加上随机过期时间
redis.setex(key, expireTime, dbValue);
return dbValue;
} else {
// 布隆过滤器判断不存在,直接返回空
return null;
}
} else {
return cacheValue;
}
坑点2:线程池配置不合理

为了提高并发,我们给很多服务调用配上了自定义线程池。但有一回线上接口大面积超时,才发现线程池默认是无界队列,导致内存爆掉。后来我们全部换成有界的队列,并设置了合适的拒绝策略。
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(40);
executor.setQueueCapacity(200);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();

坑点3:跨数据中心调用未隔离
我们有一个服务部署在北京机房,另一个在上海机房。当北京机房网络波动时,大量请求被挂起,导致上海服务也被拖死。解决办法是在调用时加熔断机制(我们用了Sentinel),并在服务发现层面做同城优先路由。
效果总结:优化之后的变化
经过这一系列优化之后,我们的推荐接口表现有了显著提升:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 平均响应时间 | 480ms | 220ms |
| TP99 | 780ms | 310ms |
| 错误率 | 3%~5% | <0.1% |
| 单实例QPS | 1200 | 3500 |
不仅提升了用户体验,还帮产品部门争取到了更多的投放空间。最重要的是,稳定性明显增强,夜里的报警频率也大幅下降。
经验分享:我想告诉你的几点建议
经历过这么多次“修修补补”的优化,我也积累了一些心得,希望能给你带来启发。
1. 性能永远要在设计阶段就开始考虑
很多人习惯先完成功能再优化性能,但在高并发系统里,这一点往往代价很高。一定要在初期就想清楚服务的负载预期、数据流向、热点处理等问题。
2. 不要过度迷信分布式
很多人一上来就要搞微服务、Docker、K8s,其实很多时候,单体服务也能扛住很大的并发,特别是在业务初期。把精力放在真正的瓶颈上才是正道。
3. 做好“防御式”编程
对于每一个对外暴露的接口,都要考虑:
- 限流:别让外部流量把内部压垮
- 降级:出了问题至少能返回基本数据
- 熔断:不要连锁反应,一个服务崩影响整体
4. 学会在监控中“听风辨位”
一个好的监控系统可以让我们提前感知风险。我们自己搭建了一整套Prometheus+Grafana+AlertManager的体系,每天早上看一眼就知道昨天有没有异常,哪些接口需要进一步优化。
5. 技术方案也要权衡成本和收益
有时候一个Redis缓存就能搞定的事情,何必用ElasticSearch?有时候一个定时补偿就能解决的问题,非要搞得那么复杂吗?记住一句话:越简单越好,能不用就不上。
结语:高并发不是终点,而是起点
高并发系统的设计,从来都不是一件轻松的事。它需要你有足够的技术视野,也需要你在细节处下功夫。但我始终相信,只有真正经历过线上问题的人,才会理解什么叫“稳如老狗”。
希望这篇文章能帮你少走点弯路,也能让你在面试或工作中说出一句“哦,这个问题我以前做过,我们可以这样处理”。
如果你正在经历类似的挑战,欢迎留言交流。说不定哪天我们就合作在一起优化下一个高并发项目了呢 😎。
附录:推荐阅读 & 工具清单
- 《高性能MySQL》 - Baron Schwartz 等著
- 《Reactive Design Patterns》 - Jonas Bonér 著
- SkyWalking(链路追踪)
- Prometheus + Grafana(监控)
- Sentinel(流量控制)
- Caffeine / Redis(缓存)
- Kafka / RocketMQ(消息队列)
如需完整代码示例或者想要具体某一部分的深入探讨,也可以随时联系我。

评论 0