技术探索与实践:从司机端削峰填谷说起
大家好,我是老张,在滴滴干了快四年后端开发,主要负责司机端的核心业务模块。平时写代码喜欢戴着耳机听点 Lo-fi 或周杰伦,不然办公室里产品经理催进度的声音真的太吵了(开玩笑的,PM 其实人挺好的,就是需求总在周五下午五点发过来……)。
最近在准备跳槽,一边被线上 bug 追着跑,一边 LeetCode 刷到眼花。但说实话,这几年真正让我成长的,不是那些刷过的题,而是实际项目里被逼出来的“土法炼钢”——尤其是去年双11期间那次司机接单接口的性能雪崩,直接把我从 CRUD Boy 打醒。
今天想聊聊那次事故后的技术探索与实践,顺便分享点开发心得,以及几本救我命的书籍。
一场“温柔”的雪崩
事情发生在去年 10 月底。公司搞了个“高峰时段冲单奖励”,结果司机们一窝蜂上线抢单。我们的接单服务瞬间 QPS 从 2k 暴涨到 8k+,数据库连接池直接打满,CPU 飙到 95%,报警电话响得比滴滴司机接单提示音还密集。
当时我正在改一个看起来人畜无害的“订单状态同步”逻辑,突然钉钉群炸了:“接单超时率飙升!用户投诉爆了!”
我点开监控一看,好家伙,MySQL 的 Threads_connected 直接拉满,线程都在等锁。
那一刻我真的想砸电脑——不是因为 bug,是因为自己居然没提前压测!
运维同事幽幽来了一句:“你们后端是不是又没做限流?”
我:……(默默咽下这口老血)
亡羊补牢:从限流到异步化
复盘会上,领导说:“别光背锅,想想怎么防住下次。”
于是我们启动了“削峰填谷”专项。目标很明确:扛住突发流量,保证核心链路不崩。
一开始想上 Redis + Lua 做令牌桶限流,但测试发现高并发下 Lua 脚本本身成了瓶颈。后来翻《高性能可扩展系统设计》(这本书真是神作,后面细说),里面提到“异步解耦 + 队列缓冲”才是应对突发流量的正道。
于是我们做了三件事:
- 入口层加滑动窗口限流(用 Sentinel 实现)
- 非核心操作异步化(比如记录日志、发通知)
- 接单主流程剥离写 DB 操作,改成先入 Kafka 再消费
关键改动在第三点。以前是这样:
public void acceptOrder(Order order) {
// 1. 校验司机状态
validateDriver(order.getDriverId());
// 2. 更新订单状态
orderDao.updateStatus(order.getId(), "ACCEPTED");
// 3. 推送消息给乘客
pushService.notifyPassenger(order);
// 4. 记录审计日志
auditLog.record(order, "ACCEPT");
}
看似没问题,但每一步都可能慢(DB 慢、推送慢、日志写磁盘慢)。高峰期一卡,全链路阻塞。
我们重构后:
public void acceptOrder(Order order) {
validateDriver(order.getDriverId());
// 只保留最核心的校验和响应
response.success();
// 其他操作扔进 MQ 异步处理
kafkaProducer.send("order-events", new OrderEvent(order, EventType.ACCEPT));
}
消费者那边再慢慢消费,失败就重试。即使 MQ 积压,也不影响司机“秒接单”的体验。
踩坑实录:Kafka 不是万能胶
理想很丰满,现实啪啪打脸。
上线第一天,测试同学就反馈:“司机点了接单,但乘客没收到通知!”
查日志发现,Kafka 消费者挂了——因为我们在消费者里又调了另一个内部服务,那个服务那天刚好发布,接口变了,返回 500,我们没做异常捕获,整个消费线程直接退出。
教训一:异步不是甩锅,失败必须有兜底。
我们立刻加上了:
- 消费失败自动重试(指数退避)
- 死信队列(DLQ)人工介入
- 关键事件落库做对账
另外,Kafka 分区数一开始设得太小(只有 3 个),导致消费速度跟不上。后来根据预估峰值流量,按 每分区 5k msg/s 的经验公式重新计算,扩到了 16 分区。
| 配置项 | 初始值 | 优化后 |
|---|---|---|
| 分区数 | 3 | 16 |
| 消费线程数 | 1 | 8 |
| 批量消费大小 | 10 | 100 |
| 重试次数 | 0 | 3(+ DLQ) |
上线后,接单 P99 延迟从 1.2s 降到 200ms,数据库负载下降 60%。双11当天零故障,团队请我吃了顿火锅(其实是团建,但我强行算作奖励)。
开发心得:代码是给人看的,不是给机器跑的
这次重构让我深刻体会到:可维护性 > 炫技。
早期我写过一段“聪明”的代码,用 Java 8 Stream 链式调用处理司机状态机,结果新来的实习生看了半小时都没敢动。后来 Leader 说:“你这是在写诗,不是写工程。”
现在我写代码会多问自己三个问题:
- 如果半夜报警,别人能看懂这段逻辑吗?
- 如果要加一个新状态,改动范围大吗?
- 单元测试覆盖了吗?
另外,日志打得好,排查快十倍。我们现在强制要求:
- 关键路径打 traceId
- 异常必须带上下文(比如
driverId=12345, orderId=abc) - 禁止打
e.printStackTrace()
救命书籍推荐
最后安利两本对我帮助巨大的书:
《Designing Data-Intensive Applications》(DDIA)
中文名《数据密集型应用系统设计》。这本书简直是我跳槽面试的圣经。里面关于“幂等性”、“恰好一次语义”、“日志即数据库”的章节,直接帮我理清了异步系统的本质。每次读都有新收获。《Clean Code》
虽然有点老,但“函数要短”、“变量命名要自解释”这些原则,在我 review 代码时天天用。特别是那句:“注释不能美化烂代码”,现在看到有人写// TODO: fix this later就手痒想删掉。
写在最后
从那次事故到现在,半年过去了。现在的系统虽然不完美,但至少扛住了几次大促。我也从“被动救火”转向“主动防御”——比如最近在推全链路压测,用 Chaos Engineering 主动搞垮服务看恢复能力(运维已经不想理我了)。
技术探索从来不是一蹴而就。它是在 deadline 前的焦头烂额中,在凌晨三点的 log 分析里,在一次次“我以为没问题”的翻车现场中,慢慢长出来的肌肉记忆。
如果你也在经历类似的挣扎,别慌。多读点书,多写点注释,多问一句“如果这里挂了怎么办”。
毕竟,我们写的不是代码,是未来某个深夜值班同事的救命稻草。
(对了,简历已更新,有 HC 的老板可以私聊 😏)
P.S. 最近在刷《剑指 Offer》和 LeetCode Hot 100,如果有同在备战跳槽的朋友,欢迎交流~

评论 0