Skip to content

配置运行时语义 ​

本文档是 Shield 最小 runtime 配置入口 的权威来源。官方可选模块可以定义自己的配置段,但不能把配置项反向塞进最小 runtime schema。

当前状态:本文冻结 Phase 1 配置契约;源码已实现启动期最小验证,旧字段仍需要按本文逐步清理。

配置原则 ​

  • YAML 只做声明式绑定,不承载业务逻辑。
  • 最小 runtime schema 只覆盖单节点 runtime 必需能力。
  • 配置驱动 Lua service、网络监听、插件实例/binding 和日志;每个配置段必须有明确 owner。(bootstrap.timeout 等启动期超时键未实现,见下文 schema 说明。)
  • 不在 core 中提供服务发现、Prometheus、健康检查、DI、注解或条件装配配置;插件系统使用独立 plugins 子树。
  • optional module 的配置由对应模块自己验证;未启用模块时,出现对应 optional 配置段必须启动失败,不能由 core 静默忽略。

Phase 1 Schema ​

配置段 owner:

配置段owner
appshield_bootstrap
logshield_log
luashield_lua
actorsshield_bootstrap + shield_core + shield_lua
actors[].networkshield_net
netshield_net
pluginsshield_plugin
bootstrap / shutdownshield_bootstrap
json
{
  "app": {
    "name": "my_game",
    "version": "1.0.0"
  },
  "log": {
    "level": "info",
    "console": true,
    "file": {
      "enabled": false,
      "path": "logs/shield.log",
      "max_size_mb": 100,
      "max_files": 10
    }
  },
  "lua": {
    "vm": {
      "mode": "per_service"
    },
    "sandbox": {
      "allow_os": false,
      "allow_io": false
    },
    "script_path": "scripts",
    "module_path": "scripts/?.lua;scripts/?/init.lua",
    "cache": {
      "enabled": true,
      "max_size": 100,
      "ttl_seconds": 0
    }
  },
  "actors": [
    {
      "name": "gateway",
      "script": "scripts/auth.lua",
      "instances": 1,
      "required": true,
      "network": {
        "tcp": "0.0.0.0:8001",
        "max_connections": 10000,
        "max_frame_size": 65536
      },
      "options": {
        "route_table": "login_routes"
      },
      "restart": {
        "policy": "on-failure"
      }
    },
    {
      "name": "player",
      "script": "scripts/player.lua",
      "instances": 0
    }
  ],
  "plugins": {
    "directory": "./plugins",
    "instances": [
      {
        "id": "db.main",
        "package": "database.mysql",
        "required": true,
        "config": {
          "host": "127.0.0.1",
          "port": 3306,
          "database": "game",
          "user": "root",
          "password": "${DB_PASSWORD:}",
          "pool_size": 8,
          "connect_timeout_ms": 5000,
          "query_timeout_ms": 30000
        }
      },
      {
        "id": "cache.session",
        "package": "cache.redis",
        "required": true,
        "config": {
          "host": "127.0.0.1",
          "port": 6379,
          "db": 0,
          "password": "${REDIS_PASSWORD:}",
          "pool_size": 8,
          "connect_timeout_ms": 5000,
          "command_timeout_ms": 5000
        }
      }
    ],
    "bindings": {
      "database.default": "db.main",
      "cache.session": "cache.session"
    }
  },
  "bootstrap": {
    "timeout": {
      "config_load": 5000,
      "log_init": 5000,
      "core_init": 10000,
      "plugin_init": 30000,
      "net_init": 10000,
      "script_init": 10000,
      "service_spawn": 60000
    },
    "retry": {
      "plugins": {
        "max_retries": 3,
        "delay": 5000
      }
    }
  },
  "shutdown": {
    "timeout": {
      "service_drain": 30000,
      "service_stop": 10000,
      "plugin_shutdown": 10000,
      "total": 60000
    }
  }
}

上面 bootstrap 段(timeout.* / retry.*)未实现:bootstrap 没有阶段超时和插件重试机制,这些键没有任何消费者,配置中出现不会生效(见 启动流程)。

