技术博客

风控参数的热更新:不打断交易的规则下发

第 67 / 141 篇 · 2026-06-02 · secfin · 风控配置管理高可用

参数系统是被所有系统共用的隐形基础设施:涨跌停价格每日一变,费率偶尔调,黑名单和限售名单盘中随时可能来监管指令。更新的需求随时发生,而交易不能停——集中交易和内存撮合柜台重启一次的代价是清空订单簿和所有会话连接(《内存撮合 vs 数据库撮合》讲过为什么状态在内存)。所以参数下发必须做到:不停机、不丢状态、全节点一致生效。这篇讲这套热更新机制的设计。

为什么不能"改库 + 通知重启"

朴素的参数管理是改数据库,节点定期读库刷新。对行情展示这类弱一致场景够用,对风控参数有三个致命伤。

一是刷新时机不可控。定时读库意味着参数变更后存在一个生效窗口,窗口内各节点生效时刻不同——客户在两秒前被黑名单拦住、两秒后同一笔单放行,这种不一致没法向监管解释。

二是半更新状态。参数往往不是单个值,而是一组(比如某证券的涨跌停价、熔断阈值、单笔限额)。节点各自读库,读到组内一半新一半旧,风控行为就是未定义的。

三是更新过程出错无回滚。新参数在库里直接改,格式错了、边界值错了,所有节点按错误的值执行,发现时成交已经发生了。

热更新的目标状态是:参数变更像数据库事务一样——原子、一致、可回滚、留痕。下面这套双缓冲模型是标准答案。

双缓冲与原子切换

节点的内存里不放一份参数,放两份:当前生效的快照 v_cur 和待加载的缓冲区 v_next。下发的完整流程:

  1. 参数管理平台把新版本参数组(带自增版本号和生效时点)发布到消息队列;
  2. 各节点消费消息,在 v_next 里构建新快照——解析、校验(字段完整、数值范围、组内引用一致)、预编译成运行结构(风控规则一般加载时编译成条件树或位图索引,运行期不解释字符串);
  3. 校验通过后在下一个安全切换点做原子切换:内存里就是一个指针交换,单条原子指令,不存在"读到一半"的状态;
  4. 切换完成后回执版本号,管理平台汇总确认全节点一致生效;任何节点校验失败则拒绝切换,全集群停留在旧版本。

这套结构和数据库 MVCC 是同一个思想:读写分离成两个版本,读者永远看到完整一致的快照。消息队列在这里的作用是解耦和审计——参数从哪台机器、什么时候、由谁发布,队列里天然有记录,补《审计日志》缺失的"下发侧"视角。

校验环节值得多说两句,因为坏参数的生产事故比参数延迟多得多。校验至少要覆盖:数值范围(费率不能超过 100%,限额不能是负数)、组内一致性(黑名单条目引用的证券代码必须存在)、与存量数据的兼容(新的单笔限额不能低于当前已存在的在途委托所需额度,否则存量委托回报时校验会莫名失败)。最后一类最容易漏:参数变更必须对"已存在的业务状态"向后兼容,或明确配套存量数据处理动作。

生效时点:立即还是下一生效边界

双缓冲解决的是"怎么下发",还有个独立的口径问题:什么时候算数。两种合法口径,混乱才是问题:

  • 立即生效:适用于黑名单、监管紧急限售。口径是"委托处理时刻的参数版本",没有争议,实现也简单。
  • 下一生效边界生效:适用于涨跌停价、费率这类有自然切换时点(每日开盘)的参数。今天结算后算出的明日涨跌停价,应该在明日第一笔委托前生效,而不是今晚改完就立刻拦夜市委托——夜市委托属于明日交易,但属于今晚的订单流,两边对"明日"的定义不同。

口径混乱的经典事故:晚上清算算出明日涨跌停价并热更新到交易节点,当晚的夜市委托(明日单)按新价校验没问题,但当晚如果还有延期交易或盘后固定价格交易,就混进了两个时点的参数。解法是把"参数生效时点"做成参数的一部分显式携带(生效时点 = 2026-10-23 09:15),节点按自己的业务时钟决定何时切换,而不是"收到即切"。

灰度、回滚与追溯

回滚在双缓冲模型里是免费的:v_cur 的旧快照保留到下一次切换,回滚就是指针拨回去加一次全量重发。真正要预先想清楚的是回滚的业务语义:新版本生效期间已经按新参数放行的委托,回滚后怎么处理?口径必须提前写死——通常是"已放行不追溯",和委托时刻快照原则一致。

追溯侧,每次参数切换要记录(版本号、全量参数摘要哈希、切换时刻、触发人),风控判定的日志里带上参数版本号。事后争议"当时为什么拒单",答案就是"按 v1073 版参数,该证券在限制名单"——可证明性和《审计日志》那篇是同一个主题。

双缓冲热更新:加载 v_next,校验后原子切换参数管理平台版本号+生效时点消息队列发布+回执交易节点v_cur 生效中风控校验读这里v_next 加载中校验+预编译原子切换校验范围:数值范围 / 组内一致性 / 存量在途兼容坏参数在 v_next 被拒绝,全集群停留旧版本回滚=指针拨回+重发;已放行委托不追溯风控日志带参数版本号,拒单原因可回查口径