知识卡片

HTTP长轮询:用"夯住连接"而非提高频率实现近实时,消息池补上竞态窗口

结构图卡

内容

Web聊天室要在不用WebSocket(存在浏览器兼容性问题)的前提下实现接近实时的消息推送,传统轮询(poll)的做法是客户端每隔固定时间主动发一次”取消息”请求——这个方式的实时性完全取决于轮询间隔,间隔缩短能降低延迟,但代价是绝大多数轮询请求根本没有新消息、白白消耗服务端资源。”消息连接”(即HTTP长轮询)换了一个思路解决同样的问题:客户端发起一条HTTP连接后,如果服务端当前没有新消息,不立刻返回,而是把这条连接”夯住”(不响应、不断开),直到有新消息到达时才把消息通过这条连接带回;由于HTTP连接不能无限期挂起(浏览器/WebServer通常最多允许夯住90秒),一旦这条连接因超时被动断开,客户端要立刻发起一条新的消息连接顶上,从而保证任意时刻都有一条”在等消息”的连接存在。这样一来,实时性不再依赖”轮询频率”,而是依赖”连接何时被唤醒”,服务端在没有消息时几乎不承担无谓的请求压力(每90秒才有一次空转),只有真正有消息时才发生一次有效的请求-响应。但这个机制留有一个天然的竞态窗口:如果消息恰好在”上一条连接刚返回、下一条连接还没建立”的瞬间到达,就没有连接能接住这条消息——解决办法是引入一个以uid为key的”消息池”,没有连接在等的时候消息先暂存进消息池,新连接一建立就优先从消息池里取走暂存的消息再返回。这套机制在设计模式上对应的正是观察者模式:聊天室用户是Observer,聊天室本身是Subject,Observer通过消息连接向Subject订阅,Subject状态变化(新消息产生)时通过这条连接反向通知Observer。

结构图

sequenceDiagram
    participant C as 客户端(Observer)
    participant S as 服务端(Subject)
    participant P as 消息池(按uid存储)

    C->>S: 发起HTTP消息连接
    Note over S: 无新消息,连接被夯住(最多90s)
    alt 90秒内无消息
        S-->>C: 超时断开
        C->>S: 立即发起新消息连接
    else 消息到达且有连接在等
        S-->>C: 立即返回消息
        C->>S: 立即发起新消息连接
    else 消息到达但恰无连接在等(竞态窗口)
        S->>P: 消息暂存进消息池
        C->>S: 新连接建立
        S->>P: 从消息池取出消息
        S-->>C: 返回消息
    end

参考来源

- 位置:《高可用架构(第1卷)》第2章《高可用架构原理与分布式实践》"2.4 从零开始搭建高可用IM系统"节,"2.4.3 Web聊天室"(源文件:_epub-src/OEBPS/Text/Chapter2_4_4.xhtml) - 结论依据:原文说明消息连接"没有消息到达的时候……被夯住,不返回……最多被夯住90s,就会被断开……如果HTTP消息连接被断开,立马再发起一个HTTP消息连接",并描述消息池处理竞态窗口的7个步骤,以及"这个消息连接的思想是一个观察者模式"的总结,共同支撑本卡片结论与结构图。 - 原始内容:没有消息到达的时候,这个HTTP消息连接将被夯住,不返回……最多被夯住90s,就会被断开……这里有个小概率事件,正返回消息(可以认为此时没有消息连接)的瞬间又到达了一条消息……此时服务端要有一个类似于"消息池"的东西,将这个消息暂存起来……这个消息连接的思想是一个观察者模式。