以下 schema 键未实现(已从示例中移除;无消费者,配置中出现不会生效,不应使用):

  • log.format——logger 输出为单一固定文本格式(<timestamp_ms> [LEVEL] <logger>: <msg>,见 src/log/logger.cpp 的 format_record),无可配置的格式项;
  • log.file.rotation——轮转只按大小触发,由 log.file.max_size_mb(写满即轮转,log.file.max_files 控制保留份数)驱动;
  • lua.vm.max_vms / lua.vm.max_memory_mb——lua.vm 下当前只有 mode 被读取校验;
  • actors[].transport、actors[].limits.*——完全未被读取;
  • actors[].restart.max_retries / initial_delay / max_delay / multiplier——重启配置中当前只有 policy 被读取校验。

shutdown.timeout 语义:

字段说明
service_drainruntime shutdown drain 阶段预算(整段共享);按 spawn 逆序调用 service on_shutdown(ctx),预算耗尽后剩余 hook 跳过并记 WARN;<= 0 关闭该阶段
service_stopfinal stop 阶段预算;用于调用 service on_exit(reason) 并释放 VM
plugin_shutdown插件 instance shutdown 预算;插件关闭在 service drain/stop 之后
total整体 shutdown 最大预算,必须覆盖所有分段

Actor 配置 ​

字段必填说明
name是actor 类型和默认 service name 前缀
script是Lua module 文件
instances否启动实例数;0 表示只允许动态 spawn
required否启动失败是否导致 runtime 启动失败,默认 true
network否gateway 类 service 的 listener 配置
options否传给 on_init(args).config 的业务配置
restart否服务异常退出后的重启策略;当前只有 policy 被读取校验(always/on-failure/never),重试次数与退避字段未实现
limits否未实现——当前无任何消费者,配置不生效

actors[].network 声明的是 Gateway 边界 owner,不是暴露客户端业务回调的 Lua service。它持有 listener、live session 和 session 的单一 target 绑定;业务 Lua 不接收通用消息回调(入站只经 spawn 期编译的 RPC 绑定 handler(ctx, client, request) 到达 target),也不获得裸连接句柄。第一版 TCP listener 仍要求 instances: 1,确保每个 live session 只有一个 Gateway owner。

actors[].network.protocol 绑定 session 固定使用的 ProtocolProfile,只描述 wire 形态(envelope、body codec、限制)。客户端 RPC 路由的唯一静态来源是 actors[].rpc.routes:每个 actor 声明自己的 descriptor 条目(id/name/binding/direction/owner_service/requires_auth/action/lazy_decode 与 schema 元数据,字段契约见 protocol-routing-design.md)。config 校验单 actor 的字段与唯一性;bootstrap 把所有 actor 的条目合并为全局 descriptor 表(跨 actor 的 id/name 冲突导致启动失败)并注入 listener pipeline;每个 Lua service 在 spawn 时只编译 owner_service == 自身 的条目,c2s/bidi 的 binding 解析不到模块函数即 spawn 失败(handler_missing)。network.protocol.routes 内联路由已删除,配置中出现即报错(pre-1.0 不做兼容读)。

actors[].network.max_frame_size 是 listener 级默认单帧上限。未显式设置 protocol.envelope.max_frame_size 时,profile 继承该值;显式设置时以 envelope 值为准。两级均未设置时不是无限:取传输层默认上限 16 MiB(kDefaultMaxFrameSize,见 include/shield/transport/protocol.hpp)——异常/恶意的长度前缀不应能驱动服务端按其分配缓冲;业务确实需要更大单帧时显式配置。

协议配置只包含固定 wire/profile 部件:

  • envelope:frame 边界与 header route_id 读取/写入规则;
  • body:session 固定的 codec 与 provider;
  • routing:遗留的 route 来源开关(header/body),仅服务过渡期入口,后续里程碑随 body route 分发一并移除。

