高并发系统设计:从理论到实践

前端散步者
2025-06-15 13:56
阅读 657

引言:为什么高并发值得认真对待?

引言:为什么高并发值得认真对待?

我第一次真正接触“高并发”这个词是在我工作的第三年,那时候我们公司正经历一次关键的业务转型,从原本的内部系统支撑转向对外提供 SaaS 服务。用户量迅速从几千增长到了几十万,突然之间,那些以前跑得好好的接口开始变得缓慢甚至崩溃。

最让我印象深刻的一次是某个促销活动上线之后,数据库直接被打崩了,整个平台几乎瘫痪。当时的场面很混乱,运维同事在疯狂重启服务,我们在代码层一遍又一遍地查问题,最终才意识到,这不是一个简单的性能优化问题,而是一个整体架构级别的挑战。

从那以后,我就开始系统性地学习高并发系统的构建和优化。这篇文章会基于我在多个项目中踩过的坑、趟过的水,来分享一些关于高并发系统设计的实战经验。


项目背景:电商交易系统改造升级

项目背景:电商交易系统改造升级

我们的主要产品是一个 B2B 电商平台,支持供应商入驻、商品管理、订单交易等功能。随着业务发展,原来的系统逐渐暴露出性能瓶颈:

  • 高峰期请求量激增,单日 PV 达到百万级别
  • 订单生成接口响应延迟严重,经常超过 500ms
  • 数据库压力巨大,出现锁等待、慢查询等问题
  • 库存服务频繁超时,影响下单成功率

这些症状背后其实暴露了一个老系统普遍存在的通病:架构过于集中化,缺少缓存体系,数据库没有分库分表,也没有限流熔断机制。

于是我们启动了一个为期三个月的系统重构计划,目标很明确:打造一个能支撑千万级 UV 的稳定交易系统


遇到的问题和挑战:高并发下的“多米诺骨牌”

遇到的问题和挑战:高并发下的“多米诺骨牌”

1. 接口并发瓶颈突显

最初的版本中,所有接口都部署在一个 Spring Boot 应用里,前端请求全部打到这一个服务节点上。结果在压测中发现:

ab -n 5000 -c 100 http://api.example.com/order/create

当并发数达到 100 时,TPS 只有不到 100,CPU 使用率飙升到了 95%,接口响应时间飙到 3s 左右。明显存在线程阻塞或者资源竞争。

2. 数据库连接池吃紧

订单创建过程中需要访问多个表,涉及库存扣减、订单写入、优惠计算等操作。每次请求都要获取数据库连接,导致连接池很快耗尽:

Caused by: java.sql.SQLNonTransientConnectionException: No connection available from pool.

3. 缓存未有效利用

虽然我们加了 Redis 缓存商品信息和库存数据,但因为使用方式不当(如串行批量读取),反而成了新的瓶颈。比如查询一个包含 20 个商品的购物车时,竟然是一个个去 Redis 查,完全没有发挥缓存的优势。


解决方案:分层架构 + 核心能力增强

针对上述问题,我们逐步采取了以下措施:

一、拆分服务 + 增加横向扩展能力

我们将核心服务解耦出来,形成独立微服务:

服务模块 功能职责
order-service 处理订单生命周期
inventory-srv 负责库存读写与预占
product-srv 商品管理与基本信息查询
user-srv 用户信息及积分、收货地址

每个服务都部署了多个实例,并配置 Nginx 做负载均衡:

upstream order_servers {
    least_conn;
    server 192.168.0.10:8080;
    server 192.168.0.11:8080;
}

这样不仅提高了系统可用性,也方便后续做灰度发布。

二、引入缓存策略:Redis + 本地缓存双层结构

我们在 Redis 中缓存热点数据,同时引入 Guava Cache 做本地缓存:

// 示例:本地缓存商品详情
LoadingCache<Long, ProductDetail> productCache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .build(this::loadProductFromDB);

对于库存这种变化频率较低、一致性要求高的数据,采用 Redis 持久化 + 预留机制:

-- Redis Lua 脚本处理库存扣减
local key = KEYS[1]
local num = tonumber(ARGV[1])
local stock = redis.call('GET', key)
if not stock then
   return -1
end
if tonumber(stock) < num then
   return 0
end
redis.call('DECRBY', key, num)
return 1

三、数据库拆分与分库分表

原来的所有业务都集中在一张订单表中,订单数突破千万后,查询非常慢。我们采用按用户 ID 进行水平分表:

  • 分片键:user_id % 4,分成四个物理表 order_0 ~ order_3
  • 查询逻辑封装在 MyBatis Interceptor 中自动路由

同时做了主从复制,把读写流量分离:

spring:
  datasource:
    dynamic:
      primary: master
      datasource:
        master:
          url: jdbc:mysql://mysql-master:3306/shop
          username: root
          password: xxx
        slave1:
          url: jdbc:mysql://mysql-slave1:3306/shop
          username: root
          password: xxx

四、引入限流与熔断机制

服务器部署方案-2

为了防止雪崩效应,我们在网关层接入了 Sentinel:

@Bean
public GlobalFilter sentinelGlobalFilter() {
    return (exchange, chain) -> {
        try {
            Entry entry = SphU.entry("order_create_api");
            return chain.filter(exchange).doOnTerminate(entry::exit);
        } catch (BlockException e) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            return response.setComplete();
        }
    };
}

缓存策略对比-1

Sentinel dashboard 设置 QPS=2000,在接口过载时自动降级。


代码实践:关键点展示

异步下单 + RocketMQ 解耦

为了减少同步调用的等待时间,我们将部分非实时操作异步化:

