7.8 KiB
HTTP 服务完整接入示例
源码工程将 SDK 的发送器、Worker、结果消费器放进业务 HTTP 进程。Orbit 平台仍然独立部署,业务表放在自己的 MySQL 数据库。
1. 场景与运行结果
客户端用固定 operation_id 提交一个对象引用。HTTP 事务同时写入业务行和待发请求,后台发送给 Orbit。Worker 对引用进行纯计算转换,结果通过可靠 WebSocket 订阅写入业务 Inbox,再由 SDK 确认交付。
接口返回 202 表示业务本地事务已提交。查询先看到 PENDING/SENDING,随后 ACCEPTED,最后收到 execution=SUCCEEDED 和转换结果。每个阶段以数据库记录为准,HTTP 请求不等待远端任务执行结束。
2. 准备配置
需要 Go 1.27.1、运行中的 Orbit 和 MySQL 8.4。创建一个独立的业务数据库,例如 orbit_http_demo,不使用 Orbit 平台库。由业务迁移账号初始化表,正常运行账号只授予所需业务表读写权限。
在仓库根目录创建示例专用应用/队列凭据,下面的 ORBIT_MYSQL_DSN 是平台库:
umask 077
mkdir -p .local
export ORBIT_MYSQL_DSN='平台数据库DSN'
go run ./cmd/orbit-admin bootstrap \
--application http-demo --namespace default --environment development \
--queue http-demo --worker-access --event-access \
--output .local/http-demo.credentials.json
该命令用于一次性准备专用队列;重启复用原凭据,不重新 bootstrap。凭据具有任务提交、队列读取、Worker 和事件所需权限,生产可按角色拆分身份。
# 在仓库根目录保存凭据绝对路径,再进入示例。
export ORBIT_CREDENTIALS_FILE="$PWD/.local/http-demo.credentials.json"
export ORBIT_URL='http://127.0.0.1:8080'
export APP_MYSQL_DSN='业务账号:密码@tcp(127.0.0.1:3306)/orbit_http_demo'
export APP_HTTP_ADDR='127.0.0.1:8090'
cd examples/http-service
export GOWORK=off
go run ./cmd/server init
export ORBIT_SUBSCRIPTION_ID="$(go run ./cmd/server provision)"
go build -o ./server ./cmd/server
./server serve
init 仅首次在空业务库执行,不自动升级或覆盖已有表;生产迁移管理四张表:sender_outbox、sender_jobs、receiver_inbox、receiver_task_views。全部字段有中文注释,没有外键。
provision 在发送任何业务任务前执行,使用固定键和固定内容创建订阅。将输出的订阅 ID 保存在部署配置中,后续启动只需 serve,不依赖 Orbit 在线才能接收本地业务请求。运行凭据必须能消费该订阅的绑定身份,轮换按原凭据规则执行。
也可用 ORBIT_TOKEN、ORBIT_NAMESPACE_ID、ORBIT_QUEUE_ID 配置代替文件;环境变量优先。APP_HTTP_ADDR 仅接受回环 IP,示例不提供对公网的账号体系。集成到现有 HTTP 服务时复用已有认证与授权中间件,不要直接取消绑定限制暴露演示路由。
3. HTTP 接口与调用
| 接口 | 返回 |
|---|---|
GET /health/live |
进程存活:200 |
GET /health/ready |
正在接收请求且业务数据库可用:200;关闭期间/数据库不可用:503 |
POST /jobs |
保存业务事务,202;字段错误400、原操作内容冲突409、存储/提交不明503 |
GET /jobs/{operation_id} |
业务提交与结果视图200;不存在404、存储错误503 |
export OPERATION_ID="$(openssl rand -hex 16)"
curl -sS http://127.0.0.1:8090/jobs \
-H 'Content-Type: application/json' \
-d "{\"operation_id\":\"$OPERATION_ID\",\"source_ref\":\"objects/demo.txt\"}"
curl -sS "http://127.0.0.1:8090/jobs/$OPERATION_ID"
首次提交:
{"operation_id":"原操作ID","local_committed":true,"duplicate":false}
等待结果入库后查询:
{
"operation_id":"原操作ID",
"submission":"ACCEPTED",
"code":"ACCEPTED",
"task_id":"平台任务ID",
"execution":"SUCCEEDED",
"result":{"source_ref":"objects/demo.txt","normalized_ref":"OBJECTS/DEMO.TXT"}
}
用同一个 OPERATION_ID 和相同请求重复 POST,返回 duplicate=true,不重复写业务行或创建平台任务。相同 ID 改 source_ref 返回409。客户端超时/503时保留原 ID、原内容重试,不重新生成随机 ID。source_ref 最多512个Unicode字符,不能空白或包含控制字符;接口正文上限4KiB。
4. 生命周期
生命周期源码包含以下顺序:
- 加载配置、连接业务库、核验表、构造 SDK 组件,再创建 HTTP 监听。任何构造/监听失败都关闭已经持有的资源;不会先启动失去宿主管理的 Worker。
- 启动 HTTP、发送器、Worker 和结果消费器;各组件使用独立的宿主 context,进程信号只触发统一关闭流程。
- HTTP 监听失败或任何后台组件不可恢复退出,撤销 readiness,进入同一关闭路径,错误返回 main 并以非零状态退出。
- 收到 SIGTERM/SIGINT 后撤销 readiness,用新的有期限 context调用 HTTP Shutdown,等待已进入的业务请求完成;共享数据库及后台组件此时仍可用。
- HTTP 结束后取消发送器和 Worker,等待当前发送确认、在途任务续租及结果上报完成。
- 最后取消结果消费器,等待处理器/ACK 有界收尾,随后关闭共享数据库连接池。
发送器和 Worker 收尾各20s,结果消费器10s;示例每个关闭阶段最大25s,进程平台的终止宽限应大于三个阶段之和,例如90s。超时返回错误,不声称工作全部完成。忽略 context 的自定义处理器不能被 Go 强杀;实际业务必须遵守取消,并为外部副作用建立幂等/查询核对。
消费器取消会取消其处理器;事务未确认时不会 ACK,重启后允许重投。关闭前并不要求全局订阅积压归零,进程退出后未消费结果仍由持久订阅保存。示例 Inbox 按事件 ID 去重,按任务版本防止迟到覆盖,提交回执未知返回错误,不能提前确认交付。
readiness 表示能安全接受本地持久请求,不要求 Orbit 或 WebSocket 始终在线;Orbit暂时不可用时任务留在 Outbox。若某类业务必须同步依赖远端,应单独增加路由级就绪门槛。监控仍需覆盖 Outbox 积压/最老待发年龄/REVIEW、Worker重连和事件积压,不能只看健康接口。
5. 故障场景
| 场景 | 预期行为 |
|---|---|
| Orbit 暂时不可用 | HTTP仍可保存业务事务,Outbox有界退避;受理超出24h/100次进入REVIEW |
| HTTP提交响应丢失 | 客户端按原ID与内容核对,业务行与待发请求不重复 |
| Orbit受理响应丢失 | 发送器保留原提交键重新核对,不能换键 |
| 本地业务/确认COMMIT不明 | 保留原身份,不把失败返回当成数据库回滚证明 |
| 进程SIGKILL | 内存丢失;重启用相同数据库、目标、凭据和订阅ID恢复,已有发送租约自然到期 |
| 正常SIGTERM | 按上述顺序收尾;成功退出码0,异常/超时非零 |
| 结果重复投递 | 同业务事务Inbox去重,提交完成后才ACK |
不要把示例的纯计算 RecoveryIdempotent 直接用于支付、发送通知等外部效果。真实业务要实现自己的幂等或查询核对。任务预算、24h发送期限、结果交付预算保持有限,长时间停机可能需要人工核查。
6. 验证与接入现有框架
# 仓库根目录:脚本自行创建、销毁专用MySQL,不操作本地开发库。
make adapters-check
make test vet build docs-check sdk-check
make integration
验证记录区分纯生命周期测试和真实数据库/平台/进程证据。Gin、Echo、go-zero 等服务可复用本例的组件装配和关闭顺序,将业务路由挂到原路由器,统一接入原生命周期;不需要另起一套业务 HTTP 服务,也不在每次请求里创建 Worker。