第 11 章 / 共 51 章
卷二 · 配置与服务装配
多服务与拓扑:一个进程,一整张服务地图
services[] 不必只有一个。真实部署常常是"HTTP 站群 + WebSocket 推送 + TCP 网关 + UDP 心跳"的混布——它们共享一个引擎、一套脚本能力与一次优雅停机。
本章导读
站位:从"配好一个服务"到"理解整张拓扑"。本章还引入运行期拓扑查询 API——它是第 40 章管理面插件的原料。
11.1 引入
多服务部署的朴素做法是"一类服务一个进程",代价是进程 sprawl、端口规划混乱、日志分散。XServer 的拓扑模型是进程内多 server:一个 xs.json 描述全部端点,一个进程承载全部服务——共享引擎线程池、共享脚本运行时能力、统一优雅停机。生产环境 home 仓库就是这个形态:一个 XServer 进程,14 个 vhost、http 与静态站混布。
11.2 一个进程,多种协议
混合拓扑示例
{
"engine": { "workers": 8 },
"services": [
{ "class": "http", "name": "web", "port": 80, "tls": true, "port_tls": 443,
"host_default": { "name": "app", "path": "wwwroot", "devlang": "c", "devfile": "main.c" } },
{ "class": "ws", "name": "push", "port": 8081,
"host_default": { "name": "app", "devlang": "c", "devfile": "push_main.c" } },
{ "class": "tcp", "name": "gw", "port": 9000, "tls": true, "port_tls": 9443,
"devlang": "c", "devfile": "gw_main.c" },
{ "class": "udp", "name": "beat", "port": 9010,
"devlang": "c", "devfile": "beat_main.c" }
]
}
刚才发生了什么:四个服务四份脚本,装配时全部候选先编译成功才进入 bind——任何一个编译失败,整个进程拒绝启动(与热重载的"先编译后换代"同一条纪律)。每个服务独立换代(reload 以 server 为发布单元),互不影响。
11.3 engine.workers
- 进程级唯一旋钮:0–1024,0 = xrt 默认(demo 实测本机 12)
- 所有 server 的回调共享这一个线程池——HTTP 请求、WS 消息、TCP 数据、UDP 报文都在 workers 上执行
- 热重载不可变:变更 workers 的 reload 意图被拒绝(线程数是进程级事实)
- 容量估算:回调是短平快的事件处理,workers ≈ CPU 核数是合理起点;有长阻塞任务应放独立线程(第 41 章)而非加大 workers
11.4 运行期拓扑查询
跨服务协作:从 ws 服务找到 http 服务
/* 在 push 服务的脚本里枚举全部服务 */
static bool PrintTopo(XS_ServerInfo* pServer, void* pUserData)
{
printf("- %s (%s) :%u%s\n", pServer->Name, pServer->Class,
pServer->Port, pServer->TLS ? " +tls" : "");
return true;
}
xsEnumServers(PrintTopo, NULL);
/* 按名取一个 retained lease(必须 Release)*/
XS_ServerInfo* pWeb = xsServerFind("web");
if ( pWeb != NULL ) {
XS_HostInfo* pHost = xsHostFind(pWeb, "app");
/* ... 跨服务读取拓扑信息 ... */
xsServerRelease(pWeb);
}
拓扑读侧由 RWLock 保护(读多写少),Accept 路径每次进读侧——查询本身是低开销的。枚举参数回调内借用、lease 纪律见 参考页。
11.5 契约
- 装配线:多服务全部候选编译成功才 bind;任一失败整体拒绝启动。
- 换代线:reload 以单个 server 为单元;xsReloadAllSubmit 是整批事务(全成或全回滚)。
- 查询线:xsServerFind 返回 retained lease;枚举参数回调内借用,保存前 Retain。
常见错误
试图热改 workers
- 症状
- reload 意图 FAILED,Message 指出 workers 不可变。
- 原因
- 线程池是进程级资源,运行期重建会改变所有回调的并发语义——被明确拒绝而非冒险执行。
跨服务持有裸指针跨代
- 症状
- 重载 web 服务后,push 服务里缓存的 XS_ServerInfo* 变成野指针。
- 原因
- 枚举回调里的指针只借用一轮;跨代/跨服务保存必须 Retain 成 lease。
编程练习
基础四协议混布
把 11.2 的拓扑跑起来(脚本可各自空导出),对照启动日志画出拓扑表。
验收:日志含四个服务的 server/bound 行,端口与配置一致。
进阶拓扑导出路由
在 web 服务加 /topo 路由,用 xsEnumServers 输出 JSON 拓扑。
验收:输出含全部服务的 name/class/port/tls 字段。
挑战单服务故障实验
故意让 gw 服务的脚本编译失败再启动,观察整体行为;然后修复 gw、故意改坏 web 的脚本再启动。
验收:能说出"任一候选失败"时的启动日志形态,并解释为什么不允许"三个好的先跑"。
小结
| 要点 | 结论 |
|---|---|
| 拓扑 | 一进程多 server 混布五类协议,共享引擎与停机 |
| 装配 | 全部候选先编译成功才 bind,任一失败整体拒绝 |
| workers | 进程级、回调共享、热重载不可变;≈CPU 核数起步 |
| 查询 | xsServerFind(lease)/ xsEnumServers(借用) |
本章涉及的 API 参考
xrt 延伸阅读
- xrt 参考 · executor — workers 线程池的工作窃取模型