高并发系统设计:从理论到实践 —— 我的实战经验分享

DNS等一等
2025-06-19 09:58
阅读 442

背景介绍:为什么要聊这个话题?

背景介绍:为什么要聊这个话题?

我是一名后端工程师,入行已经五年了。这五年中,从早期的小型项目到后来支撑百万级日活的互联网产品,我一直在和“高并发”打交道。尤其是在加入现在这家电商公司之后,经历过多次大促、秒杀等极限场景的洗礼,对高并发系统的设计也有了更深刻的理解。

我们团队负责的是一个电商平台的核心交易模块,包括下单、支付、库存扣减等关键路径。在日常流量下表现还不错,但一到大促期间,比如“双十一”,流量暴涨十几倍,系统的稳定性就变得尤为关键。而我在其中主要参与了接口性能优化、数据库调优以及服务扩容等多个关键环节。

今天想借这篇文章,结合自己亲身经历的一个真实项目,聊聊高并发系统设计到底是怎么一回事。


问题描述:一次失败的大促让我印象深刻

问题描述:一次失败的大促让我印象深刻

那是在我们平台的第一次“双十一”预热活动。当时我们的服务架构还算简单:

  • 单体 Java 应用(Spring Boot)
  • MySQL 主从 + Redis 缓存
  • Nginx 做负载均衡 + 简单限流

活动一开始,订单服务迅速被打爆。用户不断刷新页面,大量请求涌入服务器,导致 CPU 爆表、线程池耗尽、MySQL 出现死锁、Redis 大量缓存穿透……整个系统几乎瘫痪。

最糟糕的是,很多用户付款成功了但没生成订单,还有一些用户被重复扣款。这不仅影响了用户体验,还造成了非常大的舆论压力和资损风险。

那次事故之后,我们痛定思痛,决定重构整个订单服务,目标是能够应对千万级 QPS 的并发访问。


解决方案:如何构建一个能扛住高并发的系统?

解决方案:如何构建一个能扛住高并发的系统?

架构调整:从单体到微服务+异步解耦

首先我们将原来的单体应用拆分为多个独立的服务模块:

  • 订单创建服务(Order Service)
  • 支付回调服务(Payment Service)
  • 库存服务(Inventory Service)
  • 用户中心服务(User Center)

每个服务都部署为独立的 Spring Cloud 微服务,并通过 Feign 实现远程调用。引入 RabbitMQ 做消息队列,将同步调用改为异步处理,尤其适用于下单后的后续流程(如积分奖励、短信通知、推送数据给风控系统)。

示例:使用 RabbitMQ 异步处理订单完成事件

// 下单完成后发送消息
public void createOrder(OrderDTO orderDTO) {
    Order order = saveOrder(orderDTO);
    rabbitTemplate.convertAndSend("order.created", order.getId());
}

// 消费者监听消息进行后续处理
@Component
public class OrderConsumer {

    @RabbitListener(queues = "order.created")
    public void handleOrderCreated(Long orderId) {
        // 发送短信、更新积分、推送到风控系统...
    }
}

这样的设计有效减少了主线程阻塞时间,提升了整体吞吐能力。


接口层面:限流与降级策略

面对突发流量,限流和降级是必须的。我们在网关层使用了 Sentinel 进行限流控制,限制单位时间内最多请求次数,并根据当前系统状态动态调整阈值。

降级方面,我们通过 Hystrix 配合 Sentinel,在服务异常或超时时自动切换到备用逻辑,比如返回缓存数据、跳过非核心步骤等。

# 示例:Sentinel 规则配置(每秒最多1000个请求)
resources:
  - name: /order/create
    limitApp: default
    grade: 1 # 1表示按QPS
    count: 1000

这种机制帮助我们在后续几次大促中避免了服务雪崩。


数据库设计:读写分离+分库分表

MySQL 是系统的瓶颈之一。我们采取了以下措施:

  1. 读写分离:主库写,从库读,降低主库压力。
  2. 分库分表:使用 ShardingSphere 按照用户ID做水平拆分,将订单表分成多个物理表,减少单表容量。
  3. 冷热分离:把历史数据迁移到单独的冷备库,只保留近90天的活跃订单。
  4. 索引优化:频繁查询字段建立组合索引,避免全表扫描。
  5. 缓存兜底:热点订单使用本地缓存(Caffeine)+ Redis 双重缓存结构,防止缓存击穿。
-- 分库分表示例:按 user_id % 4 拆分成4张子表
CREATE TABLE orders_0 (...);
CREATE TABLE orders_1 (...);
...

