从外卖订单激增到Rust初探:一个美团Java工程师的实战复盘
去年双11那天晚上,我差点把键盘砸了。
不是因为代码写崩了——虽然那会儿确实离崩溃不远——而是产品经理在凌晨两点突然冲进钉钉群:“运营那边说,用户下单时的优惠券加载慢了300毫秒,转化率掉了0.5%,老板急了,能不能今晚搞定?”
我当时盯着屏幕上的GC日志和线程堆栈,心里一万只草泥马奔腾。这哪是优化性能,这是要命啊。
但没办法,干的就是这行。我在美团做外卖后端开发已经四年,经历过无数个大促、秒杀、节假日高峰,早就练就了一身“在火焰中写代码”的本领。今天这篇总结,既是对过去几年高并发实战经验的一次梳理,也掺杂了最近为跳槽刷题、顺带折腾R rust 的一点新思考。希望对还在一线“搬砖”的兄弟们有点帮助。
高并发不是玄学,是血泪堆出来的运营指标
很多人以为高并发就是堆机器、上缓存、搞异步,但实际在业务场景里,每一毫秒的延迟都可能直接对应真金白银的损失。比如我们外卖系统里的“优惠券可用性校验”服务,在高峰期每秒要处理8万+请求。这个服务看似简单,无非是查一下用户是否满足某张券的使用条件,但一旦变慢,整个下单流程就会卡住。
而“运营”这个词,在我们这儿从来不是虚的。运营团队会盯着各种漏斗数据:从首页曝光 → 商品点击 → 加购 → 下单 → 支付成功。任何一个环节掉链子,第二天晨会上你就要站出来解释。所以技术方案必须对齐业务目标,不能闭门造车。
举个例子:早些年我们用纯MySQL查券状态,结果大促一来,DB直接被打爆,连接池耗尽。后来上了Redis缓存,但又遇到缓存击穿问题——某个热门商户的券被大量用户同时查询,缓存失效瞬间所有请求打到DB。我们尝试过布隆过滤器、互斥锁、本地缓存,最后落地的方案是多级缓存 + 异步预热 + 熔断降级三件套。
// 伪代码示意:多级缓存结构
public CouponInfo getCoupon(String userId, String couponId) {
// L1: 本地缓存(Caffeine)
CouponInfo local = localCache.getIfPresent(couponId);
if (local != null) return local;
// L2: Redis 缓存
String redisKey = "coupon:" + couponId;
String json = redis.get(redisKey);
if (json != null) {
CouponInfo remote = JSON.parse(json);
localCache.put(couponId, remote); // 回填本地缓存
return remote;
}
// L3: DB 查询(带熔断)
if (circuitBreaker.isClosed()) {
try {
CouponInfo db = couponDao.selectById(couponId);
if (db != null) {
redis.setex(redisKey, 300, JSON.toJson(db)); // 缓存5分钟
localCache.put(couponId, db);
return db;
}
} catch (Exception e) {
circuitBreaker.recordFailure();
// 降级:返回空或兜底券
return fallbackCoupon();
}
}
return fallbackCoupon();
}
这套方案上线后,P99延迟从420ms降到68ms,运营同学终于不再半夜@我了。
但代价是什么?代码复杂度飙升,测试用例翻倍,运维监控要覆盖三层缓存命中率、熔断状态、本地缓存驱逐率……高并发系统从来不是技术炫技,而是在性能、稳定性、可维护性之间反复横跳。
踩过的坑:你以为的“最佳实践”,可能是别人的事故现场
有一次我们为了提升吞吐量,把Tomcat线程池从200调到1000。结果CPU飙到95%,响应时间反而翻倍。排查半天才发现,应用里有个隐藏的同步块:
synchronized (someSharedObject) {
// 调用第三方风控接口(平均耗时200ms)
}
线程越多,阻塞越严重。这事儿告诉我们:不要盲目相信“调大线程池能提升并发”这种教条。真正的瓶颈可能藏在一个你根本想不到的地方。
还有一次更惨。我们在压测环境一切正常,结果上线后订单创建成功率暴跌。最后发现是JVM参数没对齐:压测用的是G1 GC,生产环境还是默认的Parallel GC。G1在大堆内存下表现更好,但我们生产堆只有4G,反而触发频繁Full GC。
这些教训让我养成了几个习惯:
- 所有配置变更必须走灰度发布
- 关键路径必须有全链路追踪(我们用的是CAT)
- 每次大促前跑一遍“故障演练”:模拟DB慢、Redis挂、网络抖动
顺便吐槽一句:测试同学总说“功能测完了”,但高并发场景下的异常流,他们根本测不到。最后还得靠我们自己写混沌工程脚本。
技术选型:别被新框架忽悠瘸了
最近团队里有人提议把核心服务迁到Go或者Rust。理由很诱人:“内存安全”、“零成本抽象”、“超高性能”。
作为一个正在刷LeetCode准备跳槽、顺便研究Rust的Java老狗,我其实挺心动的。Rust的所有权模型确实能从根本上避免很多并发Bug,比如我们曾经因为HashMap在多线程下put导致的死循环(别问,问就是ConcurrentModificationException)。
但现实很骨感。
我们系统有上千个微服务,大部分是Spring Boot + MyBatis + Dubbo。如果重写,光是人力成本就够喝一壶。而且Rust生态在Web后端还不够成熟:没有像Spring那样成熟的DI框架,ORM工具也不如MyBatis灵活,连JSON序列化都要手动derive。
所以我的建议是:核心链路稳字当头,边缘服务可以试水新技术。
比如我们最近用Rust写了一个轻量级的“地址解析服务”——接收用户输入的模糊地址,调用高德API标准化。这个服务QPS不高,但对内存和启动速度敏感(需要部署在边缘节点)。用Rust写完,镜像体积只有Java版的1/10,冷启动快了5倍。
// 简化的Rust地址解析逻辑
#[derive(Deserialize)]
struct AddressRequest {
input: String,
}
#[derive(Serialize)]
struct AddressResponse {
standardized: String,
lat: f64,
lng: f64,
}
async fn parse_address(req: Json<AddressRequest>) -> Result<Json<AddressResponse>, Error> {
let client = reqwest::Client::new();
let resp = client
.get("https://restapi.amap.com/v3/geocode/geo")
.query(&[("address", &req.input), ("key", API_KEY)])
.send()
.await?
.json::<GeoResponse>()
.await?;
Ok(Json(AddressResponse {
standardized: resp.geocodes[0].formatted_address.clone(),
lat: resp.geocodes[0].location.parse_lat()?,
lng: resp.geocodes[0].location.parse_lng()?,
}))
}
虽然写起来比Java啰嗦(尤其错误处理),但运行时确实稳如老狗。不过要说替代主链路?再等三年吧。
实战经验总结:给后来者的几条忠告
经过这些年在美团外卖的摸爬滚打,加上最近准备跳槽时重新审视自己的技术栈,我总结了几点心得:
| 维度 | 错误做法 | 正确姿势 |
|---|---|---|
| 性能优化 | 盲目加缓存、调线程数 | 先压测定位瓶颈,再针对性优化 |
| 容灾设计 | 只考虑正常流程 | 必须设计降级、熔断、兜底策略 |
| 技术选型 | 追新不追稳 | 新技术先在非核心场景验证 |
| 监控告警 | 只监控CPU/内存 | 关注业务指标(如订单创建成功率) |
| 协作沟通 | 只和技术团队聊 | 主动和运营、产品对齐目标 |
另外,别忽视“软技能”。我见过太多技术牛人因为不会表达,方案被否;也见过普通开发者靠清晰的文档和演示赢得信任。现在每次提技术方案,我都会附上一张“影响面分析图”:哪些服务受影响、运营指标如何变化、回滚方案是什么。
写在最后:技术人的长期主义
说实话,写这篇文章的时候,我已经投了几家公司的简历。但无论去哪,我相信这些实战经验都不会白费。
高并发系统没有银弹,只有不断试错、复盘、沉淀。每一次线上事故,都是免费的实战课;每一个深夜加班,都在为未来的技术判断力充值。
最近在Rust社区看到一句话特别戳我:“Ownership is not just a language feature, it’s a mindset.”(所有权不仅是语言特性,更是一种思维方式。)
其实做技术也一样。无论是Java还是Rust,无论是优化300毫秒还是重构整个架构,真正重要的是对系统的ownership——你要为它负责到底。
共勉。
P.S. 如果你在准备跳槽,推荐刷《Java并发编程实战》+ LeetCode Hot 100。别信那些“三个月速成架构师”的鬼话,基本功才是硬通货。
P.P.S. 产品经理又在群里@我了,说春节档要推新玩法……溜了溜了,改代码去。

评论 0