聊聊技术探索与实践:一个北京小厂后端的深夜自白
今天凌晨两点,我刚合上 MacBook,窗外五环外的夜色静得能听见自己心跳。咖啡杯底残留的冷萃已经结了一层膜,脑子里还回荡着 Rust 编译器那句亲切又扎心的 “error[E0382]: use of moved value”。
但奇怪的是,我不觉得累——甚至有点兴奋。因为今天终于把那个拖了两周的异步任务队列用 Rust 重写完了,吞吐量直接翻了三倍。
我是谁?简单说:北京一家不到百人的小厂后端,独立负责一条业务线(说得高大上,其实就是没人帮我背锅)。每天通勤一小时,在国贸和回龙观之间反复横跳,白天应付产品经理“能不能加个按钮但别改逻辑”的需求,晚上回家才能摸点真正有意思的东西。最近迷上了 Rust,虽然学得磕磕绊绊,但越啃越上头。
今天这篇文章,不讲八股文,也不画架构图吹牛逼。就想和大家聊聊:在资源有限、时间紧张的小厂环境下,一个普通后端怎么在“活着”和“成长”之间找平衡,顺便搞点技术探索与实践。
起因:前端甩过来一个“合理”需求
事情得从上周五说起。那天 PM 在群里@我:“后端大佬,我们想做个实时数据看板,前端需要每秒推 5k 条记录,延迟不能超过 200ms,能支持吗?”
我盯着消息看了十秒,默默打开 Jira —— 果然,Deadline 是三天后上线,理由是“配合运营双十二预热”。
我们现在的技术栈?Java Spring Boot + MySQL + Redis,典型的 CRUD 小作坊配置。之前处理实时数据靠 WebSocket + 长轮询,QPS 顶天 800。5k?还 200ms?我当时差点把键盘扔出窗户。
更绝的是,前端同事补了一句:“我们用 Svelte 写的,轻量,应该压力不大吧?”
兄弟,你轻量,但我后端不是永动机啊!
但抱怨归抱怨,活还得干。我拉了个会,和前端、测试、运维一起对齐。结论很现实:现有架构撑不住,要么砍需求,要么换方案。PM 摇头:“用户要的就是‘丝滑’。” 得,没得选。
探索:为什么是 Rust?
其实第一个想到的是 Go。毕竟 Goroutine 天然适合高并发,生态也成熟。但转念一想:我们团队就我一个后端,Go 虽然简单,但 GC 停顿在极端场景下还是不可控。而且……说实话,我早就想试试 Rust 了。
契机其实挺“功利”:上个月面试了一家大厂,被问到“有没有系统级语言经验”,我支支吾吾说了半天 Java 的 JIT,对方礼貌微笑。回来我就下了决心:技术债可以欠,但成长不能停。
Rust 的卖点我很清楚:零成本抽象、内存安全、无 GC、高性能。但风险也很明显:学习曲线陡,团队无人会,线上出问题我一个人兜底。
权衡之后,我决定赌一把——用 Rust 写一个独立的微服务,专门处理实时数据推送,和主 Java 服务通过 Kafka 解耦。 这样即使挂了,也不影响核心交易链路。跟老板汇报时我说:“就当技术预研,万一成了,明年双11就靠它扛。”
老板居然点头了。可能是因为我用了“降本增效”这个词(笑)。
实践:从 Hello World 到线上跑
第一步:搭架子
我用 cargo new realtime-pusher --bin 创建项目,引入几个关键 crate:
[dependencies]
tokio = { version = "1.0", features = ["full"] }
warp = "0.3" # Web 框架,比 Actix 轻
tokio-tungstenite = "0.20" # WebSocket 支持
rdkafka = "0.34" # Kafka 客户端
serde = { version = "1.0", features = ["derive"] }
tracing = "0.1" # 日志追踪
选择 Warp 而不是 Actix,是因为它基于 Hyper,组合式 API 更符合我的函数式偏好,而且代码量少,调试方便。
第二步:处理 WebSocket 连接
前端要的是“推”,所以服务得维护大量长连接。Rust 的 async/await + tokio 的多线程 runtime 刚好合适。核心逻辑如下:
use warp::ws::{Message, WebSocket};
use futures_util::{SinkExt, StreamExt};
async fn handle_ws(ws: WebSocket) {
let (mut user_ws_tx, mut user_ws_rx) = ws.split();
// 从全局广播通道订阅消息(后面会讲)
let mut broadcast_rx = BROADCAST_CHANNEL.subscribe();
// 启动两个任务:收 + 发
let send_task = tokio::spawn(async move {
while let Ok(msg) = broadcast_rx.recv().await {
if user_ws_tx.send(Message::text(msg)).await.is_err() {
break; // 连接断开
}
}
});
let recv_task = tokio::spawn(async move {
while let Some(_) = user_wsrx.next().await {
// 心跳或关闭处理,略
}
});
tokio::select! {
_ = send_task => {},
_ = recv_task => {}
}
}
这里踩了个坑:一开始用 mpsc channel,结果发现它不支持多消费者。后来换成 tokio::sync::broadcast,完美解决一对多广播。
第三步:对接 Kafka
前端数据来自上游业务系统,通过 Kafka Topic 发送。我用 rdkafka 消费消息,并推送到广播通道:
async fn kafka_consumer(broadcast_tx: broadcast::Sender<String>) {
let consumer: StreamConsumer = ClientConfig::new()
.set("bootstrap.servers", "kafka:9092")
.set("group.id", "realtime-pusher")
.create()
.expect("Kafka consumer");
consumer.subscribe(&["user-events"]).expect("Subscribe");
loop {
let message = consumer.recv().await.unwrap();
let payload = String::from_utf8(message.payload().unwrap().to_vec()).unwrap();
// 广播给所有 WebSocket 客户端
let _ = broadcast_tx.send(payload);
}
}
注意:broadcast::Sender::send() 如果没有接收者会返回 Err,但这里我们不在乎,直接丢弃即可。
踩坑实录:那些让我想砸电脑的瞬间
生命周期地狱
初期我把broadcast::Sender作为全局变量,结果在 Warp handler 里引用时各种'static报错。最后用Arc<Mutex<...>>包一层才搞定。Rust 的所有权模型真不是盖的,第一次写时感觉像在解数学题。WebSocket 断连检测
测试时发现客户端断网后,服务端还在傻乎乎地发消息。后来加了 ping/pong 心跳,并设置超时断开:tokio::time::timeout(Duration::from_secs(30), user_ws_rx.next()).await日志打不出来
本地跑得好好的,上 K8s 后日志全没了。原来是tracing默认输出到 stderr,而我们的日志收集只认 stdout。加了个tracing-subscriber配置才解决。CPU 爆了
压测时发现 CPU 占用 90%+。用perf分析发现是频繁字符串拷贝。改成Arc<str>共享数据后,CPU 降到 30%,内存也省了 40%。
效果对比:值不值得折腾?
上线前我做了压测(用 websocat 模拟 5k 并发连接),结果如下:
| 指标 | 原 Java 方案 | 新 Rust 方案 |
|---|---|---|
| QPS | 800 | 5200 |
| P99 延迟 | 450ms | 120ms |
| 内存占用 | 1.2GB | 320MB |
| CPU 峰值 | 75% | 35% |
最爽的是,Rust 服务跑了三天零 panic。运维老哥都惊了:“你这新服务是不是没流量?” 我:“不,它在默默扛着全场最狠的活。”
前端同事也反馈:“现在滑动看板真的丝滑了!” —— 虽然我知道他根本分不清是前端优化还是后端升级,但至少没再提新需求(笑)。
综合思考:小厂技术人的“野蛮生长”
回头看看这段经历,其实挺典型的:需求倒逼 + 个人兴趣 + 风险隔离 = 可行的技术探索路径。
在小厂,没有专职 SRE,没有架构师帮你兜底,但好处是决策链短,试错成本低。只要不影响核心业务,老板其实挺愿意让你“折腾点新东西”——前提是别把线上搞崩。
我也不是盲目追新。选 Rust 不是因为 hype,而是它确实解决了我们的问题:高性能 + 内存安全 + 无运行时依赖。部署时一个二进制文件扔进 Docker,连 JVM 都不用装,运维直呼内行。
当然,前端同学要是哪天说“我们要上 WebAssembly”,我可能会连夜跑路。不过话说回来,现在前后端界限越来越模糊,“综合能力”才是小厂程序员的生存之道。你得懂点网络协议,会调性能,能看前端报错,还得会写文档哄 PM 开心。
最后一点真心话
技术探索不是为了炫技,而是为了解放生产力。当你能在深夜安静地写代码,不用开会、不用改需求、不用解释“为什么这个 Bug 修了三天”,那种纯粹的快乐,是加班费给不了的。
Rust 我还在学,下周打算试试用它写个 CLI 工具,自动同步数据库表结构到前端 Type 文件——这样就不用每次改字段都手动通知前端了。PM 再说“就改一个字段”,我就甩他一个 .rs 文件。
如果你也在小厂挣扎,别觉得技术探索是大厂专利。哪怕每天只学 30 分钟,一年后回头看,你会感谢那个没躺平的自己。
好了,天快亮了,我得去挤地铁了。希望今天别遇到“需求变更”。
附:项目已开源(内部 GitLab),欢迎 star(如果你们能访问的话)😉

评论 0