// 下单流程核心逻辑
OrderDTO createOrder(CreateOrderReq req) {
    OrderDTO order = prepareOrder(req);
    
    // 同步:库存扣减
    reduceInventory(req.getItems());
    
    // 异步:消息通知、积分增加、风控检查
    rocketMQTemplate.convertAndSend("ORDER_CREATED", order);
    
    return order;
}

消费者监听订单创建事件,进行后续处理:

@RocketMQMessageListener(topic = "ORDER_CREATED", consumerGroup = "order-consumer")
public class OrderCreatedConsumer implements RocketMQListener<OrderDTO> {
    @Override
    public void onMessage(OrderDTO order) {
        sendNotification(order.getUserId(), "您的订单已创建");
        addUserPoint(order.getUserId(), order.getPoint());
        riskControlService.check(order);
    }
}

使用线程池实现并行调用

例如:在结算页加载多个子服务时,并行发起请求:

@Autowired
private ProductClient productClient;

@Autowired
private CartClient cartClient;

public OrderCheckout checkout(long userId) {
    ExecutorService executor = Executors.newFixedThreadPool(3);
    
    Future<ProductInfo> productFuture = executor.submit(() -> productClient.getProduct(userId));
    Future<CartItems> cartFuture = executor.submit(() -> cartClient.getCart(userId));

    // 合并结果
    OrderCheckout checkout = new OrderCheckout();
    checkout.setProduct(productFuture.get());
    checkout.setCart(cartFuture.get());

    return checkout;
}

当然这里也可以使用 CompletableFuture 来更优雅地处理组合式异步调用。


踩坑经验:那些被教训打醒的日子

1. 忽视数据库索引优化的惨痛代价

有一次我们新增了一个订单状态变更的日志功能,表建好了但是没加索引。过了几天,一个 SQL 查询卡住整个数据库,导致全站瘫痪。最后才发现是因为有一个 WHERE order_id = ? 的查询没走索引!

所以后来我们立下规矩:

凡是涉及到 WHERE 或 JOIN 的字段,必须添加索引;每张新表上线前必须进行 Explain 审核

2. Redis 穿透、击穿、雪崩三兄弟

一开始我们对缓存异常情况没有足够的防护意识。某次大促期间,大量缓存失效,导致数据库瞬间承受几倍的压力。

后面我们采用了以下几个方案来应对:

  • 布隆过滤器防穿透
  • 互斥锁更新缓存防击穿
  • 设置随机过期时间防雪崩
// 防击穿示例
String getFromCache(String key) {
    String value = redis.get(key);
    if (value == null) {
        synchronized (this) {
            value = redis.get(key);
            if (value == null) {
                value = loadFromDB();
                redis.setex(key, randomExpireTime(), value);
            }
        }
    }
    return value;
}

3. 线程池配置错误引起的“伪高并发”

我们曾为异步任务配置了一个无界队列的线程池,结果在突发请求到来时,任务越堆越多,内存被撑满,GC 疯狂回收,服务器彻底挂掉。

后来改为有界队列 + 拒绝策略:

new ThreadPoolTaskExecutor()
    .setCorePoolSize(10)
    .setMaxPoolSize(20)
    .setQueueCapacity(500)
    .setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

效果总结:改造后的收益

经过这次大规模重构,系统有了明显提升:

指标 改造前 改造后 提升幅度
并发能力 500 TPS 7500 TPS 15X
订单接口平均响应时间 800ms 120ms ✅降低85%
DB QPS 1500 400 ✅下降73%
故障恢复时间 数小时 10分钟内 ✅大幅缩短
系统扩展性 单体难以扩容 微服务易扩缩容 ✅增强显著

最重要的是,现在遇到高流量冲击时,我们已经有一套完整的应急手段和监控报警体系,而不是像过去那样临时抱佛脚。


我的经验和建议

给新手的几个忠告:

  1. 不要一开始就追求极致性能
    很多人一上来就要分库分表、搞集群、上缓存,但其实很多业务初期完全没必要。先把架构做清晰,技术债后期更容易还。

  2. 关注链路追踪与监控体系建设
    真正的高并发系统不是靠肉眼判断性能瓶颈的,你必须得能快速定位哪个环节出问题。推荐使用 SkyWalking 或 Zipkin 做分布式追踪。

  3. 学会使用压测工具摸清系统边界
    Apache Bench、JMeter、Locust 都要掌握,只有你知道自己的系统上限在哪,才敢谈扛压能力。

  4. 别忽视线上日志和报警机制
    线上出问题时往往是你最慌的时候。提前配好 Prometheus + Grafana 监控指标,配合企业微信/钉钉报警,才能做到第一时间响应。

当前趋势中的参考方向:

  • 云原生架构:K8s + Service Mesh 成为标配,弹性伸缩更容易。
  • Serverless 架构:阿里云函数计算、AWS Lambda 在部分场景可节省资源成本。
  • 多活架构:异地多活正在成为大型企业的标准配置。
  • AI辅助诊断:部分团队已经在尝试用 AI 来分析日志、预测异常。

结语:技术是一场修行

回想这几年,从刚入行时只会埋头改 bug,到现在可以主导整个系统的设计与优化,最大的成长不是学会了更多工具,而是建立了一种系统性的思考方式

高并发从来不是一个单一的技术点,它考验着你对网络、操作系统、数据库、中间件、编程语言乃至组织协作的理解和把控。它就像一场马拉松,比拼的不是爆发力,而是耐力和韧性。

如果你正站在这个路口上,请相信一句话:

每一个高并发系统,都是由无数个深夜和一次次失败打磨出来的。

愿你在路上不惧风浪,勇往直前。


如果喜欢这篇内容,欢迎点赞、转发,也欢迎留言交流你的高并发实战经验~

评论 0

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