Ice 规则引擎详细指南
Ice 规则引擎完整功能说明,助您深度掌握可视化业务编排
规则引擎节点开发
Ice 规则引擎采用节点化设计,每个节点代表一个独立的业务逻辑单元。通过组合不同类型的节点,可以实现复杂的业务规则编排。
三种叶子节点类型
Ice 规则引擎提供三种叶子节点类型,对应不同的业务场景:
1. Flow 节点 - 流程控制
- 用途:控制业务流转的规则节点
- 场景:判断条件、过滤规则、权限校验等
- 返回值:明确的 true(通过)或 false(不通过)
- 示例:用户等级判断、金额范围校验、时间条件过滤
2. Result 节点 - 结果处理
- 用途:执行具体业务操作的规则节点
- 场景:发放奖励、扣减库存、发送通知等
- 返回值:true(执行成功)或 false(执行失败)
- 示例:优惠券发放、积分赠送、余额充值
3. None 节点 - 辅助操作
- 用途:不影响业务流转的辅助节点
- 场景:数据查询、日志记录、信息装配等
- 返回值:无返回值(none)
- 示例:用户信息查询、埋点上报、缓存预热
节点注解
Ice 2.0 提供了节点注解来丰富节点的元信息:
@IceNode(
name = "金额发放",
desc = "向用户发放指定金额",
order = 10, // 排序优先级,值越小越靠前
alias = {"amount_result"} // 别名,用于多语言兼容
)
public class AmountResult extends BaseLeafRoamResult {
// ...
}
字段注解
@Data
public class AmountResult extends BaseLeafRoamResult {
@IceField(name = "用户Key", desc = "roam中获取用户ID的key")
private String key;
@IceField(name = "发放金额", desc = "发放的金额数值", type = "double")
private double value;
@IceIgnore // 忽略此字段,不在配置界面展示
private String internalField;
}
别名 (Alias) - 多语言兼容
当使用 Go/Python SDK 配置的规则需要被 Java SDK 执行时,可以通过 alias 实现类名映射:
// Java 类可以响应 confName 为 "score_flow" 的配置
@IceNode(alias = {"score_flow", "ScoreFlow"})
public class ScoreFlow extends BaseLeafRoamFlow {
// ...
}
节点基类选择
Ice 规则引擎提供三种基类供开发者继承,根据入参类型选择:
- BaseLeaf* - 使用 IceContext 作为方法入参,需要实现 do* 方法
- BaseLeafPack* - 使用 IcePack 作为方法入参,需要实现 doPack* 方法
- BaseLeafRoam* - 使用 IceRoam 作为方法入参,需要实现 doRoam* 方法
例:
/**
* 发放余额节点
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class AmountResult extends BaseLeafRoamResult { //需要发放的uid从roam获取,因此继承BaseLeafRoamResult即可
@Resource
private SendService sendService; //如果是spring应用,可以直接使用springbean,非Spring应用请初始化IceBeanUtils的IceBeanFactory用于装配自己需要的实例
private String key; //可供配置的uidKey
private double value; //可供配置的发放余额值value
@Override
protected boolean doRoamResult(IceRoam roam) { //需要实现doRoamResult(即自己的业务内容)
Integer uid = roam.getMulti(key); //从roam里拿到需要发放余额的用户uid
if (uid == null || value <= 0) {
return false;
}
boolean res = sendService.sendAmount(uid, value); //调用第三方接口发放余额(给uid发value值的余额)
roam.put("SEND_AMOUNT", res); //业务中想把发放结果再放回roam,也许供后续使用
return res; //返回发放结果
}
}
执行Ice
组装IcePack
pack为执行ice前需要组装的包裹
- iceId 需要触发的规则id,对应配置后台的ID,iceId只能触发一条配置的规则
- scene 需要触发的场景,所有订阅该场景的规则都会触发
- confId 以任意节点ID为root触发规则
- requestTime 请求时间,默认System.currentTimeMillis()
- roam 放入执行规则所需的参数等信息
- traceId 链路ID,默认自动生成
- debug 日志打印,参考DebugEnum 最终执行的debug为 handler.debug|pack.debug
调用Ice方法
- void syncProcess(IcePack pack) 同步执行
- List<Future<IceContext>> asyncProcess(IcePack pack) 异步执行并返回futures
业务中可能会往roam中又放入了执行结果等信息,在执行结束后可以从roam里获得想要的数据。
IceRoam
roam提供了节点执行所需的数据源或存放执行结果供后续执行使用,roam为ConcurrentHashMap的扩展。
- put/get 重写了ConcurrentHashMap的put/get,忽略了ConcurrentHashMap的key/value空指针异常
- putValue/getValue 忽略了类型匹配校验,节约了强转操作,使用时注意类型是否匹配
- putMulti/getMulti 使用"."分隔并构建拥有层级关系型的数据结构
- getUnion 如果参数是以"@"开头的字符串会去roam里拿数据并返回,否则返回参数自身
roam.putValue("a", 1); //{"a":1}
roam.getValue("a"); //1
roam.putMulti("b.c", 2); //{"a":1,"b":{"c":2}}
roam.putMulti("b.d", 3); //{"a":1,"b":{"c":2,"d":3}}
roam.getMutli("b"); //{"c":2,"d":3}
roam.getMutli("b.c"); //2
roam.getUnion("a"); //"a"
roam.getUnion("@a"); //1
roam.getUnion(1); //1
roam.put("e", "@a");
roam.getUnion("@e");//1
roam.put("e", "a");
roam.getUnion("@e");//"a"
后台配置
视频地址:https://www.bilibili.com/video/BV1Q34y1R7KF
app
app用于区分不同的应用,如app=1的ice-test,在ice-test启动时,会根据配置从文件系统拉取app为1的所有配置并初始化
新增ice
- ID iceId 可以通过iceId触发
- 名称 描述
- 场景 订阅的场景,可用","分隔订阅多个场景,当任一场景发生时触发
- 配置ID ice树的root节点Id
- Debug 日志打印,参考DebugEnum,将需要打印的内容对应的值进行累加,如想要打印IN_PACK(执行ice前的Pack-1)和PROCESS(执行过程-2)
- 操作
- 编辑 编辑ice
- 查看详情 查看详细节点配置
- 备份 备份配置
- 备份历史 可以从历史备份中恢复
- 导出 导出当前配置(包含未发布的变更)
配置节点
单击节点,弹出相关操作
- 查看/编辑节点
- 添加子节点 仅限关系关系节点
- 添加前置节点 添加前置执行节点
- 转换节点 可将当前节点转换成任意节点
- 上下移节点 移动节点
- 删除本节点 节点的删除为软删除,只是断开连接,并未物理删除,可通过添加节点ID的方式添加回来
其他配置:
- confName 叶子节点的类名,第一次添加的叶子节点需要手动输入全类名,并会有校验该类是否在client中真实存在,添加叶子节点时需要有一个运行中的client用于校验
- 节点ID 通过节点ID的方式添加子节点即为对象级别复用性的体现,ID相同的节点在内存中只会有一份,更改其中之一其余的都会一起变化
- 记录 节点的debug仅用于在processInfo中是否展现
- 反转 反转节点,如节点本该返回false,反转后会返回true,如此ContainsFlow等节点类就不需要再额外开发一个NotContainsFlow
发布、清除、导入、导出
- 发布 所有的变更只有在发布后才会真实的热更新到client中,未发布的变更节点会有"^"标识
- 清除 清除所有变更,恢复到上次发布版本
- 导入 导入配置
- 导出 导出当前配置(包含未发布的变更)
- 实例选择
- Server server配置,目前仅Server模式下支持编辑操作
- Client:host/app/uniqueId 对应client中真实的配置展现,仅支持查看操作
存储架构(2.0新特性)
Ice 2.0 采用文件系统存储,完全移除了对MySQL的依赖。
目录结构
ice-data/
├── apps/ # 应用配置
│ ├── _id.txt # 应用ID生成器
│ └── {app}.json # 应用配置文件
├── clients/ # 客户端信息
│ └── {app}/ # 按应用分组
│ ├── {address}.json # 客户端心跳文件
│ └── _latest.json # 最新客户端信息(O(1)读取)
└── {app}/ # 应用规则配置
├── version.txt # 当前版本号
├── _base_id.txt # Base ID生成器
├── _conf_id.txt # Conf ID生成器
├── _push_id.txt # Push ID生成器
├── bases/ # Base规则配置
│ └── {baseId}.json
├── confs/ # Conf节点配置
│ └── {confId}.json
├── updates/ # 待发布的变更
│ └── {iceId}/
│ └── {confId}.json
├── versions/ # 版本增量更新
│ └── {version}_upd.json
└── history/ # 发布历史
└── {pushId}.json
版本同步机制
Server 发布配置:
- 更新
bases/和confs/目录下的配置文件 - 生成增量更新文件写入
versions/{version}_upd.json - 更新
version.txt版本号
- 更新
Client 轮询更新:
- 定期检查
version.txt版本号 - 发现新版本后读取增量更新文件
- 如增量文件缺失则进行全量加载
- 定期检查
心跳机制:
- Client 定期写入心跳文件到
clients/{app}/ - Server 通过心跳文件判断 Client 是否在线
- 超时未更新的 Client 被标记为离线
- Client 定期写入心跳文件到
配置文件格式
所有配置以 JSON 格式存储,便于版本控制和人工审查。
Base 配置示例:
{
"id": 1,
"app": 1,
"name": "用户等级活动",
"scenes": "user_login,user_upgrade",
"confId": 1,
"status": 1,
"debug": 0,
"createAt": 1701234567890,
"updateAt": 1701234567890
}
Conf 配置示例:
{
"id": 1,
"app": 1,
"confId": 1,
"type": 5,
"confName": "com.ice.test.flow.LevelFlow",
"confField": "{\"level\": 5}",
"sonIds": "2,3",
"status": 1,
"createAt": 1701234567890,
"updateAt": 1701234567890
}
多实例部署
共享存储方案
多个 Server/Client 实例需要共享同一个存储目录:
# docker-compose.yml 示例
services:
ice-server-1:
image: waitmoon/ice-server:2.0.0
ports:
- "8121:8121"
volumes:
- /shared/ice-data:/app/ice-data # 共享存储
ice-server-2:
image: waitmoon/ice-server:2.0.0
ports:
- "8122:8121"
volumes:
- /shared/ice-data:/app/ice-data # 同一共享存储
推荐共享存储方案
- NFS:网络文件系统,适合内网环境
- 云盘:阿里云NAS、AWS EFS等云存储服务
- 分布式文件系统:GlusterFS、CephFS等
与表达式引擎结合
ice可以融合如Aviator等各种表达式引擎,简化节点配置。
例: 以Aviator为例,如果想要做一个基于Aviator的Flow节点:
@Data
@EqualsAndHashCode(callSuper = true)
public class AviatorFlow extends BaseLeafRoamFlow {
private String exp;//可供配置与热更新的Aviator表达式
private Expression compiledExpression;
@Override
protected boolean doRoamFlow(IceRoam roam) {
return (boolean) compiledExpression.execute(roam);
}
public void setExp(String exp) { //为了更好的性能,设置/更新表达式时重新编译
this.exp = exp;
this.compiledExpression = AviatorEvaluator.compile(exp, true);
}
}
节点报错处理
- 统一处理:IceErrorHandle
设置IceErrorHandle的handle实例(继承IceErrorHandle并实现handle方法),IceErrorHandle.setHandle(IceErrorHandle customHandle)改变所有的节点统一error处理;默认实现为DefaultIceErrorHandle:直接返回NodeRunStateEnum.SHUT_DOWN,不处理并终止整个流程。
- 叶子节点重写errorHandle方法
叶子节点重写NodeRunStateEnum errorHandle(IceContext cxt, Throwable t)方法,处理当前叶子节点发生的error,如果返回NodeRunStateEnum.SHUT_DOWN将会终止整个流程。叶子节点如果重写了errorHandle方法。就不会再走统一error处理,不过可以通过super.errorHandle(cxt, t)再走一遍统一处理。
- 配置处理
节点提供了iceErrorStateEnum配置,如果该配置非空,它的优先级最高,将首先使用配置作为返回值。配置处理只会影响返回值,依然会执行叶子节点重写的errorHandle方法/统一的handle处理方法。
Server 配置详解
Ice Server 的完整配置项:
server:
port: 8121
ice:
storage:
# 文件存储路径
path: ./ice-data
# 客户端失活超时时间(秒)
# 超过此时间未更新心跳的客户端将被标记为离线
client-timeout: 60
# 版本文件保留数量
# 超过此数量的旧版本增量文件将被清理
version-retention: 1000
Client 配置详解
Ice Client 的完整配置项:
ice:
# 应用ID(必填)
app: 1
storage:
# 文件存储路径(必填,需与Server共享)
path: ./ice-data
# 叶子节点扫描包路径
# 多个包用逗号分隔,不配置则扫描全部(较慢)
scan: com.ice.test
# 版本轮询间隔(秒),默认5秒
poll-interval: 5
# 心跳更新间隔(秒),默认10秒
heartbeat-interval: 10
pool:
# 线程池并行度,默认-1使用ForkJoinPool默认配置
parallelism: -1