Java HttpClient WebSocket 怎样处理重连后的消息顺序
Java HttpClient 的 WebSocket 重连后,要靠应用层的消息序列与恢复协议保证业务顺序:服务端为同一业务流提供稳定序号,客户端记录最后连续提交的游标,重连时从该游标继续回放;本地再用连接代次隔离旧回调。重新建立 WebSocket 只恢复通信通道,不会自动补发断线期间的消息。
官方地址:https://docs.oracle.com/en/java/javase/25/
使用场景:连接重新通了,业务状态却跳了一步
以库存变更订阅为例,客户端已经处理到 120,断线后先收到 122,随后又收到重放的 120。直接按到达顺序修改库存,可能漏掉 121,也可能重复扣减。另一个隐蔽来源是旧连接已经投递到业务线程池的任务:新连接开始工作后,旧任务才完成。
Java 的 Listener 对单个 WebSocket 的回调有顺序约束,但不同 WebSocket 的回调可能并发。回调把任务提交到并行线程池后,业务提交完成的顺序也需要自己管理。因此必须分别回答三个问题:这一条消息属于哪次连接、属于哪个业务流、是否接在最后成功提交的事件后面。
候选方案:直接继续、游标回放还是快照恢复
对于只展示最新温度或在线人数的页面,可以在重连后取最新值,不追求每条中间变化。对于库存增减、任务状态演进等增量事件,应优先选择带序号的回放协议。服务端若只保留有限历史,还需要快照加水位的恢复方式。
| 方案 | 适用数据 | 重连后的主要要求 |
|---|---|---|
| 直接接收最新状态 | 允许覆盖的完整状态 | 用版本过滤旧状态 |
| 游标回放 | 不能随意丢弃的增量事件 | 稳定序号、历史保留、重复处理规则 |
| 快照加水位 | 历史已过期或重放成本很高 | 快照与后续事件具有一致的切换水位 |
这些是应用协议的设计选择,不是 HttpClient 的开关。若服务端既没有历史查询,也没有可靠快照,客户端不能仅靠本地排序恢复已经丢失的内容。无限等待一个从未收到的序号,只会让故障变成缓冲区不断增长。
对比维度:四个字段不要混用
stream 标识业务序列的归属,例如一个仓库的一条变更流;sequence 表示这个流里的事件顺序;lastApplied 表示已经连续、成功提交的位置;generation 则是客户端本地的连接代次。连接断开并不意味着业务序列从头开始,连接代次也不能替代业务事件编号。

