高并发系统设计:从理论到实践——一个Spark老油条的血泪复盘

许建国
2025-12-16 21:47
阅读 418

嗨,各位码友。我是阿哲,在大数据领域摸爬滚打三年,日常和 Spark 打交道打得比女朋友还多(别问,问就是远程办公+单身狗)。平时除了在公司搞离线数仓、实时计算这些“正经活”,我还热衷参加各种技术分享会,偶尔也研究点前端动画玩玩——比如用 GSAP 做个粒子雨,或者用 Three.js 搞个3D地球转圈圈。虽然产品经理总说我“不务正业”,但谁让我对交互和视觉有执念呢?

今天这篇不是讲 Spark 的(虽然我真的很想吹一波 Structured Streaming 的背压机制),而是想聊聊高并发系统设计。为啥?因为上个月面试某大厂时,被一道“如何设计一个支持百万 QPS 的秒杀系统?”直接干懵了。回去后痛定思痛,结合自己去年参与的一个区块链数据聚合项目实战经验,决定写点干货,顺便帮正在求职的兄弟们避坑。


起因:被面试官按在地上摩擦

事情得从上个月说起。我在 BOSS 上看到一家 Web3 初创公司招后端,薪资给得挺狠,JD 里赫然写着“熟悉高并发、分布式系统设计”。我当时心想:“小意思,天天处理 TB 级数据流,还怕你区区百万请求?”

结果面试官上来就问:

“假设我们要做一个 NFT 交易市场,用户在开售瞬间疯狂抢购,系统要支撑 50 万 QPS,你怎么设计?”

我嘴一瓢:“用 Redis 缓存库存,加分布式锁……”

“Redis 单实例能扛 10 万 QPS 吗?锁竞争怎么解决?超卖怎么办?数据库怎么扛住回源压力?”

我:……(大脑宕机)

那一刻,我仿佛看到了自己被拒的邮件标题:“感谢您的时间,但……”

回家路上我就意识到一个问题:大数据开发≠高并发系统设计。我天天调 Spark 参数、优化 Shuffle、处理 Kafka 积压,但真到了面向 C 端用户的高并发场景,很多细节根本没碰过。

于是,我翻出去年做的一个项目——一个基于区块链的链上数据聚合服务,正好涉及高并发读写,今天就拿它当案例,复盘一下从理论到落地的全过程。


项目背景:链上数据 API 的“流量海啸”

我们团队去年接了个需求:为某 DeFi 协议提供实时 NFT 持仓查询接口。用户通过钱包地址,能查到他在某个 NFT 合约下的所有资产。

听起来简单?但问题来了:

  • 区块链数据是公开的,但查询速度慢(以太坊 RPC 调一次可能几百毫秒)
  • 用户量爆发式增长,尤其在项目方空投前夕,QPS 直接飙到 20 万+
  • 我们的 API 是免费开放的,意味着谁都可能来刷(包括机器人)

最惨的是去年 11 月某天晚上 8 点,一个热门 NFT 项目空投,我们的服务直接 503。监控报警响成一片,运维同事在 Slack 里疯狂@我:“阿哲!快看看!API 又崩了!”

我当时正撸着代码听 Lo-fi,看到消息差点把键盘砸了——又双叒叕是数据库被打爆了


实战拆解:四层防线扛住流量洪峰

第一关:流量削峰 + 网关限流

首先得承认,不是所有请求都值得处理。很多是爬虫、脚本、恶意刷单。所以第一件事:在入口做拦截。

我们用了 Kong 网关 + 自研限流插件(基于令牌桶算法),配置如下:

rate_limiting:
  minute: 60          # 每分钟最多 60 次
  burst: 10           # 允许突发 10 次
  key: ip             # 按 IP 限流

但光这样不够。高峰期合法用户也会被误杀。于是我们加了动态权重机制

  • 正常用户:限流宽松
  • 已知机器人 IP(从历史日志聚类得出):直接 block
  • 新 IP 首次访问:走验证码流程(用 hCaptcha,防绕过)

吐槽一句:产品经理一开始非要“无感体验”,不让加验证码。结果上线第一天就被 DDoS,最后灰溜溜同意了。

第二关:缓存穿透 & 击穿防御

用户查一个钱包地址,如果缓存没命中,就得去查链上数据——这一步最耗时。更要命的是,攻击者可以故意查询不存在的地址,导致每次都要穿透到后端。

我们做了三重防护:

  1. 布隆过滤器(Bloom Filter):提前加载所有已知有效钱包地址(每天凌晨用 Spark 任务跑全量扫描,输出到 Redis)。查询前先过 BF,无效地址直接返回 { "data": null }
  2. 空值缓存:对查询不到的结果,也缓存 5 分钟(带特殊标记),避免重复穿透。
  3. 热点 Key 自动发现:用 Redis 的 KEYS 太危险,我们改用 Redis Streams + 异步统计模块,每 10 秒聚合一次访问频次,超过阈值的 Key 自动提升为“热点”,走本地缓存(Caffeine)。
