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

半栈青年
2025-12-18 13:58
阅读 1994

大家好,我是老K,一个在深圳混了4年多的后端工程师。目前在一家出行平台(你懂的,滴滴风格那家)做司机端核心业务开发,天天和订单状态机、定位上报、抢单逻辑打交道。最近跳槽季,一边刷LeetCode准备面试,一边被新项目压得喘不过气——上周五晚上10点还在改一个“高并发场景下司机位置更新丢失”的线上Bug,差点把键盘砸了。

写这篇文章,其实有点“痛定思痛”的意思。前两天面了个腾讯系的兄弟,聊到高并发系统设计,他问:“你们司机端QPS峰值多少?怎么扛住双11那种流量?” 我支支吾吾说了个数,心里却清楚——很多设计都是“事后补救”,而不是“事前规划”。再加上最近在啃AI课程(别问,问就是怕被淘汰),突然意识到:技术债这东西,早晚要还的

所以今天不装X,不堆术语,就结合我这几年踩过的坑,聊聊高并发系统从理论到落地的真实体验。顺便也给正在刷面试题、改简历的兄弟们一点参考——毕竟,光背“CAP理论”可过不了大厂二面。


问题来了:司机疯狂“抖动”,系统差点崩了

去年双11那天,我们搞了个“高峰时段补贴翻倍”活动。结果司机们像打了鸡血一样,疯狂打开App、刷新位置、抢高价单。监控面板直接爆红:司机位置上报接口QPS从平时的5k飙到12w+

最要命的是,数据库CPU直接干到98%,大量请求超时。更离谱的是,有些司机明明在线,后台却显示“离线”——因为位置更新写库失败,状态同步链路断了。PM在群里@我:“老K,司机都投诉收不到单了,快看看!”

我当时第一反应是扩容DB,但运维小哥冷冷回了一句:“主库已经32核64G了,再扩成本超标,老板不让。”

行吧,只能自己动手丰衣足食。


技术选型:Go vs Java,不是信仰之争,是业务之需

我们团队之前主力语言是Java(Spring Boot那一套),但这次我力推用Go重写位置上报服务。原因很简单:

  • 高并发场景下,Go的goroutine比Java线程轻量得多
  • 内存占用低,适合处理海量短连接
  • 编译成二进制,部署简单,运维不骂你

当然,也有同事反对:“Go生态不如Java成熟”、“没有JVM优化那么稳”。但现实是——当你的接口每秒要处理上万次写请求时,优雅不如能跑

最终我们用Go重构了位置服务,配合Redis+Kafka做缓冲,效果立竿见影。下面是我整理的一个对比表,方便大家在简历里吹(划掉)写技术选型依据时参考:

维度 Go方案 Java (Spring Boot)
启动速度 < 100ms ~2s
内存占用(1w QPS) ~300MB ~1.5GB
并发模型 Goroutine(轻量级协程) Thread(操作系统线程)
GC压力 极低(三色标记+并发回收) 中等(G1/ZGC依赖调优)
学习曲线 简单,语法精简 较陡,生态复杂
团队熟悉度 初期低,后期快速上手

说实话,用Go之后,我再也不用半夜被“Full GC”告警叫醒了。而且Go的context + select机制,处理超时和取消特别清爽,写起来有种“代码如流水”的感觉。


架构设计:别一上来就分布式,先做好“削峰填谷”

很多人一提高并发,张口就是“上Kafka、上Redis、上分库分表”。但真实情况是——80%的性能问题,靠缓存和异步就能解决

我们的位置上报链路改造后长这样:

司机App → Nginx → Go Service → Kafka → 消费者 → Redis(实时位置) + MySQL(持久化)

关键点:

  1. 写请求不直接打DB:所有位置更新先入Kafka,削峰。
  2. 读写分离:司机位置查询走Redis(TTL 30s),只有状态变更才落库。
  3. 批量消费:消费者每100ms批量写MySQL,减少IO。

这里有个坑:一开始我们用Redis的HSET存司机位置,结果内存爆炸。后来改成GEOADD,既省空间又能支持“附近司机”查询,一举两得。

另外,接口设计也做了优化。以前是每次上报都带完整JSON,现在改成Protobuf二进制协议,网络传输体积减少60%。测试同学都说:“老K,你们这次压测没把网卡打满,难得!”


数据库:别让MySQL裸奔

即使用了Kafka缓冲,最终还是要写库。而MySQL在高并发写场景下,简直就是“纸糊的”。

我们做了几件事:

  • 分表:按司机ID哈希分成64张表,避免单表过大
  • 字段精简:去掉冗余字段,用INT代替VARCHAR存状态
  • 关闭binlog强一致性:设置innodb_flush_log_at_trx_commit=2,牺牲一点持久性换吞吐
  • 只写必要数据:历史轨迹用冷热分离,热数据留7天,其余归档到TiDB

有次上线忘了开慢查询日志,结果一个没走索引的UPDATE语句把主库锁了3分钟。DBA直接冲到工位:“你是不是又手写SQL了?!” —— 自此之后,我们团队强制要求所有SQL必须走ORM或预定义模板。


运维视角:监控和熔断,比代码更重要

再好的架构,没监控等于裸奔。我们接入了Prometheus + Grafana,重点监控:

  • 接口P99延迟
  • Kafka堆积量
  • Redis命中率
  • DB连接池使用率

同时,在Go服务里集成了熔断机制(用的是sony/gobreaker)。当MySQL响应超过500ms,自动拒绝新请求,返回缓存数据或默认值。虽然用户体验稍差,但保住了系统不雪崩。

记得有一次,第三方地图API挂了,导致位置解析超时。因为有熔断,系统自动降级为“使用上次有效位置”,司机还能继续接单。PM居然夸我:“这次事故影响不大,做得不错。” —— 哈,这大概是我今年听到最动听的话了。


面试题 & 简历怎么写?

说到这个,最近帮几个朋友改简历,发现很多人写“参与高并发系统设计”,结果面试一问细节就露馅。

建议这么写:

  • 主导司机位置服务重构,采用Go + Kafka + Redis架构,支撑QPS 12w+,P99延迟<50ms
  • 设计基于GEO的位置存储方案,内存占用降低40%,支持“附近司机”实时查询
  • 引入熔断降级机制,系统可用性从99.2%提升至99.95%

记住:数字 + 技术栈 + 业务价值,三者缺一不可。

至于面试题,高频的几个:

  • “如何保证高并发下数据一致性?” → 答:最终一致 + 补偿机制,别硬刚强一致
  • “Redis和MySQL如何保持同步?” → 答:先更新DB,再删缓存(Cache-Aside),配合重试队列
  • “Go怎么处理大量并发连接?” → 答:net/http原生支持,配合pprof做性能分析

别背标准答案,结合你自己的项目讲,面试官眼睛会亮。


最后:高并发不是炫技,是克制

折腾完这一轮,我最大的感悟是:高并发系统不是堆技术,而是做减法

能异步就别同步,能缓存就别查库,能降级就别死扛。有时候,一个简单的“拒绝服务”比复杂的重试机制更有效。

现在我每天上班第一件事,就是看监控大盘。看到曲线平稳,心里就踏实。毕竟,在深圳这个卷成麻花的城市,系统稳了,KPI才能稳,年终奖才能稳(笑)。

如果你也在搞高并发,欢迎留言交流。说不定哪天,我们在腾讯大厦楼下咖啡店偶遇,一起吐槽PM和运维呢?

—— 老K,一个不想再半夜修Bug的Go程序员

评论 0

最热最新
暂无评论
半栈青年Lv.1
0
影响力
0
文章
0
粉丝