Gateway 只从 header 读取 route_id,在 descriptor 校验表中确认其存在与方向,然后把消息投递给 session 的当前单一 target。普通 RPC body 的 decode 必须在目标 Service actor 中进行。

yaml
actors:
  - name: gateway
    script: scripts/gateway_control.lua
    instances: 1
    network:
      tcp: "0.0.0.0:8001"
      max_frame_size: 65536
      protocol:
        name: game.v1
        envelope:
          type: idlen
          route_id_bytes: 4
          length_bytes: 4
          endian: big
          max_frame_size: 65536
        body:
          codec: protobuf
          provider: protocol.protobuf
    rpc:
      routes:
        - id: 1001
          name: login
          binding: do_login
          direction: c2s
          requires_auth: false
        - id: 2001
          name: login_result
          binding: push_result
          direction: s2c

预登录 RPC 在 descriptor 中声明 requires_auth: false;认证成功后由认证服务通过 shield.client.bind 把 session 的 target 原子切换到玩家服务。room、scene、map 等动态路由由玩家服务私有状态管理,不能写成静态 actor 配置。

actors[].script 可以是绝对路径,也可以是相对路径。相对路径解析规则:

  1. 如果是绝对路径,直接使用
  2. 如果路径相对于当前工作目录存在文件,直接使用
  3. 如果 actor 配置指定了 source_dir,尝试从该目录解析
  4. 如果全局配置指定了 lua.script_path,尝试从该路径解析
  5. 如果以上都不存在,使用原始路径(可能导致启动失败)

Lua 模块加载路径

lua.module_path 配置项用于设置 Lua 的 package.path,控制 require() 函数的模块搜索路径:

  • 默认值:"scripts/?.lua;scripts/?/init.lua"
  • 支持多个路径,用分号分隔
  • ? 会被替换为模块名

示例:

json
{
  "lua": {
    "script_path": "scripts",
    "module_path": "scripts/?.lua;scripts/?/init.lua;libs/?.lua"
  }
}

这样 require("utils.helper") 会依次尝试:

  • scripts/utils/helper.lua
  • scripts/utils/helper/init.lua
  • libs/utils/helper.lua

Lua 脚本缓存

lua.cache 配置项控制 Lua 脚本的缓存行为,可以显著提升重复加载相同脚本的性能:

字段默认值说明
enabledtrue是否启用缓存
max_size100最大缓存文件数
ttl_seconds0缓存过期时间(秒),0 表示永不过期

缓存机制:

  • 缓存基于文件路径和修改时间
  • 文件修改后缓存自动失效
  • 使用 LRU 策略淘汰旧缓存
  • 缓存存储源代码,每次使用时重新编译

环境差异:

  • 开发环境:建议 enabled: false 以支持热重载
  • 生产环境:建议 enabled: true 以提升性能

Plugins 配置 ​

后端能力(SQL、文档、缓存、队列、排行榜、监控等)统一走插件系统 v1。认证/JWT 属于业务层策略,不作为 Shield 官方插件发布。最小 runtime 配置层只识别 plugins 子树并把它交给 PluginHost;各实例的 config 由对应插件 manifest.yaml 的 config_schema 校验。

字段必填说明
plugins.directory否插件包目录,默认由 bootstrap 决定
plugins.instances否显式声明要启动的 plugin instance
plugins.instances[].id是instance id,本配置内唯一
plugins.instances[].package是package id,必须能在 catalog 中找到
plugins.instances[].required否默认 true;required 失败会中断启动
plugins.instances[].config否传给插件的实例配置,由插件 manifest schema 拥有
plugins.bindings否logical_name: instance_id 显式 binding 表

规则:

  • core 不解释数据库、Redis 或其他后端的字段;例如 pool_size、host、database 都属于具体插件实例配置。
  • 数据访问 Lua API 使用插件 namespace 和 binding 逻辑名,例如 shield.database.mysql("database.default")。
  • 缺少对应 binding、目标实例未启动或插件未启用时,插件 Lua API 返回 nil, { code = "module_unavailable" } 或等价错误对象。
  • 连接池归插件 instance 自治,host 不创建全局 DB/Redis 连接池。
  • 旧顶层 database / redis 配置段已废弃,不属于 Phase 1 schema。

