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

CI掉线了
2025-12-15 16:54
阅读 792

上周五晚上,我又一次在工位上敲代码到十一点。办公室只剩我和运维老张(他是因为生产环境又炸了才被迫留下的)。窗外下着小雨,咖啡机早就断电,我盯着屏幕上那堆“503 Service Unavailable”的日志,心里默默问候产品经理全家——这需求明明是三天前才提的,非得赶在下周大促上线,还说“只是加个按钮而已”。

我是谁?一个在某二线互联网公司摸爬滚打三年多的Java后端,日常主力工具是 VSCode + 一堆插件(比如 GitHub Copilot、Lombok、Rainbow Brackets),最近疯狂研究 AI 编程助手,因为……嗯,打算跳槽了。现在这家公司文化有点僵化,每次提架构优化都被说“能跑就行”,但大促一来就全员救火,搞得人身心俱疲。

所以,今天这篇文章,不灌鸡汤,不讲空话,就把我这几年踩过的坑、看过的书、试过的产品、写过的 Spring Boot 项目,以及为啥连区块链都扯进来了,一股脑儿倒出来。希望能帮到和我一样在高并发泥潭里挣扎的兄弟。


为什么突然要搞高并发?

起因其实很现实:我们公司的核心交易服务,在去年双11当晚 QPS 突破 8w,直接把 MySQL 主库干崩了。DBA 老王一边骂娘一边扩容,结果 Redis 缓存穿透又引发雪崩,整个下单链路瘫痪了快一个小时。

复盘会上,CTO 黑着脸说:“你们后端是不是没看过《高性能MySQL》?” 我心想:看过啊!但书上写的是一回事,线上跑的是另一回事。更惨的是,我们的 Spring Boot 应用还是单体架构,所有业务逻辑塞在一个 jar 包里,改一行代码就得全量发布——简直是 CI/CD 的噩梦。

于是,领导拍板:“重构!必须支持 10w+ QPS!”
我:“……行吧,反正简历也投出去了。”


从书本到实战:那些被现实打脸的“最佳实践”

1. 读缓存?别只想着 Redis!

刚接手这个任务时,我信心满满地翻开了《高并发系统设计》(阿里出品,确实干货)。第一章就讲缓存策略。我心想:简单,上 Redis + 本地缓存(Caffeine)双层缓存,热点数据扛住!

结果第一版上线后,测试同学反馈:某些商品详情页偶尔返回旧数据。查了一圈才发现,本地缓存和 Redis 没做一致性同步。更离谱的是,有次运维误删了 Redis key,而本地缓存还没过期,导致脏数据持续了整整 5 分钟。

教训:缓存不是万能的,一致性比性能更重要。后来我们做了:

  • 写操作时,先更新 DB,再删除缓存(Cache-Aside)
  • 本地缓存 TTL 设置为 Redis 的 70%,避免长期不一致
  • 加了熔断机制:如果 Redis 连续失败 3 次,自动降级到直连 DB(虽然慢点,但至少数据准)
// Spring Boot 中使用 Caffeine + Redis 双缓存示例
@Service
public class ProductCacheService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private final Cache<String, Product> localCache = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(30, TimeUnit.SECONDS) // 注意:比Redis短
        .build();

    public Product getProduct(Long id) {
        String key = "product:" + id;
        
        // 先查本地
        Product product = localCache.getIfPresent(key);
        if (product != null) return product;

        // 再查 Redis
        product = (Product) redisTemplate.opsForValue().get(key);
        if (product != null) {
            localCache.put(key, product);
            return product;
        }

        // 最后查 DB
        product = productMapper.selectById(id);
        if (product != null) {
            redisTemplate.opsForValue().set(key, product, 5, TimeUnit.MINUTES);
            localCache.put(key, product);
        }
        return product;
    }
}

2. 数据库分库分表?别被 MyBatis Plus 宠坏了

我们之前用 MyBatis Plus,各种 lambdaQuery() 看似优雅,实则暗藏杀机。当订单表数据达到 5000w 行时,一个简单的 count(*) 都要跑十几秒。

看了《数据库系统概念》和《MySQL技术内幕》,决定上 ShardingSphere。但问题来了:原来的业务代码全是耦合的,连分页查询都用的 PageHelper,根本不支持分片。

折腾两周后,终于把订单、用户、商品三大核心表做了水平分片(按 user_id 哈希)。但测试发现:跨分片 join 查询直接爆炸。最后只能妥协——放弃复杂关联查询,改用宽表 + 异步同步(用 Canal 监听 binlog)。

方案 QPS 响应时间 开发成本 运维复杂度
单库单表 ~2k 50ms
读写分离 ~5k 30ms
分库分表 ~20k 15ms

血泪建议:分库分表不是银弹!如果 QPS 不到 1w,优先考虑读写分离 + 缓存 + SQL 优化。别为了“高大上”硬上分片,后期维护成本会让你哭着求 DBA 收留你。


