技术探索与实践:从司机端削峰填谷说起

CSS摆烂王
2025-12-17 04:26
阅读 1437

大家好,我是老张,在滴滴干了快四年后端开发,主要负责司机端的核心业务模块。平时写代码喜欢戴着耳机听点 Lo-fi 或周杰伦,不然办公室里产品经理催进度的声音真的太吵了(开玩笑的,PM 其实人挺好的,就是需求总在周五下午五点发过来……)。

最近在准备跳槽,一边被线上 bug 追着跑,一边 LeetCode 刷到眼花。但说实话,这几年真正让我成长的,不是那些刷过的题,而是实际项目里被逼出来的“土法炼钢”——尤其是去年双11期间那次司机接单接口的性能雪崩,直接把我从 CRUD Boy 打醒。

今天想聊聊那次事故后的技术探索与实践,顺便分享点开发心得,以及几本救我命的书籍。


一场“温柔”的雪崩

事情发生在去年 10 月底。公司搞了个“高峰时段冲单奖励”,结果司机们一窝蜂上线抢单。我们的接单服务瞬间 QPS 从 2k 暴涨到 8k+,数据库连接池直接打满,CPU 飙到 95%,报警电话响得比滴滴司机接单提示音还密集。

当时我正在改一个看起来人畜无害的“订单状态同步”逻辑,突然钉钉群炸了:“接单超时率飙升!用户投诉爆了!”
我点开监控一看,好家伙,MySQL 的 Threads_connected 直接拉满,线程都在等锁。
那一刻我真的想砸电脑——不是因为 bug,是因为自己居然没提前压测!

运维同事幽幽来了一句:“你们后端是不是又没做限流?”
我:……(默默咽下这口老血)


亡羊补牢:从限流到异步化

复盘会上,领导说:“别光背锅,想想怎么防住下次。”
于是我们启动了“削峰填谷”专项。目标很明确:扛住突发流量,保证核心链路不崩

一开始想上 Redis + Lua 做令牌桶限流,但测试发现高并发下 Lua 脚本本身成了瓶颈。后来翻《高性能可扩展系统设计》(这本书真是神作,后面细说),里面提到“异步解耦 + 队列缓冲”才是应对突发流量的正道。

于是我们做了三件事:

  1. 入口层加滑动窗口限流(用 Sentinel 实现)
  2. 非核心操作异步化(比如记录日志、发通知)
  3. 接单主流程剥离写 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 说:“你这是在写诗,不是写工程。”

现在我写代码会多问自己三个问题:

  1. 如果半夜报警,别人能看懂这段逻辑吗?
  2. 如果要加一个新状态,改动范围大吗?
  3. 单元测试覆盖了吗?

另外,日志打得好,排查快十倍。我们现在强制要求:

  • 关键路径打 traceId
  • 异常必须带上下文(比如 driverId=12345, orderId=abc
  • 禁止打 e.printStackTrace()

救命书籍推荐

最后安利两本对我帮助巨大的书:

  1. 《Designing Data-Intensive Applications》(DDIA)
    中文名《数据密集型应用系统设计》。这本书简直是我跳槽面试的圣经。里面关于“幂等性”、“恰好一次语义”、“日志即数据库”的章节,直接帮我理清了异步系统的本质。每次读都有新收获。

  2. 《Clean Code》
    虽然有点老,但“函数要短”、“变量命名要自解释”这些原则,在我 review 代码时天天用。特别是那句:“注释不能美化烂代码”,现在看到有人写 // TODO: fix this later 就手痒想删掉。


写在最后

从那次事故到现在,半年过去了。现在的系统虽然不完美,但至少扛住了几次大促。我也从“被动救火”转向“主动防御”——比如最近在推全链路压测,用 Chaos Engineering 主动搞垮服务看恢复能力(运维已经不想理我了)。

技术探索从来不是一蹴而就。它是在 deadline 前的焦头烂额中,在凌晨三点的 log 分析里,在一次次“我以为没问题”的翻车现场中,慢慢长出来的肌肉记忆。

如果你也在经历类似的挣扎,别慌。多读点书,多写点注释,多问一句“如果这里挂了怎么办”。
毕竟,我们写的不是代码,是未来某个深夜值班同事的救命稻草

(对了,简历已更新,有 HC 的老板可以私聊 😏)


P.S. 最近在刷《剑指 Offer》和 LeetCode Hot 100,如果有同在备战跳槽的朋友,欢迎交流~

评论 0

最热最新
暂无评论
CSS摆烂王Lv.1
0
影响力
0
文章
0
粉丝