验证规则 ​

字段规则
app.name必填,1-64 字符
log.leveldebug、info、warn、error
lua.vm.mode当前只允许 per_service
lua.script_path可选,默认 "scripts"
lua.module_path可选,默认 "scripts/?.lua;scripts/?/init.lua"
lua.cache.enabled可选,默认 true
lua.cache.max_size可选,默认 100,范围 1-10000
lua.cache.ttl_seconds可选,默认 0,范围 0-86400
lua.sandbox.allow_os / lua.sandbox.allow_io可选布尔,缺省 true(fail-open:未配置即开放 os/io 标准库);生产环境必须显式声明为 false(仓库 config/app.yaml 即如此)
actors至少一个 actor
actors[].name必填,本配置内唯一
actors[].script必填,文件必须存在
actors[].instances>= 0
actors[].restart.policyalways、on-failure、never
actors[].network.*监听地址必须是 host:port
actors[].network.tcpPhase 1 要求 instances == 1;必须同时声明 network.protocol,否则拒绝启动
actors[].network.max_connections / max_connections_per_ip可选,范围 1-10000000
actors[].network.max_frame_size可选,范围 1-16777216(16 MiB)
actors[].network.max_session_send_queue可选,范围 0-1000000
actors[].network.read_idle_timeout可选,范围 0-86400000(毫秒;0 关闭读空闲检测)
actors[].network.rate_limit.*messages_per_second、burst 均为 0-1000000;必须为 map
actors[].network.blocklist.deny必须为数组;每个条目启动期按地址/CIDR 解析校验,非法条目拒绝启动
actors[].network.tls必须为 map;enabled 可选布尔,缺省 false;enabled: true 时 cert_file / key_file 必填非空,handshake_timeout_ms 范围 1-600000,缺省 10000
actors[].network.udp/kcp/websocketPhase 1 拒绝启动;这些 transport 属于 deferred extension
actors[].rpc.routes存在时必须是数组,每项必须是 map:id 必填整数且 >= 1、单 actor 内唯一;binding 必填且非空;direction 限定 c2s/client_to_server/s2c/server_to_client/bidi/bidirectional;action 限定 decode_local/forward_raw/drop;requires_auth / lazy_decode 出现必须是布尔;name 非空时单 actor 内唯一;出现 schema_id / response_schema 直接拒绝(已删除键)
net.threads可选,默认 0(单 I/O 线程),范围 0-64
plugins.directory可选;路径必须是字符串
plugins.instances[].id必填,本配置内唯一
plugins.instances[].package必填,必须存在于插件 catalog
plugins.instances[].configJSON-compatible map;按插件 manifest schema 校验
plugins.bindingsbinding 名唯一,目标 instance 必须存在
shutdown.timeout.total必须大于各分段 timeout

验证失败时拒绝启动,输出字段路径和原因。

net.threads 默认值评估与调优 ​

net.threads 未配置或 0 时,全部客户端面 I/O(游戏 TCP 监听、HTTP ops、 console)跑在单个 net 线程的一个 io_context 上(legacy 模式, bootstrap.cpp:空列表才退化单线程;N > 0 时开 N 线程跑同一 io_context)。

评估结论(2026-09):维持默认 0,不改默认值。 理由:

  • 产品目标是小规模开箱即用:中小规模(数百连接、无 TLS 卸载)单线程绰绰 有余,且是并发行为最可预测的形态(所有 socket 事件串行)。
  • 静默改默认值会改变所有存量部署的并发语义,收益不成比例;调优是上量后的 显式动作。
  • 业务 Lua 永远不在 net 线程执行(bridge 只做 framing + CAF 转发,handler 跑在 CAF actor 线程),所以调大 N 不改变任何业务并发语义,只增加 socket 层并行度——默认保守没有隐藏成本。