Spring Boot 真的适合高并发吗?

很多人喷 Spring Boot “太重”,不适合高并发。但我想说:工具没问题,问题是人怎么用。

我们项目最初用的 Tomcat,默认线程池 200,结果压测时线程全堵在 I/O 上。后来换成 Undertow,配合异步 Servlet + CompletableFuture,吞吐量直接翻倍。

# application.yml
server:
  undertow:
    io-threads: 16
    worker-threads: 256
    buffer-size: 1024
    direct-buffers: true

另外,别滥用 @Transactional!有一次一个接口里套了三层事务,每层都查 DB,结果锁等待超时,整个服务卡死。后来我们定下规矩:

  • 事务方法必须标注 timeout
  • 禁止在循环里调用事务方法
  • 读多写少的场景,用 @Transactional(readOnly = true)

还有,日志别乱打log.info("user {} bought product {}", userId, productId) 看似无害,但在 10w QPS 下,光字符串拼接就能吃掉 20% CPU。后来全改成占位符 + 异步日志(Logback + AsyncAppender),CPU 使用率立马降了 15%。


区块链?你没看错,它居然帮了忙

说到这儿你可能懵了:高并发和区块链有毛关系?

事情是这样的:我们有个“积分兑换”模块,用户频繁操作,但又要保证幂等性和审计追踪。传统方案是加唯一索引 + 重试机制,但分布式环境下还是会出现重复扣减。

某天我刷 Hacker News,看到一篇讲“用区块链思想做状态机”的文章。灵光一闪:能不能把每次积分变动当作一个“区块”,记录前一个状态 hash,形成不可篡改的链?

于是我们用轻量级方式实现了一个 伪区块链

  • 每次积分变更生成 transactionId + previousHash
  • 存入单独的 audit_log 表
  • 校验时递归验证 hash 链

虽然没用真正的区块链(比如 Ethereum),但这种链式校验思想极大提升了数据可靠性。而且因为只存 hash,存储开销几乎可以忽略。

CREATE TABLE point_transaction (
  id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  amount INT NOT NULL,
  previous_hash CHAR(64), -- SHA256 of last record
  current_hash CHAR(64),  -- SHA256(this record + previous_hash)
  created_at TIMESTAMP
);

产品经理看到后惊呼:“我们是不是要发币了?”
我:“……你再提发币,我就把你电脑格式化。”


产品思维:高并发不是技术自嗨

最后想说点题外话。很多工程师(包括我以前)总以为高并发就是堆技术:上 Kafka、换 Redis Cluster、搞 Service Mesh……

但真正决定系统成败的,往往是产品设计

举个例子:我们有个“秒杀”功能,最初设计是用户点击“抢购”就直接扣库存。结果大促时,几万人同时点,DB 直接锁死。

后来产品改了规则:

  • 提前 10 分钟开放“预约”
  • 秒杀开始时,只允许预约用户进入
  • 实际下单走异步队列

QPS 瞬间从 8w 降到 5k,系统稳如老狗。运维老张那天居然请我喝了杯瑞幸。

所以,下次接到“高并发”需求,先问清楚:

  • 用户行为是什么?(是真并发还是可削峰?)
  • 数据一致性要求多高?(最终一致可接受吗?)
  • 有没有业务手段分流?(比如限流、排队、预加载)

技术永远服务于业务,别让“炫技”毁了你的系统。


总结:我的三条保命经验

折腾半年,系统终于能扛住 12w QPS(压测数据),线上零重大事故。总结下来,就三点:

  1. 缓存要谨慎,一致性优先
    别盲目加缓存,先想清楚失效策略、穿透防护、雪崩预案。我们现在的缓存命中率 98%,但花了两个月调优。

  2. 异步化 + 削峰填谷是王道
    所有非核心链路(发短信、记日志、更新推荐)全部扔进 Kafka,主流程只干最关键的事。Spring Boot + Spring Kafka 配置起来比想象中简单。

  3. 监控和告警比代码更重要
    上了 Prometheus + Grafana + ELK 后,终于能在故障发生前收到预警。有次凌晨 3 点收到“Redis 连接池耗尽”告警,远程登录扩容,避免了一次 P0 事故。

至于要不要跳槽?说实话,这次重构让我技术成长飞快,简历上也能写“主导高并发系统设计”。但公司给的奖金……emmm,只够买两台 Switch。

不过无所谓了。下周面试一家做 Web3 基础设施的 startup,他们说要用 Spring Boot 构建去中心化交易所后端——又是高并发 + 区块链,看来我这套经验还能接着用。

对了,如果你也在啃《高并发系统设计》这本书,记得配合实践。书是骨架,血肉得自己长。就像我桌上那杯凉透的咖啡,理论再香,也得自己熬。

共勉。

评论 0

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