// 伪代码:热点 Key 本地缓存
if (hotKeyDetector.isHot(walletAddress)) {
    return localCache.get(walletAddress);
} else {
    return redisCache.get(walletAddress);
}

有个坑:一开始用 Guava Cache,结果 OOM 了。后来换成 Caffeine,内存占用降了 60%,真香。

第三关:异步化 + 最终一致性

链上数据更新是有延迟的(区块确认需要时间)。我们没必要强一致。于是大胆采用 “写时更新 + 读时补偿” 策略:

  • 用户查数据 → 返回缓存(可能旧)
  • 后台有个 Spark Streaming 作业,监听区块链事件(通过 WebSocket 接入 Alchemy),一旦发现新交易,就异步更新缓存和 DB
  • 如果用户查到的数据“看起来不对”(比如刚转账却没显示),前端 JS 会自动发起一次 force_refresh=true 请求,触发实时回源

前端这块我可得意了——用 Intersection Observer + requestIdleCallback,只在用户滚动到相关区域时才加载数据,减少无效请求。产品经理看了直呼“高级”。

第四关:数据库分库分表 + 读写分离

最终还是要落库。我们的 PostgreSQL 表结构长这样:

wallet_address contract_addr token_id balance last_updated
0x...a1 0x...b2 123 1 2023-11-05

初期单表 5 亿行,慢查询直接拖垮 DB。解决方案:

  • 按合约地址哈希分 64 库,每个库再按钱包地址哈希分 256 表
  • 写操作走主库,读操作走只读副本(AWS RDS Read Replica)
  • 对高频查询字段(如 wallet_address)建 BRIN 索引(适合时间/数值连续字段),比 B-Tree 节省 70% 空间

性能对比(单节点 vs 分库分表后):

指标 单库单表 64 库 * 256 表
平均查询延迟 (ms) 320 18
最大 QPS 1,200 85,000+
CPU 使用率峰值 98% 45%

关于“区块链”和“JavaScript”的意外联动

很多人以为区块链后端就是 Solidity + Go,其实不然。我们这个项目里,前端 JS 扮演了关键角色

  • 用户钱包连接用 Web3.js / Ethers.js,但为了防刷,我们在 JS 层加了 设备指纹(Canvas 渲染、WebGL 特征等)
  • 查询结果用 IndexedDB 本地缓存,减少重复请求
  • 甚至用 Web Workers 做 Bloom Filter 的本地校验(虽然最终没上,但 POC 成功了)

这让我意识到:高并发不仅是后端的事,前后端协同才能做到极致优化。现在我写 API,都会主动问前端:“你们能帮我挡掉多少无效请求?”


面试题挑战:我的答案升级版

回到开头那道面试题。现在再答,我会这样说:

  1. 分层防御:网关限流 → 缓存(BF + 空值 + 热点)→ 异步更新 → DB 分片
  2. 库存扣减:用 Redis Lua 脚本保证原子性,预减库存,异步下单
  3. 兜底策略:队列削峰(Kafka)、降级开关(Hystrix)、熔断机制
  4. 监控体系:Prometheus + Grafana 看 QPS/错误率/延迟,ELK 查日志,Sentry 抓前端异常

最关键的是:不要追求“完美架构”,而要“快速迭代 + 数据驱动”。我们第一版只用了 Redis + 限流,撑不住就加 BF,再不行就分库。每次上线都有 A/B 测试,用真实流量验证效果。


给求职者的真心话

如果你也在准备高并发相关的面试(尤其是投 Web3、电商、社交类公司),别光背八股文。建议:

  • 动手搭个 demo:用 Node.js + Redis + PostgreSQL 模拟秒杀,部署到 AWS Free Tier
  • 关注 SRE 思维:高并发不仅是性能,更是可观测性、可恢复性
  • 善用开源方案:Sentinel、Redisson、ShardingSphere 都有成熟实践,别 reinvent the wheel

我就是因为去年那个线上事故,逼自己啃完了《Designing Data-Intensive Applications》,现在面试再也不慌了(至少能聊 30 分钟)。


结语:程序员的浪漫在于“搞定它”

上周五晚上 11 点,我又收到一条告警:“API 延迟突增”。但这次,我淡定地喝了口冰美式,打开 Grafana——哦,原来是某个新上线的 DApp 在疯狂调用。我调整了限流策略,加了热点缓存,10 分钟搞定。

看着监控曲线重新平稳,那一刻的成就感,比做出一个炫酷的前端动画还爽。

高并发系统设计没有银弹,只有不断踩坑、复盘、优化。但正是这种“把混乱变成有序”的过程,让我觉得写代码这件事,还挺酷的。

共勉。
—— 一个在家撸 Spark 也撸高并发的远程打工人

P.S. 如果你对区块链数据管道 or 前端性能优化感兴趣,欢迎来 GitHub 找我(ID: zhe-spark-dev)。最近在用 Svelte 做一个链上数据可视化工具,求 star 🙏

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