# HTTP 服务完整接入示例 [源码工程](../../examples/http-service/README.md)将 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 是**平台库**: ```sh 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 和事件所需权限,生产可按角色拆分身份。 ```sh # 在仓库根目录保存凭据绝对路径,再进入示例。 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 | ```sh 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" ``` 首次提交: ```json {"operation_id":"原操作ID","local_committed":true,"duplicate":false} ``` 等待结果入库后查询: ```json { "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. 生命周期 [生命周期源码](../../examples/http-service/cmd/server/lifecycle.go)包含以下顺序: 1. 加载配置、连接业务库、核验表、构造 SDK 组件,再创建 HTTP 监听。任何构造/监听失败都关闭已经持有的资源;不会先启动失去宿主管理的 Worker。 2. 启动 HTTP、发送器、Worker 和结果消费器;各组件使用独立的宿主 context,进程信号只触发统一关闭流程。 3. HTTP 监听失败或任何后台组件不可恢复退出,撤销 readiness,进入同一关闭路径,错误返回 main 并以非零状态退出。 4. 收到 SIGTERM/SIGINT 后撤销 readiness,用**新的有期限 context**调用 HTTP Shutdown,等待已进入的业务请求完成;共享数据库及后台组件此时仍可用。 5. HTTP 结束后取消发送器和 Worker,等待当前发送确认、在途任务续租及结果上报完成。 6. 最后取消结果消费器,等待处理器/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. 验证与接入现有框架 ```sh # 仓库根目录:脚本自行创建、销毁专用MySQL,不操作本地开发库。 make adapters-check make test vet build docs-check sdk-check make integration ``` [验证记录](../reports/outbox-http-integration.md)区分纯生命周期测试和真实数据库/平台/进程证据。Gin、Echo、go-zero 等服务可复用本例的组件装配和关闭顺序,将业务路由挂到原路由器,统一接入原生命周期;不需要另起一套业务 HTTP 服务,也不在每次请求里创建 Worker。