Files
orbit/docs/examples/http-service.md

130 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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。