异步 DNS 缓存中的状态机博弈
在构建高性能负载均衡器时,DNS 解析往往是那个被忽视的性能瓶颈。如果你直接调用标准的 getaddrinfo,原本毫秒级的请求可能会因为一次 DNS 超时而卡死整个 Worker 线程。为了解决这个问题,异步设计是必经之路,而状态机则是控制并发解析冲突的终极武器。
为什么不能只是加个锁?
在简单的缓存实现中,我们习惯于“查缓存 -> 没命中 -> 查上游 -> 写缓存”。但在高并发环境下,当一个热门域名过期时,成千上万个并发请求会同时发现缓存失效。如果只是简单加锁,会导致严重的锁竞争;如果不加控制,则会触发“缓存击穿”,瞬间向 DNS 服务器发送大量重复请求。
状态机的四种博弈
在工业级负载均衡器设计中,一个 DNS 缓存条目通常存在四种状态:
- RS_NOT_RESOLVED (未解析):初始状态。第一个到达的请求会抢占解析权。
- RS_IN_PROGRESS (解析中):解析任务已发出。此时后续请求不应发起新任务,而是进入挂起队列(Wait Queue),静默等待通知。
- RS_RESOLVED (已解析):数据就绪。请求直接读取结果。
- RS_FAILED (解析失败):记录失败状态,避免持续重试拖垮系统。
优雅的“双重检查”
当一个处于 RS_IN_PROGRESS 状态的请求收到“解析完成”的通知时,它并不直接返回结果,而是再次触发一次状态机检查。这种“双重检查”的思想确保了即便在高并发竞争下,每个请求获取到的都是最新的、一致的解析视图。
Stale-While-Revalidate 策略
为了极致的响应速度,当缓存过期但尚未完成更新时,我们通常允许请求继续使用“过期”的旧数据(Stale data),同时在后台异步触发更新任务。这种权衡避免了用户请求等待 DNS 解析的延迟,虽然牺牲了一点点时效性,但换取了系统的极高吞吐量。
总结
异步 DNS 缓存不仅仅是加一个 map,它是一场关于并发、状态和一致性的博弈。通过将复杂的网络 I/O 隐藏在状态机背后,我们才能在保证响应速度的同时,维持系统的稳定性。
系列: Arch (37/90)
系列页
▼