这是一篇关于 Fly.io 公司 Anycast 路由器中一个严重并发 bug 的文章,主要内容如下:
- 背景:Fly.io 是一个在全球多个地点运行应用的公共云,其 Anycast 路由器是代码库中最大的 Rust 项目。客户给 Fly.io 提供 Docker 容器,并指定在全球 30 多个区域中的一个运行。Fly.io 将容器转换为轻量级虚拟机,然后将它们链接到 Anycast 网络,
fly-proxy
负责路由请求。 角色介绍:
fly-proxy
:无畏的 Anycast 路由器。corrosion
:无畏的 Anycast 路由协议。Rust
:一种编程语言。read-write locks
:允许多个读取器或一个写入器的同步原语。parking_lot
:Rust 中备受推崇的优化锁实现。
- Anycast 路由:
fly-proxy
在处理请求时做了一些有趣的事情,它是用 Rust 编写的,处理多个协议、TLS 终止和证书颁发等。管理数百万个应用的数百万个连接是一个难题,即“状态分布问题”,这类似于一个路由协议。 - 路由协议实现困难:全局复制的 SQLite 数据库是一个很好的原始数据,但每次请求到达时并不实际进行 SQL 查询。
fly-proxy
中有一个用于路由信息的记录系统(Corrosion)和一个用于快速决策的内存聚合(Catalog)。去年出现了一个有趣的 bug,if let
表达式中的锁持有时间过长,导致整个 Anycast 路由层出现死锁。 - Watchdog 与区域化:Anycast 中断的经历促使 Fly.io 制定了两项任务,一是通过“看门狗”系统使死锁不再致命;二是解决所有路由状态共享一个全局广播域的问题,即“区域化”,将大多数更新限制在发生的区域。
新 bug 及解决过程:
- 新 bug 第一轮:懒加载:将原始的 Anycast 路由器转换为具有分区状态的区域化路由器,部分
fly-proxy
目录开始懒加载,之后代理开始锁定并被看门狗杀死,回滚了更改,怀疑是懒加载改变了读写模式和锁竞争。 - 新 bug 第二轮:锁重构:对代理进行一些重构,包括使目录写锁超时、消除 RAII 式锁获取并替换为显式闭包以及添加日志和指标,但仍出现更多锁定问题,尤其是在欧洲。
- 新 bug 第三轮:遥测检查:通过仪器检测仍发现死锁,线索是锁超时日志在代理锁定和被杀死之前出现,但无法确定原因。
- 新 bug 第四轮:陷入疯狂:看门狗杀死代理时会捕获核心转储,发现代理锁定时没有线程在关键部分,但有多个线程等待获取锁,推测可能是内存损坏,但内存看起来并未损坏,通过
gdb
进行更深入的核心转储检查。 - 新 bug 第五轮:绝望之举:切换到
read_recursive
,生成了新的证据RwLock 读取器计数溢出
日志消息,但问题仍未解决。 - 最终发现:经过一系列排查,发现
parking_lot
的 RWLock 实现存在问题,在特定条件下会导致锁损坏。parking_lot
团队很快确认并修复了该 bug,修复后锁定问题不再出现。
- 新 bug 第一轮:懒加载:将原始的 Anycast 路由器转换为具有分区状态的区域化路由器,部分
- 后续改进:审计了所有目录锁定,消除了
iflets
,停止依赖 RAII 锁保护,获得了锁定时指标,所有写入都有标签并记录慢写入的上下文信息,还跟踪写入锁的最后持有者和当前持有者,以便下次出现死锁时能快速定位问题。
总之,通过一系列排查和修复工作,Fly.io 解决了这个严重的并发 bug,并在过程中进行了一些改进,以更好地处理未来的并发问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。