3.4 KiB
3.4 KiB
ADR-003-04: 异步操作轮询策略
状态
已接受
日期
2026-02-03
背景
Salesforce Metadata API 的许多关键操作(如 deploy, retrieve)是异步执行的。API 调用立即返回一个 AsyncResult,其中包含 id 和初始状态。客户端必须后续通过此 ID 查询最终结果。
我们需要为 datai-scene-salesforce 模块设计一种通用的异步操作管理机制,以支持上层业务(如部署、检索)获取操作结果。
核心问题是:如何在服务端高效且简单地获取异步操作的最终状态?
决策
选择 服务端同步轮询 (Server-side Synchronous Polling) 方案。
具体实现为:
- Service 层封装:在
MetadataAsyncService中提供pollAsyncResult(String asyncId, long interval, long timeout)方法。 - 阻塞式实现:使用
Thread.sleep(interval)在循环中检查状态,直到状态变为Completed、Failed或超时。 - 对外接口:
- 提供
GET /poll/{id}接口,允许客户端发起一个长连接请求等待结果(受超时限制)。 - 同时提供
GET /status/{id}接口,供不希望阻塞的客户端手动轮询。
- 提供
理由如下:
- 简单可靠:当前项目架构为基于 Spring Boot 的 Web 应用,使用线程阻塞虽然占用资源,但在 Metadata API 操作并发量不大的场景下(通常是管理员操作),是实现成本最低且最可靠的方案。
- 易于封装:将轮询逻辑封装在服务端,可以简化上层业务代码。例如,部署服务可以直接调用
asyncService.pollAsyncResult(...)并在方法返回后立即进行后续处理(如更新部署记录),而无需编写复杂的异步回调逻辑。 - 超时控制:服务端统一控制超时和重试间隔,防止客户端滥用 API 或因网络问题导致的轮询失控。
后果
正面影响
- 简化客户端逻辑:客户端(前端或第三方调用者)可以选择简单的“等待结果”模式,减少了处理轮询定时器的复杂度。
- 统一管理:轮询频率和超时策略由服务端统一配置,易于优化和调整。
- 开发效率:利用现有的同步编程模型,开发调试迅速。
负面影响
- 资源占用:每个轮询请求会占用一个 Tomcat 线程。如果并发量激增,可能导致线程池耗尽(但在 Metadata 操作场景下风险较低)。
- 响应延迟:受限于轮询间隔(如 5秒),获取结果的延迟至少为
interval时间,不如 WebSocket 实时。
替代方案
方案 2:客户端轮询 (Client-side Polling)
- 描述:服务端只提供
checkStatus接口,客户端负责定时调用。 - 优点:服务端无状态,不占用线程等待,扩展性好。
- 缺点:如果服务端其他模块(如部署后自动运行测试)依赖该结果,则服务端仍需实现轮询逻辑,导致逻辑重复。
方案 3:响应式/异步非阻塞 (Reactive/Async)
- 描述:使用 Spring WebFlux 或
@Async+CompletableFuture。 - 优点:非阻塞,资源利用率高。
- 缺点:引入了响应式编程的复杂度,与当前项目的传统 Servlet 架构风格不一致,增加了维护成本。