何时调大(满足其一):

  • 在线连接数持续上千,或单帧吞吐大(接近 max_frame_size 的流量);
  • 服务端做 TLS 终结(加解密占用 net 线程);
  • 抓取 /ops/metrics、console 往返在客户端高峰期明显变慢(三者共用一个 io_context,N ≥ 2 即可把 ops/console 与客户端流量隔离到不同线程)。

取值建议:2 起步;按核数 2–4;超过物理核数无收益。会话读写在 per-session strand 上串行(session.hpp),多线程 io_context::run 安全; 写队列与 idle timer 同在 strand,无跨线程额外锁。

验证:调整后压测观察 /ops/metrics(抓取延迟)与客户端 RTT;口径见 runtime-ops.md。

环境变量展开(未实现) ​

通用 ${VAR} / ${VAR:default} 展开仍为目标契约,配置加载器不会处理这些占位符,插件会拿到字面量字符串。唯一已实现的限定用途是 cluster.auth.token:由集群消费者严格解析完整 ${VAR},缺失/空值拒绝启动,不支持 default,见 服务间认证。其他键请勿使用。

规划语义:

json
{
  "plugins": {
    "instances": [{
      "id": "db.main",
      "package": "database.mysql",
      "config": {
        "host": "${DB_HOST:localhost}",
        "password": "${DB_PASSWORD:}"
      }
    }]
  }
}

规则:

  • 未设置且无 default 时,值为空字符串。
  • 环境变量展开发生在 schema 验证前。
  • 敏感字段不应在日志中输出明文。

多配置文件合并 ​

启动可以指定多个配置文件:

bash
shield --config config/app.yaml --config config/production.yaml

合并规则:

  • 后面的文件覆盖前面的 scalar 和 map 字段。
  • actors 按 name 合并;同名 actor 后者覆盖字段。
  • 不允许两个文件定义同名 actor 且 script 不同,除非后者显式设置 override: true。

当前实现状态:源码已实现 map/scalar 的后者覆盖前者以及启动期验证;actors 按 name 智能合并仍是目标规则,当前同名 actor 会在合并后的配置验证中被拒绝。

热更新 ​

配置文件热重载当前未实现(reload_config() 是直接返回 true 的空实现,没有配置文件热重载入口);下表描述各配置项的现状:

配置项热更新行为
log.level否配置文件热重载未实现(reload_config() 是返回 true 的空实现);运行期改日志级别走 console 命令 root.log.level,改完立即生效
log.console / log.file否Phase 1 需要重启
plugins.*否Phase 1 需要重启;不支持热加载/热卸载插件
actors[].instances否Phase 1 需要重启
actors[].script否需要 service 重启或未来 hot reload 机制
actors[].network否需要 listener 重建
lua.vm.*否需要重启
lua.script_path否需要重启
lua.module_path否需要重启
lua.cache.enabled否需要重启
lua.cache.max_size否需要重启
lua.cache.ttl_seconds否需要重启
bootstrap.* / shutdown.*否需要重启

全局配置推送、运维热更新、cluster-wide 配置同步属于 shield_global 或 shield_ops 后续设计,不进入 core schema。

Optional Module Schema ​

以下配置段不属于 core schema:

配置段所属模块文档
clustershield_cluster集群语义
globalshield_global全局数据
opsshield_ops运维语义
player / player_managershield_player玩家生命周期
server_managershield_server服务器状态

optional module 启用后,由该模块读取并验证自己的配置段。core bootstrap 只负责把未消费的 optional 配置快照传给已启用模块。

如果配置文件包含 optional module 配置段,但对应模块未构建或未启用,启动必须失败并输出明确错误;不能静默忽略。

删除的旧配置 ​

以下旧配置直接删除,不保留兼容:

  • plugins.enabled
  • 旧插件 v0 配置
  • middleware
  • discovery
  • metrics
  • health
  • conditions
  • annotations
  • di
  • ioc
  • event_bus

Apache License 2.0