一个人,一台服务器,10万长连接:我是如何用 Rust + Flutter 撸出一个交易系统软件的?

5 阅读4分钟

大家好,我是《交易学徒》的独立开发者。

做交易类软件,最让人头秃的就是实时行情推送。最近我把后端的行情网关重构了一版,目标是在单机环境下支撑 10w+ 同时在线用户

在做技术选型时,身边的朋友都劝我:“上微服务吧,Redis 集群搞起来,Kafka 消息队列堆上去。” 作为一个“抠门”的独立开发者,我算了一笔账:这一套运维成本和服务器费用,能把我吃穷。而且,为了解耦而解耦,对于单兵作战来说就是灾难。

谁说高并发一定要微服务?

我决定反其道而行之:回归单体,All in Rust。 最终的架构非常狂野:没有 Redis,没有 Kafka,没有任何外挂组件,只有一个几 MB 的 Rust Binary。

经过几轮压测和线上实战,这套架构稳得一批。今天就把压箱底的 6 个优化思路分享出来,欢迎大家来指点(或者吐槽)。

一、 极简主义:按需订阅(省下的就是赚的)

早期的 Demo 版比较无脑,客户端一连上来,服务端就咣咣咣推送 Top 20 币种的行情。用户手机发烫,我也在给云厂商送钱。

优化后,我采用了**“查岗式”订阅**: 服务端时刻盯着客户端的 UI 状态。你停留在 BTC/USDT 的 15分钟 K线页面,我就只给你推这一个数据。你手一滑切到了 ETH,我立马把 BTC 的订阅掐断。

给兄弟们算一笔带宽的账: 假设一个行情包 200 Bytes,一秒推 5 次。

  • 豪横模式(全推): 10万用户 × 20个币种 × 200B × 5次 = 2 GB/s (带宽费直接破产)
  • 抠门模式(按需): 10万用户 × 1个币种 × 200B × 5次 = 100 MB/s

结论: 逻辑改两行,带宽节省 95% ,单机千兆网卡瞬间觉得自己又行了。

二、 列表页怎么搞?白嫖 Cloudflare!

对于“行情列表”这种一屏显示几十个币种,但实时性要求没那么高(延迟 1秒无所谓)的页面,我坚决不用 WebSocket。

我的骚操作:

  1. 客户端改为 HTTP 轮询(Polling),1秒一次。
  2. 服务端设置 HTTP 头,允许缓存。
  3. 关键点: 把流量全部经过 Cloudflare,设置边缘缓存 TTL = 1秒。

**效果:**哪怕 10 万人同时刷新列表,99% 的请求都被 CF 的全球节点挡住了,真正打到我 Rust 源站的 QPS 只有个位数。既省了服务器资源,又利用了 CDN 加速,真·白嫖快乐。

三、 架构做减法:要什么 Redis?

这是我这次重构最大的感悟:当你的代码跑在同一个进程里,内存就是最快的中间件。

  • 干掉 Redis: 我用了 Rust 的 DashMap (Concurrent HashMap)。数据就在内存堆上,读写是纳秒级的,还没网络开销。
  • 干掉 Kafka: 我用了 tokio::sync::broadcast。我们要 MQ 不就是为了发布订阅解耦吗?Rust 内部的一个 Channel 就能做,还没运维负担。只要我的进程活着,消息系统就活着。

四、 拒绝假死:信使模式 (Messenger Pattern)

Tokio 异步代码,最怕的就是阻塞 Event Loop。 如果某个用户进电梯了,网络巨卡,socket.send().await 卡住了,可能会连累同个 Loop 下的心跳检测,导致服务端误判掉线。

我的解法:读写彻底分离。 为每个连接 spawn 一个独立的 send_task,通过 mpsc::channel 通信。 主业务逻辑只管往 channel 里丢数据,至于怎么发、发不发得出去,让那个 task 自己去头疼,别挡着我处理别人的业务。

五、 严格背压:该丢就丢,别犹豫

如果用户网络实在太差,Channel 满了怎么办? 这里必须要有**“背压控制” (Backpressure)**。

我的策略非常冷酷:使用 try_send,一旦 Channel 满,直接丢弃新消息。 对于实时行情来说,旧数据比没数据更可怕。这行代码是系统的保命符,防止因为几个慢连接把服务端内存撑爆(OOM)。

六、 极致性能:零拷贝 (Zero-Copy)

给 10 万个人发同一条 BTC 价格,最蠢的做法是 Copy 10 万份内存。

Rust 的 Arc (原子引用计数) 简直是神器:

Rust

// 内存中只有一份二进制 Payload
let payload = Arc::new(Bytes::from(vec![...]));

// 10万次分发只是增加了 10万次引用计数
// 几乎没有任何内存分配开销,CPU 缓存极度友好
for client in clients {
    client.tx.try_send(payload.clone()); 
}

写在最后

这套**“Rust 单机后端 + Flutter 前端”**的架构,目前跑在我的 App《交易学徒》上,稳得一比。 事实证明,对于独立开发者来说,简单才是最高的护城河。盲目追求大厂的微服务架构,往往是死得最快的。

我是全栈做交易的凯哥,如果你对 Rust 高并发或者 Flutter 性能优化感兴趣,欢迎在评论区交流!

(福利时间:从Google下载 App 体验并在评论区留下你的 ID,我后台人肉送一个月 VIP,就当交个朋友!)