-- 查询时根据 user_id 定位子表
SELECT * FROM orders_#{user_id % 4} WHERE ...

这些优化让我们的 DB 抗压能力得到了显著提升。


服务治理:监控与弹性伸缩

我们使用 Prometheus + Grafana 搭建了完整的监控体系,涵盖 JVM、线程池、慢SQL、接口延迟、TPS 等指标,实时预警。并通过 Kubernetes 自动扩缩容来应对流量峰值。

Kubernetes 的 HPA(Horizontal Pod Autoscaler)根据 CPU 使用率自动增加 Pod 数量,极大缓解了突发流量带来的压力。

# Kubernetes HPA 配置示例
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

这套机制在双十一大促中发挥了重要作用。


踩坑经验:那些年我踩过的坑

1. Redis 缓存击穿差点搞挂 MySQL

在初期,我们使用 Redis 缓存热点商品信息,但在某次缓存失效的时候,大量请求直接打到了 MySQL,造成短暂不可用。

解决方法

  • 设置永不过期,后台异步更新
  • 使用布隆过滤器防空查
  • 加互斥锁控制重建缓存并发
// 伪代码:加互斥锁重建缓存
public Product getProduct(Integer id) {
    String cacheKey = "product:" + id;
    Object product = redis.get(cacheKey);
    if (product == null) {
        synchronized(this) {
            product = redis.get(cacheKey); // double-check
            if (product == null) {
                product = db.getProduct(id);
                redis.setex(cacheKey, 60 * 60, product);
            }
        }
    }
    return product;
}

2. RabbitMQ 死信队列处理不当导致消息积压

有一段时间,我们因为消费者异常退出导致大量消息堆积。最初没有设置 TTL 和死信队列,导致消息一直卡在 Broker 中,最后需要手动清理。

改进方式

  • 设置消息最大尝试次数(max retries)
  • 消息失败后进入死信队列,供人工介入处理
  • 使用幂等性去重消费

3. Feign 调用默认超时太短,服务依赖链路长导致超时雪崩

Feign 默认的连接和读取超时时间都很短,当多个微服务存在级联调用时,很容易出现层层超时。

解决方案

  • 统一设置合理的超时时间
  • 使用 Resilience4j 或 Sentinel 增强熔断能力
  • 关键服务间采用 Dubbo 替代 Feign 提高性能

效果总结:我们到底获得了什么?

经过一系列改造后,我们再次迎接大促的压力测试:

指标 改造前 改造后
最高 TPS 300/秒 8000+/秒
平均响应时间 >800ms <120ms
错误率 12% <0.5%
服务可用性 95% 99.99%

更为关键的是,系统具备了更好的可观测性和自愈能力,运维同学也能快速定位问题点,再也不用半夜提心吊胆接电话了 😅。


经验分享:给后端朋友们的一些建议

1. 高并发的本质是系统设计的平衡术

不要追求极致性能,而是要从成本、稳定、可维护性等多维度考虑。有时候一个合理的降级比一个不稳定的高性能更好。

2. 性能优化要从源头抓起

接口慢,不一定就是程序的问题;可能数据库索引不对、网络有瓶颈、锁竞争严重。建议大家用 APM 工具(比如 SkyWalking、Pinpoint)追踪调用链,找到真正的性能瓶颈。

3. 不要迷信任何中间件

像 Kafka、Redis、Zookeeper 这些工具虽然强大,但不是万金油。它们也有适用边界。比如,Redis 不适合用来做复杂事务操作,Kafka 不适合低延迟场景。

4. 预案永远比出事后再补救好

高并发系统一定要有完善的应急预案,比如:

  • 流量削峰(排队机制、令牌桶)
  • 熔断降级(提前演练)
  • 数据补偿机制(比如对账、补偿Job)

这些都要提前设计好,不能临时抱佛脚。

5. 多参与压测和故障演练

我们定期会组织故障注入演练(如模拟数据库宕机、Redis集群断连),通过这些模拟极端情况的方式不断提升系统的健壮性。


写在最后:技术是手段,业务才是目标

作为一名后端开发者,我一直坚信一句话:“再牛的技术也要服务于业务”。高并发系统不是为了炫技,而是为了让用户买得顺、付得快、体验好。

这些年走过的弯路、踩过的坑,都成为了我们团队宝贵的财富。希望这篇来自一线开发者的分享,能帮你在自己的项目中少走一些弯路。

如果你也在做高并发相关的工作,欢迎留言交流,一起进步!

—— By 一个爱写代码也爱思考的后端人

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