关于 RoundWatch
RoundWatch 是一项持久化监控服务,供需要在进程退出后确认 Algorand MainNet 上特定未来 USDC 付款的调用方使用。它保存监控任务,并在之后提供匹配的链上证据。
主要功能
- 01创建精确未来付款监控
- 02持久保存监控状态与扫描进度
- 03按发送方、接收方、金额、资产和备注匹配
- 04通过监控 ID 返回持久化结果
适用场景
- 01任务退出后确认发票付款是否到账
- 02无需持续运行 Algorand 轮询进程
MCP 相关
RoundWatch 提供远程 MCP 端点,其 MCP 工具允许客户端创建和查询持久化付款监控任务。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://roundwatch-api.onrender.com/mcpclaude mcp add --transport http vladyslav-webdev-roundwatch https://roundwatch-api.onrender.com/mcp{"mcpServers":{"vladyslav-webdev-roundwatch":{"type":"http","url":"https://roundwatch-api.onrender.com/mcp"}}}在 Cursor 中打开{"servers":{"vladyslav-webdev-roundwatch":{"type":"http","url":"https://roundwatch-api.onrender.com/mcp"}}}在 VS Code 中打开{"mcpServers":{"vladyslav-webdev-roundwatch":{"type":"http","url":"https://roundwatch-api.onrender.com/mcp"}}}{"mcpServers":{"vladyslav-webdev-roundwatch":{"serverUrl":"https://roundwatch-api.onrender.com/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
4此工具无需参数。
详细介绍
RoundWatch
为自主 x402 工作流提供持久化的 Algorand USDC 支付监控。
网站 · 快速入门 · 技术指南 · 在线 API · OpenAPI · llms.txt · MCP 注册表 · 架构
RoundWatch 会监控一笔指定的、尚未产生交易 ID 的未来 Algorand 主网 USDC 支付。调用方定义预期支付,支付一次性 x402 服务费,获得持久化监控 ID 后即可退出。RoundWatch 会持久保存这项待履行义务和扫描进度,之后可提供经过验证的链上证据供调用方检索。
**生产环境:**Algorand 主网 · Circle USDC · x402 v2 · GoPlausible 结算服务 · Bazaar 元数据已上线(目录可见性正在重新审核)· 远程 MCP 端点 · 重启后可持久恢复。
解决的问题
短暂运行的 Agent、作业或服务可能需要确认:调用方停止运行后,某笔特定支付是否到账。如果可以由持久化观察器负责等待,调用方就没有必要继续运行并轮询 Algorand Indexer。
RoundWatch 允许调用方定义预期支付,通过 x402 购买一次监控,然后退出。服务会持久保存这项待履行义务,持续扫描 Algorand,并记录匹配的交易,以供之后检索。
已经运行持久化 Indexer 或订阅基础设施的后端,也可以自行实现这一功能。RoundWatch 面向不想自行维护这类运维组件的调用方。
生命周期
- 客户端提交精确的预期发送方、接收方、原子单位金额,以及可选的发票备注。
- x402 返回支付要求,客户端在本地签署服务费支付。
- GoPlausible 验证并结算服务费支付。
- 只有在确认了精确的服务费支付交易后,RoundWatch 才会启用已持久保存的监控;该交易的确认轮次会成为扫描基准。
- RoundWatch 轮询 Algorand Indexer 期间,调用方可以退出。
- 出现精确匹配的未来 USDC 资产转账时,监控状态变为
matched。 - 调用方通过
GET /v1/watch/:id读取持久化结果。
请参阅架构,了解常规、恢复和匹配流程。
请参阅运维,了解就绪状态、备份与恢复、保留策略和事故处理流程。
请参阅路线图,了解当前的产品方向和优先级。
在线生产服务
| 属性 | 当前值 |
|---|---|
| API | https://roundwatch-api.onrender.com |
| 创建监控 | POST /v1/watch |
| 读取监控 | GET /v1/watch/:id |
| 存活状态 | GET /health |
| 就绪状态 | GET /ready |
| 网络 | Algorand MainNet |
| CAIP-2 | algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8= |
| 资产 | Circle USDC,ASA 31566704 |
| 服务价格 | 0.10 USDC(100000 个原子单位) |
| 服务接收方 | EQPLN32HPLPGBCNPOZUL6BL34CTNQGT3VAAMNAJWSIZGQ5CUNXOHB634XY |
| 协调服务 | https://facilitator.goplausible.xyz |
| 托管 | Render,持久化 SQLite 存储挂载在 /data |
| 资格截止时间 | 从持久化创建时间起 30 分钟;只有在固定的关闭检查点前完整验证链上覆盖范围后,终态 expired 才成立 |
| 工作预算 | 每个监控任务最多执行 500 轮持久化后台工作;预算耗尽时终止为 indeterminate,绝不会终止为 expired |
| 未结义务容量 | 全局 50 个;每位已验证服务付款方 5 个 |
生产服务不包含也不需要钱包助记词或私钥。
Economics v1 于 2026-09-20 部署,提交为
80ed94c746f2eac4a784a3738c6dbe8306ecfa3e,价格为0.02 USDC(20000个原子单位)。2026-09-29,公开服务合约价格调整为0.10 USDC(100000个原子单位),30 分钟监控时限和 500 轮工作预算保持不变。下方历史付费凭证保留了收集各凭证时生效的价格。
API
健康状态和就绪状态
GET /health 仅用于检查存活状态。使用 GET /ready 判断持久化服务是否已准备好接收付费流量。Render 平台的健康检查必须指向 /ready;/health 必须保持为轻量级进程存活检查端点,不得用作付费流量的就绪状态信号。
GET /health
生产环境响应:
{
"status": "ok",
"purpose": "liveness",
"network": "mainnet"
}
就绪状态:
GET /ready
就绪的生产实例返回 HTTP 200:
{
"status": "ready",
"network": "mainnet",
"checks": {
"storage": true,
"backgroundWorkers": true
}
}
创建监视
POST /v1/watch
Content-Type: application/json
请求正文示例:
{
"idempotencyKey": "invoice-2026-09-16-001",
"expectedSender": "3YFZ47IAKPB4H6B7U6MXI35HCAB5E6DA47UANIHOON53J7I5SMXUSYQXQQ",
"expectedReceiver": "EQPLN32HPLPGBCNPOZUL6BL34CTNQGT3VAAMNAJWSIZGQ5CUNXOHB634XY",
"atomicAmount": "1",
"invoiceNote": "roundwatch:invoice-2026-09-16-001"
}
普通请求会收到 402 Payment Required。支持 x402 的客户端会读取付款要求,为所公布的服务付款签名,然后重试同一请求。只有在结算和持久化激活均成功后,RoundWatch 才会返回成功:
{
"watchId": "f5d2fb6f-b224-4aae-989c-87a5418fd2ae",
"workUnitBudget": 500,
"eligibilityTtlMs": 1800000,
"expiresAt": "2026-09-16T12:30:00.000Z",
"message": "The watch is returned only if x402 settlement and durable activation succeed"
}
请求字段:
| 字段 | 必填 | 规则 |
|---|---|---|
idempotencyKey | 是 | 字符串,长度为 8–128 个字符。在服务数据库中全局唯一。重复使用时返回 HTTP 409 和现有的公开监视记录,且不会激活第二个监视。 |
expectedSender | 是 | 创建新监视时必须是校验和有效、非零且长度为 58 个字符的 Algorand 地址。新监视不接受 Algorand 零地址;为了兼容旧版已付款记录,精确恢复仍有意保持宽松。 |
expectedReceiver | 是 | 校验和有效且长度为 58 个字符的 Algorand 地址。它是未来发票付款的接收方,不一定是 RoundWatch 服务的接收方。 |
atomicAmount | 是 | 正整数字符串,不能超过 JavaScript 的最大安全整数(9007199254740991)。对于 6 位小数的 USDC,1000 表示 0.001 USDC。 |
invoiceNote | 否 | 要匹配的精确 UTF-8 备注;提供时长度为 1–128 字节。 |
被监视的资产不是请求字段。服务器会根据明确配置的网络选择 USDC ASA:MainNet ASA 31566704 或 TestNet ASA 10458941。
服务器控制有效期、工作预算和准入策略。在购买前,RoundWatch 会公布 30 分钟的资格窗口和 500 轮后台工作预算。资格计时从持久化义务准备就绪时开始,早于 x402 结算完成,因此结算延迟会占用这 30 分钟窗口的一部分。调用方无法覆盖这两个限制。超过墙上时钟截止时间本身不会使监视过期;证明过期需要完成截至某个关闭检查点的链上扫描覆盖。如果先耗尽工作预算,监视会以 indeterminate 终止,并设置 terminalReason=work_budget_exhausted,绝不会错误地标记为 expired。全局最多可有 50 个未完成的义务处于开放状态,已验证服务付款方最多可有 5 个。容量耗尽时会在结算前返回 HTTP 429。
读取监视
GET /v1/watch/f5d2fb6f-b224-4aae-989c-87a5418fd2ae
响应为 { "watch": ... }。以下精简的匹配示例展示了面向使用方的稳定字段;实时记录还会在可用时公开结算和扫描元数据:
{
"watch": {
"id": "f5d2fb6f-b224-4aae-989c-87a5418fd2ae",
"state": "matched",
"expectedSender": "3YFZ47IAKPB4H6B7U6MXI35HCAB5E6DA47UANIHOON53J7I5SMXUSYQXQQ",
"expectedReceiver": "EQPLN32HPLPGBCNPOZUL6BL34CTNQGT3VAAMNAJWSIZGQ5CUNXOHB634XY",
"assetId": 31566704,
"atomicAmount": "1",
"invoiceNote": "roundwatch:invoice-2026-09-16-001",
"createdAt": "2026-09-16T12:00:00.000Z",
"expiresAt": "2026-09-16T12:30:00.000Z",
"matchedTransaction": "VZKWYELPR4HHXPXM476NRLNU4JUKAEUUD4HGHFNAHDRD5IBFB2MA",
"matchedRound": 65096073
}
}
未知 ID 会返回 HTTP 404。
监视状态
| 状态 | 含义 |
|---|---|
settlement_pending | 监视规范和确定性的服务付款标识已持久化,但尚未证实激活。协调流程可以恢复结算/激活期间崩溃的情况。 |
active | 已确认精确的服务付款交易;其确认轮次是激活基线和初始扫描游标。轮询器正在查找未来的发票付款。 |
matched | 已找到完全匹配的未来资产转账。匹配交易 ID 和确认轮次均已存储。 |
settlement_unknown | 结算未能立即产生可用的激活。settlementReconciliationTerminal=false 表示精确协调仍可能恢复;true 表示协调已达到最终的失败关闭结果,不会重试。 |
expired | 截止时间对应的完整合格链上范围已扫描至固定的关闭检查点,且未发现完全匹配项。终止记录仍可读取。 |
indeterminate | RoundWatch 在没有正向匹配或完整过期证明的情况下停止。terminalReason=work_budget_exhausted 表示已耗尽该监视的持久化工作预算;terminalReason=indexer_permanent_failure 表示轮询遇到经分类的、不可重试且针对该监视的 Indexer 拒绝。两种结果都不表示被监视的付款不存在。 |
精确匹配
只有所有已配置属性都一致时,交易才匹配:
- 发送方;
- 接收方;
- 根据网络选择的 USDC ASA;
- 原子金额;以及
- 请求中提供的发票备注(如有)。
系统只会考虑在已确认的服务付款轮次之后严格发生的、已确认的顶层直接资产转账。精确的资格边界为 confirmed-round > activationRound 和 round-time < expiresAt:同一轮次的发票不符合条件,区块时间戳恰好等于截止时间也不符合条件。发送方、接收方、ASA、原子金额和可选备注都必须匹配。内部交易、追回转账和资产清算转账明确不属于当前 RoundWatch 匹配契约,不计为发票付款。处理时间不会影响资格判定。
x402 付款与恢复
Hono 资源服务使用 x402 v2 和托管式 GoPlausible facilitator。结算前,RoundWatch 会持久化确定性的 Algorand 服务付款交易 ID,以及根据已验证的签名载荷得出的不可变接收方、ASA、原子金额、付款方、FirstValid 和 LastValid 条款。结算后,通过精确的 Indexer 查询获取已确认的服务付款轮次,该轮次既作为激活基准,也作为初始游标。
如果进程在链上结算后、激活提交前停止,协调工作线程会根据已持久化的指数退避策略查询该确切交易。只有原始不可变条款一致时,才会激活。单纯的 404 可重试;只有覆盖充分的 LastValid 之后的历史缺失搜索才能确认最终未付款。确认存在明确不匹配时则会安全失败。不带 ID 的免费恢复查询会保留监视 ID 的披露边界,同时为确切的未解决检查点返回明确的 retryable/terminal、稳定原因和后续操作元数据。
所有 Indexer 调用共用一个令牌桶/并发调度器。扫描使用有限的轮次范围,并在页面级别进行验证;每个页面和每次重试都会消耗容量。此外,每个持久化工作轮次还受到限制:活动轮询最多有四次逻辑 Indexer 请求机会,结算协调最多有三次,因此默认 500 轮的契约对后台请求的保守上限为 2,000 次。Indexer 响应正文在解析 JSON 前会限制为 8 MiB,延续令牌限制为 4 KiB,因此格式错误或恶意的上游无法分配无界的解析器/令牌内存。凡是因声明大小超限、允许的 404 或其他非 OK 状态而未读取的响应正文,都会在释放调度器槽位前明确取消。经过验证的历史扫描页面可在多轮扫描之间复用,但进程内 LRU 同时受条目数量和序列化载荷字节数限制。分页令牌保存在内存中,因此重启后会从持久化游标重新执行未完成的窗口。截止时间过后,RoundWatch 会将时间戳不早于截止时间的索引区块固定为 closingRound;只有覆盖至该轮次的完整验证数据才能产生 expired 状态。状态读取绝不会根据墙上时钟自行判定过期。
本地开发
要求:
- Node.js 24;
- 工作区声明的 pnpm 12.3.4;以及
- 仅当你有意测试需要付费的本地工作流时,才需要 TestNet 账户。
安装依赖:
pnpm install --frozen-lockfile
将 apps/server/.env.example 复制到 apps/server/.env,并配置一个公开的 TestNet 接收地址:
ROUNDWATCH_NETWORK=testnet
AVM_ADDRESS=<public TestNet receiver address>
FACILITATOR_URL=https://facilitator.goplausible.xyz
ROUNDWATCH_DB_PATH=data/roundwatch.sqlite
ROUNDWATCH_POLL_INTERVAL_MS=5000
ROUNDWATCH_RECONCILE_INTERVAL_MS=5000
ROUNDWATCH_WATCH_TTL_MS=1800000
ROUNDWATCH_MAX_OPEN_WATCHES=50
ROUNDWATCH_MAX_OPEN_WATCHES_PER_PAYER=5
ROUNDWATCH_WORK_UNIT_BUDGET=500
ROUNDWATCH_INDEXER_REQUESTS_PER_SECOND=4
ROUNDWATCH_INDEXER_BURST=4
ROUNDWATCH_INDEXER_CONCURRENCY=2
ROUNDWATCH_SCAN_ROUND_WINDOW=100
ROUNDWATCH_SCAN_PAGE_CACHE_ENTRIES=16
ROUNDWATCH_SCAN_PAGE_CACHE_BYTES=8388608
ROUNDWATCH_SCAN_QUERY_VARIANT=C
ROUNDWATCH_MIN_FREE_DISK_BYTES=67108864
PORT=4021
然后启动服务:
pnpm dev:server
免费检查:
curl -i http://localhost:4021/health
curl -i http://localhost:4021/ready
curl -i -X POST http://localhost:4021/spike/watch \
-H "content-type: application/json" \
--data '{"idempotencyKey":"local-invoice-001","expectedSender":"3YFZ47IAKPB4H6B7U6MXI35HCAB5E6DA47UANIHOON53J7I5SMXUSYQXQQ","expectedReceiver":"EQPLN32HPLPGBCNPOZUL6BL34CTNQGT3VAAMNAJWSIZGQ5CUNXOHB634XY","atomicAmount":"1"}'
就绪请求必须返回 HTTP 200,才能接纳付费监控。其依据包括缓存的 SQLite 写入/回滚探测、用于轮询器和协调器的最新功能性 Indexer 证据、工作线程卡顿检测,以及文件系统可用空间下限。在有界只读 Indexer 探测确认健康之前,服务在启动时处于未就绪状态;空客户周期无法确认服务就绪。服务未就绪时,创建监控会在 x402 验证前返回 HTTP 503。最后一个请求应在付款前返回 HTTP 402。服务默认的本地路由是 /spike/watch,而不是生产环境的 /v1/watch 路由。
付费 TestNet 实用工具使用 apps/client/.env,并在客户端进程中进行签名。切勿在本地开发中使用有资金的 MainNet 助记词,也切勿提交 .env 文件。自动化测试套件不需要钱包,也不会进行付款。
TestNet 与 MainNet 的区分
| 配置 | TestNet | MainNet |
|---|---|---|
| 选择方式 | 省略时默认为此项,或设置 ROUNDWATCH_NETWORK=testnet | 必须设置 ROUNDWATCH_NETWORK=mainnet |
| 监控路由 | /spike/watch | /v1/watch |
| USDC ASA | 10458941 | 31566704 |
| CAIP-2 | algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI= | algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8= |
| 发现标签 | roundwatch-spike-0 | x402-global-challenge |
| 公共基础 URL | 可选 | 必填,必须使用 HTTPS,且不能是环回地址 |
| 数据库路径 | 允许使用本地默认路径 | 必须显式指定绝对持久化路径 |
操作 MainNet 前,请参阅部署。
测试与 CI
运行本地验证套件:
pnpm typecheck
pnpm -C apps/server test
pnpm -C apps/client test
docker build -t roundwatch-local .
GitHub Actions 以只读仓库权限运行,按不可变修订版本固定第三方操作和 Gitleaks 镜像,扫描完整深度的历史记录,拒绝跟踪除示例文件以外的 .env 文件,从冻结的锁文件安装依赖,审计生产依赖中的 High/Critical 级安全公告,对两个工作区进行类型检查,运行服务端和 MainNet 安全测试,并构建生产镜像。Dependabot 会监控 npm/pnpm 依赖和 GitHub Actions。测试为合成测试/只读测试,不会授权 MainNet 支出。
安全模型
- 钱包签名由客户端负责;资源服务只需要其公开的接收地址。
- MainNet 启动时会验证网络、接收地址、HTTPS 端点、公共基础 URL 和持久化数据库路径。
- 服务结算会在恢复激活前根据精确的链上字段进行核对。
- 后续发票匹配必须精确,且只有存在安全的轮次游标时才会启动激活。
- SQLite 对幂等键的唯一性约束可防止重复创建监视项。
- 全局和每个付款方的事务性准入,以及共享的有限 Indexer 调度器,可限制持久化工作和外部工作;容量不足时会在结算前拒绝请求。
- MCP 和免费恢复 POST 请求在解析请求正文前,分别执行独立的匿名请求准入;MCP 版本、信封和工具参数验证遵循受支持的新旧版契约,而不采用宽松的强制类型转换。
- 绝不能提交
.env文件和钱包材料;CI 会检查已跟踪的环境文件和完整历史记录中的密钥。
状态 API 并非经过身份验证的保险库:任何知道监视 ID 的人都可以查询其公开记录。状态响应和其他特定于监视项的响应使用 Cache-Control: no-store。请勿在 invoiceNote 中放入敏感信息,也不要将 RoundWatch 元数据用作机密存储。/health 仅用于检测存活状态;在将付费流量导入服务前,请使用 /ready 检查持久化存储和后台工作器是否就绪。挑战赛版本会保留终止状态的记录,直到运维人员有意进行维护;没有公开的删除 API。有关信任边界和运维流程,请参阅 安全 和 运维。
MainNet 证明
生产流程已于 2026-09-16 成功执行:
- x402 服务结算:
OJMUUHJPZVXS6MNW4TISXXAZIHAPNYNM446DAFY35OAJOBDDOPYA,确认轮次为65095955; - 持久化监控:
7c606f02-0257-4dfd-b59c-a13b61f480f0; - 后续发票转账:
VZKWYELPR4HHXPXM476NRLNU4JUKAEUUD4HGHFNAHDRD5IBFB2MA,确认轮次为65096073; - RoundWatch 报告同一笔发票交易在轮次
65096073匹配。
独立的 MainNet Indexer 验证确认,两笔转账均使用 Circle USDC ASA 31566704,发送方和接收方均符合预期,原子单位金额分别为服务付款的 1000 和受监控发票的 1。匹配的监控在多次 Render 重新部署后仍然保留。
完成初始 MainNet 证明后,生产服务在 18e8712431bc02a904dd5a3f227b2f5a49e9f6f7 合并了更大规模的正确性与资源控制加固更新。该版本将仅依赖墙上时钟的过期机制替换为基于链上时间的资格判定和固定关闭检查点;从服务付款确切的确认轮次激活监控;通过共享调度器限制所有 Indexer 工作;增加跨监控公平且有界的处理机制;并加强结算对账和分页验证。Render 已成功部署该合并更新,免费的生产冒烟测试也返回了预期的 MainNet 健康状态、x402 402 和状态行为。
后续生产修复 d05fabaea6124ed5658dd13cf06167a885aefeb0 将无效的 Bazaar 示例接收方替换为校验和有效的 Algorand 地址,并增加了回归测试。部署后进行的外部 402 冒烟测试解码了实时的 payment-required 标头,并验证了生产 URL、MainNet 网络、exact 方案、ASA 31566704、金额 1000 以及校验和有效的 Bazaar 示例地址。这些加固版本发布后未执行第二次付费 MainNet E2E,也不应据此认为已执行。
GoPlausible Bazaar 于 2026-09-16 发现了生产资源,其 URL、网络、资产、金额、接收方、挑战标签和 settleCount: 1 均正确。当时商户排行榜条目显示 bazaar: true、challenge: true、settles: 1 和 volume: 0.001。这是历史证据,并不保证该资源会永久留在目录中。
2026-09-24 的新一轮资格验证最初报告 RoundWatch 资源不存在,但对原始目录的取证纠正了这一解析错误:GoPlausible 将确切的生产 URL 存储在顶层 resourceUrl 字段中。该资源确实存在,但其存储的付款/发现条款已过时,仍是最初的 1000 原子单位(0.001 USDC)合约。2026-09-24 新进行的一笔经授权的 0.02 USDC 结算更新了同一目录记录的 lastSeen / settleCount,却未将其存储的 accepts 金额更新为当时生效的 20000 原子单位价格。因此,在 2026-09-29 调价后,必须重新进行资格验证。
详细证据见 MainNet Readiness。
当前限制
- 当前挑战释放策略包括 30 分钟资格期限、500 轮持久化工作预算、全局最多 50 个未结义务,以及每位经过验证的服务付款方最多 5 个未结义务。这些是运营安全上限,并非商业 SLA 或最终定价/容量策略。
- 没有取消操作、SLA 或长期定价策略。
- 当前部署是单个应用实例,使用进程内轮询器/协调器和本地持久化 SQLite,并非可横向协调的工作系统。
- 每轮轮询最多为每个活动监控提供一次有界处理机会;分页在各轮之间让出控制权,FIFO Indexer 调度确保全局请求数受限。这些调优值不构成 SLA,也不代表可任意扩展。
- 通过轮询获取结果;没有 webhook 或推送通知 API。
- 任何知道监控 ID 的人都可以在无需身份验证的情况下读取监控状态。
仓库结构
.
├── apps/
│ ├── server/ # Hono API, x402 integration, SQLite, Indexer workers
│ └── client/ # TestNet utilities and guarded MainNet verification runner
├── docs/
│ ├── ARCHITECTURE.md
│ ├── DEPLOYMENT.md
│ ├── MAINNET_READINESS.md
│ ├── OPERATIONS.md
│ ├── SECURITY.md
│ ├── FAULT_INJECTION.md
│ └── ROUNDWATCH_SPIKE.md
├── .github/workflows/ci.yml
├── Dockerfile
└── README.md
历史 TestNet 开发证据保留在 RoundWatch Spike 和 Fault Injection 中。当前产品和生产环境契约见本文档以及上方链接的四份运维文档。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。