阶段一:客户端发起“钓鱼”请求
客户端把本地配置的
Map<dataId+groupId, MD5>封装好,发一个 HTTP 请求给服务端。客户端做好了等 30 秒的准备(ReadTimeout 设置为 30s 以上)。
阶段二:服务端“Hold 住”请求(长轮询核心)
服务端收到后立马比对 MD5。
如果不一致:说明配置早就变了,立马返回发生变化的 key 列表。
如果一致:服务端开启 Tomcat 的
request.startAsync()。这个操作极其精妙,它让 Tomcat 把当前请求的 HTTP 连接通道留着,但把当前负责执行的业务线程给释放回线程池去接别的客,实现了高性能挂起。随后,服务端把这个异步上下文对象(包含配置 Key、MD5、Response 引用)包装成一个任务,塞进一个队列,并丢给定时任务线程池(29.5s 后执行)。
阶段三:双轨并行的“收网”机制(CAS 保底)
此时,有两条路可以唤醒这个被 Hold 住的请求:
路径 A(配置没变,憋到超时):29.5 秒到了,定时任务触发。抢先通过 CAS 锁住对象,把对象从队列移除,往 Response 里写入空响应。Tomcat 链接断开,客户端收到空响应,知道配置没变,扭头发起下一次长轮询。
路径 B(突发变更,事件发布):在 29.5 秒内,有人改了配置。服务端触发 DataChangeEvent(发布订阅模式)。订阅者线程被唤醒,拿到队列里所有的长轮询对象,比对发现 MD5 确实变了。抢先通过 CAS 锁住对象,把发生变更的
dataId+groupId写入 Response 返回。同时,由于 CAS 互斥,定时任务那边走到这里就会发现被抢先了,自动放弃。
阶段四:客户端“两步走”更新
客户端收到响应。如果是空,继续长轮询。
如果里面有变更的 Key,客户端立刻发起一个普通的 HTTP Get 请求去下载最新配置文本,更新本地内存,计算新 MD5,然后带着新 MD5 进入下一轮长轮询死循环。