本文选择一个明确契约:单一 stream,正整数序号从 1 开始且连续递增,0 表示尚未处理任何事件。多个 stream 应各自维护游标。若服务端按全局序号编号、订阅过滤后允许跳号,就不能照搬“必须等于上一条加一”的规则,应改用服务端提供的前驱游标、恢复令牌或其他连续性约定。
同样,服务端重新创建流并把序号归零时,需要新的流标识或服务端 epoch。单独保存一个数字不足以区别“新流的 10”和“旧流已经处理过的 10”。游标恢复必须携带完整订阅身份,而不只是最大序号。
推荐选择:一个串行闸门同时管代次、重复与缺口
下面是应用层核心类,采用 Java 17 及以上可用的语法,不包含具体服务端协议、网络连接器或数据库实现。它刻意把提交操作留成接口:实现方必须保证业务修改与持久化游标在同一事务里成功,否则跨进程恢复仍会出现重复或遗漏。
final class SequenceGate {
interface Commit {
// 实现必须把业务修改和持久化游标放在同一事务中。
void applyAndSave(long sequence, String payload) throws Exception;
}
record Ticket(long generation, long after) {}
enum Result { APPLIED, DUPLICATE, STALE, GAP, PAUSED }
private long generation;
private long lastApplied;
private boolean paused = true;
private final Commit commit;
SequenceGate(long persistedCursor, Commit commit) {
if (persistedCursor
每次尝试建立连接前调用 beginConnection(),为该连接创建独立 Listener,固定持有返回的 Ticket。通过 HttpClient.newWebSocketBuilder().buildAsync(uri, listener) 建立连接后,用约定的应用消息请求 after=Ticket.after() 的事件;“after”表示严格晚于游标。它是本文协议约定,不是 WebSocket 标准参数。
握手失败或连接出错时,由连接管理器对该 Ticket 调用 retire;退避重试也归同一个管理器管理,避免 onError、onClose 和发送失败各自启动一条重连链。过期的连接建立完成回调应关闭自己的连接,不能把它登记成当前连接。
闸门把 seq 当作重放重复,只允许恰好下一条提交;发现缺口就暂停本代并通知管理器恢复。它不会缓存后续事件,因此不会偷偷跳过缺失项。示例在锁内调用提交接口,是为了展示最简单的串行提交边界,适合短小同步事务;高吞吐场景可以改为每条业务流一个有界串行执行器,但代次切换和实际提交仍要在同一顺序域内完成。

假设已提交到 120:旧代事件直接忽略;当前代重放 120 判为重复;当前代先发 122 则进入缺口暂停,不能直接把游标改成 122。重新建立恢复会话后,从 120 回放 121、122 才能连续提交。这是按规则推导的例子,没有声称运行结果。
不适用情况:分片、异步副作用和关闭也有边界
Listener 的一次 onText 不一定是一整条业务消息。每个连接使用独立累积器,把分片复制并组装到 last=true 后再解码序号,同时设置总长度上限,断开时丢弃未完成片段。不能把旧连接的一半文本留给新连接继续拼接。
request(1) 增加的是回调次数额度,不是业务消息确认,也不是一次完整消息的保证。返回的 CompletionStage 与需求额度是不同机制;如果回调立即派发并行异步任务然后返回,不能由此推出任务已按顺序完成。需要在任务完成后继续发放额度时,应把它与有界队列、片段组装和失败处理共同设计。
本例的同步锁只覆盖当前进程。多进程同时消费同一条流时,还需要在存储层限制游标竞争;业务副作用若发生在外部服务,也不能靠一个数据库事务自动实现跨系统“恰好一次”。可按业务选择幂等键或可靠投递机制。提交结果不确定时,应读取持久化状态核对,不能简单把内存游标向前推。
主动停止时,先将管理器置为停止状态,取消已安排的重连,并 retire 当前代,再调用 sendClose(WebSocket.NORMAL_CLOSURE, "") 发起正常关闭。sendClose 的完成不等于输入已经关闭;需要等待对端关闭并设置应用自己的等待期限,到期可用 abort() 终止。停止后的迟到 onError/onClose 不应再次安排重连。
决策表:按业务语义选择恢复方式
| 条件 | 选择 | 不能承诺的事 |
|---|---|---|
| 完整状态可覆盖 | 重连拉最新状态并比较版本 | 保留全部中间事件 |
| 增量事件可回放 | 连续提交游标、代次隔离、去重 | 仅靠连接成功实现恢复 |
| 游标早于历史保留范围 | 一致快照加切换水位 | 从不存在的历史补齐事件 |
| 多个业务分区独立有序 | 每分区维护序列与提交域 | 由一个最大数字推导全局顺序 |
常见问题
换成单线程业务池就足够了吗
它只能帮助串行执行,不能补齐断线消息,也不能区分旧连接和重放重复。仍需要稳定序列、恢复协议以及在提交点执行的代次检查。
为什么不直接按时间戳排序
时间戳可能重复,也不一定表达业务提交顺序。除非服务端定义了可靠的排序与缺口语义,否则它只能作为展示字段,不能替代恢复游标。
重连后先接实时消息,再异步补历史可以吗
必须先有服务端定义的水位与缓冲合并规则。本文的简化方案要求恢复会话按序返回历史并衔接实时流;不具备这个契约时,应冻结提交并通过独立同步流程恢复。
参考资料
Java SequencedSet reversed 怎样反向遍历不复制集合
- 上一篇
- Java SequencedSet reversed 怎样反向遍历不复制集合
- 下一篇
- Java virtual threads 与 ThreadLocal 资源绑定的清理边界
-
- 文章 · java教程 | 12分钟前 | 并发 · Java · java arena MemorySegment FFM API
- Java Arena.ofConfined 与 ofShared 如何选择
- 335浏览 收藏
-
- 文章 · java教程 | 13分钟前 | java Event JFR shouldCommit 事件过滤
- Java JFR Event 怎样只记录满足条件的事件
- 412浏览 收藏
-
- 文章 · java教程 | 18分钟前 | 虚拟线程 · 性能边界 · java 并发 threadlocal 资源释放 ScopedValue Virtual Threads
- Java virtual threads 与 ThreadLocal 资源绑定的清理边界
- 339浏览 收藏
-
- 文章 · java教程 | 21分钟前 | java LinkedHashSet SequencedCollection reversed SequencedSet
- Java SequencedSet reversed 怎样反向遍历不复制集合
- 248浏览 收藏
-
- 文章 · java教程 | 23分钟前 | Stream · Java教程 · java Stream Gatherer finisher Gatherer.Downstream
- Java Gatherer.Downstream 怎样在结束阶段发送尾部元素
- 336浏览 收藏
-
- 文章 · java教程 | 24分钟前 | 工程实践 · 虚拟线程 · java 虚拟线程 ThreadFactory 线程命名 Thread.Builder
- Java Thread.Builder factory 怎样复用虚拟线程的命名规则
- 233浏览 收藏
-
- 文章 · java教程 | 29分钟前 | java 请求上下文 StructuredTaskScope ScopedValue
- Java ScopedValue 怎样把请求上下文传给子任务
- 315浏览 收藏
-
- 文章 · java教程 | 9小时前 | Java · java 正则表达式 pattern named group
- Java Pattern named group 提取结构化文本的方法
- 333浏览 收藏
-
- 文章 · java教程 | 12小时前 | 字符串 · 工程实践 · Java教程 · java Locale.ROOT String.toLowerCase String.toUpperCase 机器可读字符串
- Java Locale.ROOT 固定机器可读字符串大小写的做法
- 436浏览 收藏
-
- 文章 · java教程 | 12小时前 | Java · 文件下载 · java httpclient Content-Disposition BodyHandlers ofFileDownload filename
- Java BodyHandlers.ofFileDownload 怎样按响应头命名文件
- 438浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 1018次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1020次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1155次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1121次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 962次使用
-
- Java 时间格式兼容多个输入格式的方法
- 2026-10-18 501浏览
-
- Java try-with-resources 多个资源关闭顺序是什么
- 2026-09-10 501浏览
-
- 矩阵主副对角线快速定位技巧
- 2026-05-31 501浏览
-
- Java多态优化流程代码与行为分发改进
- 2026-05-26 501浏览
-
- JVM 类元数据双亲委派链表深度解析
- 2026-05-21 501浏览

