规则引擎核心是条件与动作分离并结构化配置,用govaluate+gjson等组合实现动态匹配,需扁平化变量、两级分发优化性能,动作异步超时执行且支持热更新校验。

规则引擎核心不是写死逻辑,而是让条件和动作可配置
硬编码 if-else 处理设备上报数据,一旦规则变多、条件嵌套加深,维护成本会指数级上升。真正可行的思路是把「触发条件」和「执行动作」分离,并用结构化方式(如 JSON/YAML)描述规则,运行时加载解析、动态匹配。
Go 本身没有内置规则引擎库,但可以组合使用:govaluate 解析表达式、gjson 提取上报数据字段、mapstructure 绑定配置、再加一层轻量调度器。
- 设备原始消息(如 MQTT payload)应先统一解包为
map[string]interface{}或结构体,避免每条规则重复解析 JSON - 条件表达式建议限制在单层比较(
temperature > 35)、逻辑与(&&),避免支持eval类任意代码执行,防安全风险 - 动作部分不要直接写业务逻辑,而是定义动作类型(如
"send_mqtt"、"call_webhook")+ 参数,由独立 handler 执行
用 govaluate 动态计算条件表达式时要注意变量注入方式
govaluate 不支持直接传入嵌套 map,对 device.status.battery 这类路径需提前扁平化或自定义 Parameters 实现 Get 方法。
常见错误是直接传 map[string]interface{} 导致 Unknown identifier 报错——因为 govaluate 默认只认顶层 key。
- 推荐做法:用
gjson.GetBytes(payload, "device.status.battery")提前提取所有可能用到的字段,构造成扁平map[string]interface{},例如{"device_status_battery": 82} - 若必须保留嵌套结构,可实现
Parameters接口,在Get(key string)中按.分割路径并递归取值 - 表达式中禁止出现未声明变量,建议在加载规则时用
govaluate.NewEvaluableExpressionWithFunctions预检语法,失败则跳过该规则
规则匹配性能瓶颈常出现在高频设备消息 + 全量遍历
1000 条规则、每秒 500 条设备消息,朴素 for-loop 匹配会导致 CPU 持续飙高。不能靠加机器硬扛,得从匹配机制降维。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
真实场景下,90% 的规则只关心少数几个字段(如 topic、device_id、alarm_level)。利用这点做两级分发:
- 第一级:用
map[string][]*Rule按topic或device_type预分组,消息进来先查 topic 对应的规则子集 - 第二级:对子集用
govaluate计算;若仍有性能压力,可对数值型字段(如temperature)建简单区间索引([][]float64+ 二分查找) - 避免在匹配阶段做 I/O(如查数据库、调 API),所有依赖数据应在规则加载时预热进内存
动作执行必须异步且带超时控制,否则阻塞规则主线程
一个规则动作里调用外部 webhook 超时 10 秒,会拖慢后续所有规则处理。Go 的 goroutine 轻量,但没管控就会堆积泄漏。
动作执行层要封装成统一接口,强制约束行为:
- 每个动作启动独立
context.WithTimeout(ctx, 3*time.Second),超时后 cancel 并记录"action_timeout"错误 - 用带缓冲的 channel(如
chan ActionRequest)做执行队列,防止突发流量打爆内存;缓冲满时丢弃低优先级规则动作 - 动作失败不重试(规则引擎不负责可靠性),但需透出失败原因(如
"http_status_503")供运维定位
最易被忽略的是规则热更新时的动作中断——新规则生效瞬间,旧规则正在执行的动作不会自动取消。需要在 Action 执行前检查对应规则是否已被标记为 stale,否则可能产生脏数据。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










