LiteFlow LiteFlow
首页
  • v2.16.1 (当前版本)
  • What's New

    • What' s New In LiteFlow v2.16.1?
    • What' s New In LiteFlow v2.16.0?
  • 历史版本

    • v2.15.X
    • v2.13.X
    • v2.12.X
    • v2.11.X
    • v2.10.X
    • v2.9.X
    • v2.8.X
  • 升级指南

    • 2.13.0升级指南
    • 2.12.4升级指南
    • 2.12.0升级指南
    • 升级到2.9.3说明
    • 升级到2.9.X说明
    • 升级到2.8.X说明
    • 升级到2.7.X说明
AI Agent
AI Skill
IDEA 插件
  • 答疑解惑

    • 常见问题
    • 如何理解上下文这个概念?
    • Slot是一个什么样的概念?
  • 项目与社区

    • 项目介绍
    • 项目成员
    • 更新记录
    • 参与开发
    • 加入群聊
    • 谁在使用
赞助
GitHub (opens new window)

广告采用随机轮播方式显示 ❤️成为赞助商
首页
  • v2.16.1 (当前版本)
  • What's New

    • What' s New In LiteFlow v2.16.1?
    • What' s New In LiteFlow v2.16.0?
  • 历史版本

    • v2.15.X
    • v2.13.X
    • v2.12.X
    • v2.11.X
    • v2.10.X
    • v2.9.X
    • v2.8.X
  • 升级指南

    • 2.13.0升级指南
    • 2.12.4升级指南
    • 2.12.0升级指南
    • 升级到2.9.3说明
    • 升级到2.9.X说明
    • 升级到2.8.X说明
    • 升级到2.7.X说明
AI Agent
AI Skill
IDEA 插件
  • 答疑解惑

    • 常见问题
    • 如何理解上下文这个概念?
    • Slot是一个什么样的概念?
  • 项目与社区

    • 项目介绍
    • 项目成员
    • 更新记录
    • 参与开发
    • 加入群聊
    • 谁在使用
赞助
GitHub (opens new window)
  • 🍤LiteFlow简介
  • 🍓项目特性
  • 🧁环境支持

    • ☕️JDK支持度
    • 🌿Springboot支持度
    • 🌱Spring的支持度
  • 🍟快速开始(Hello world)

    • 🍄说明
    • 🌿Springboot场景安装运行
    • 🌱Spring场景安装运行
    • 🍩Solon场景安装运行
    • 🌵其他场景安装运行
  • 🍢配置项

    • 🍄说明
    • 🌿Springboot下的配置项
    • 🌱Spring下的配置项
    • 🍩Solon下的配置项
    • 🌵其他场景代码设置配置项
  • 🔗组件

    • 🛍继承式组件

      • 📎普通组件
      • ✂️选择组件
      • ⛓布尔组件
      • 🧬次数循环组件
      • ⌛️迭代循环组件
      • 🏄LiteflowComponent
      • 🛀组件内方法覆盖和调用
    • 🎁声明式组件

      • 🥭什么叫声明式组件
      • 🧅类级别式声明
      • 🥥方法级别式声明
  • 🧩EL规则

    • 🍄说明
    • 🌴串行编排
    • 🎋并行编排
    • 🌾选择编排
    • 🌵条件编排
    • 🌳循环编排
    • 🥦异步循环模式
    • 🎃捕获异常表达式
    • 🍄与或非表达式
    • 🍁使用子流程
    • 🍂使用子变量
    • 💐复杂编排例子
    • 🍒前置和后置编排
    • 🍉组件参数语法

      • 说明
      • tag语法
      • data语法
      • bind语法
    • 🫐重试语法
    • ⏱️超时控制语法
    • 🥯链路继承
    • 🔆验证规则
    • 🌰关于注释
    • 🌻关于分号
    • 🐚组件名包装
  • 🌮上下文

    • 🍄说明
    • 🌯数据上下文的定义和使用
    • 🪶用初始化好的上下文传入
    • 🥨给上下文设置别名
    • 🥙上下文参数注入
    • 🪴用表达式获取上下文参数
  • 🛩执行器

    • 🍄说明
    • 🎡执行方法
    • 🎢流程入参
    • 🎈LiteflowResponse对象
    • 🪃直接执行EL规则
  • 🍋脚本组件

    • 🌭脚本语言介绍
    • 🍫脚本语言种类

      • ☕️Java脚本引擎
      • 🥏Groovy脚本引擎
      • 🧀Javascript脚本引擎
      • 🥞QLExpress脚本引擎
      • 🍧Python脚本引擎
      • 🍝Lua脚本引擎
      • 🥐Aviator脚本引擎
      • 🥠Kotlin脚本引擎
    • 🍣脚本与Java进行交互
    • 🍱多脚本语言混合共存
    • 🌯文件脚本的定义
    • 🍘动态刷新脚本
    • 🍦验证脚本
    • 🗑卸载脚本
  • 🗂规则配置源

    • 📕本地规则文件配置
    • 📘SQL数据库配置源(旧)
    • 📗ZK规则文件配置源(旧)
    • 📋Nacos配置源(旧)
    • 🗄Etcd配置源(旧)
    • 📜Apollo配置源(旧)
    • 📑Redis配置源(旧)

      • 配置说明
      • 轮询模式配置
      • 订阅模式配置
    • 📙自定义配置源
  • 🏦Rule-DB模式

    • 🏦Rule-DB是什么
    • 🐬快速开始(SQL)
    • 🐘快速开始(PostgreSQL)
    • 🍃快速开始(MongoDB)
    • 📕快速开始(Redis)
    • 📗快速开始(ZooKeeper)
    • 🗄快速开始(Etcd)
    • 📋快速开始(Nacos)
    • 🍢配置项参考
    • 🗃存储结构参考
    • 📤发布API与写入规范
      • 推荐:统一发布API
        • 请求与返回对象
        • 乐观锁expectedVersion
        • 生命周期
        • 各后端的原子性保证
      • SQL兼容门面(新代码不推荐用)
      • 停用(enable=0)
      • 绕过API直接写存储的规范
      • content_md5这道双保险
      • 发布校验与依赖顺序
    • ⚖️一致性与收敛模型
    • 🪁内存与性能
    • 🔭可观测性与降级
    • 🚧限制与迁移
  • 🍼元数据管理

    • ⛰元数据操作器
    • 🍖平滑热刷新
    • 🍮启动不检查规则
    • 🥨启动不检查脚本
  • 🌌异步中的线程池

    • 💧说明
    • 🐋FlowExecutor层面的线程池
    • 🐠组件异步层面的线程池
    • 🪶虚拟线程
  • 🎲动态构造

    • 🍄说明
    • 🥜构造Node
    • 🌰构造EL
    • 🍞构造Chain
  • 🧮决策路由

    • 🏖概念以及介绍
    • 🍽决策路由用法
  • 😸生命周期

    • 🐮启动时生命周期
    • 🐳执行时生命周期
  • 🎨高级特性

    • 🍌本地规则文件监听
    • 🥠组件降级
    • 🍑组件别名
    • 🥝组件事件回调
    • 🐋组件回滚
    • 🥑隐式子流程
    • 🫐活跃规则保活策略
    • 🍕私有投递
    • 🍪组件切面
    • 🍡步骤信息
    • 🧊异常
    • 🧇打印信息详解
    • 🧁自定义请求Id
    • 🫕快速解析模式
    • 🌭不同格式规则加载
    • 🍿自定义组件执行器
    • 🍥简单监控
    • 🧉XML的DTD
  • 📈指标监控

    • 📈指标监控概述
    • 🚀快速接入
    • 📋指标目录
    • 🔌Actuator端点说明
    • 📐分位与直方图
    • 🔍常用PromQL与告警
    • ⚡性能影响与基数控制
  • ⛱测试用例以及示例

    • 🪁测试用例
    • 🪀DEMO案例
  • 🪂性能表现
  • v2.16.X文档
  • 🏦Rule-DB模式
铂赛东
2026-07-25
目录

📤发布API与写入规范

版本支持:v2.16.1+

# 推荐:统一发布API

七个后端共用一套发布接口com.yomahub.liteflow.publisher.RulePublisher,通过RulePublisherFactory.create(config),按你传入的后端配置实例化出来。

这是推荐的写入方式,尤其适合独立的管理后台,只依赖一个插件jar,不用拉起FlowExecutor,也不依赖全局的LiteflowConfig。

// 以 Redis 为例;其他后端换成对应的 XxxPublisherConfig
try (RulePublisher publisher = RulePublisherFactory.create(
        RedisPublisherConfig.builder()
                .address("redis://127.0.0.1:6379")
                .applicationName("your-app")
                .build())) {

    PublishResult r = publisher.publishChain(PublishChainRequest.builder()
            .chainId("orderChain")
            .el("THEN(a, b)")
            .route("AND(a)")          // 可选:路由EL
            .namespace("ns1")         // 可选:命名空间
            .build());
    r.getVersion();   // 新版本号
    r.getSequence();  // 变更序号
}

getSequence()的含义随后端不同:SQL、PostgreSQL、MongoDB、Redis、Nacos这几个是seq,zk是zxid,etcd是revision。

# 请求与返回对象

三个请求类型都是不可变的builder对象:

类型 字段
PublishChainRequest chainId、el、route(可空)、namespace(可空)、expectedVersion(可空)
PublishScriptRequest nodeId、script、name(可空)、type(script、boolean_script、switch_script、for_script之一)、language(可空)、expectedVersion(可空)
RemoveRuleRequest targetId、expectedVersion(可空),removeChain和removeScript共用
PublishResult(返回) targetId、targetType、operation(UPSERT或DELETE)、version、sequence

发布时md5由框架自己算,version由存储层自增。

发布脚本只负责保存源码和语言标识,所以每个执行应用都必须显式引入对应的liteflow-script-*插件,否则这个脚本首次加载时会失败。

# 乐观锁expectedVersion

这是并发安全发布的关键,三种取值:

取值 语义
不设(null,默认) UPSERT。已存在则version = version + 1,不存在则插入version = 1。已有行的并发更新在行锁、Lua或事务下原子自增,本身就是安全的。
0 意思是必须新建。目标已经存在的话抛VersionConflictException。并发首发同一个id时用这个,第一个成功,其余都被明确拒绝。
N(大于0) CAS。只有目标当前版本恰好是N时才更新到N+1,否则抛VersionConflictException。适合读出来改完再存、要确保中间没人改过的场景,管理后台的编辑表单就是典型。

VersionConflictException和配置、校验类的错误各有独立的异常类型,都在com.yomahub.liteflow.publisher.exception.*下,方便上层区分是该重试冲突还是参数写错了。

# 生命周期

RulePublisher实现了AutoCloseable。SQL和PostgreSQL后端是每次操作借一次连接,而Redis、MongoDB、zk、etcd、Nacos可能持有客户端连接,所以用完一定要close(),推荐直接用try-with-resources。

从外部传进来的DataSource、MongoClient、ConfigService或其他客户端仍归调用方所有,Publisher不会去关它们。

# 各后端的原子性保证

后端 保证
SQL 单事务内先锁定change_lock.lock_id = 1,再完成UPSERT内容行和INSERT change_log,要回滚就一起回滚。
PostgreSQL 同SQL,另外用RETURNING seq返回提交序号。
MongoDB 在多文档事务里完成内容CAS、sequence自增和change_log插入。
Redis 一段Lua脚本在Redis单线程内原子完成HSET内容、SADD索引、INCR seq、ZADD changelog四步,要么全成要么全不成。
zk 一个multi-op事务内原子写meta和content两个znode。
etcd 一个Txn内原子写meta和content两个key。
Nacos 读取当前Catalog,再以它的MD5为条件执行CAS,原子替换正文、业务版本、sequence和lastChange。并发CAS失败会重新读取后重试,最多8次。

# SQL兼容门面(新代码不推荐用)

SQL模块里还保留着com.yomahub.liteflow.repository.sql.SqlRulePublisher。它的无参构造从全局LiteflowConfig取连接配置,但不支持route、namespace和expectedVersion,也没有统一Publisher那套基于change_lock的发布顺序协议。

所以它不能作为多节点或者并发发布场景的生产写入入口。2.16.1的新代码和管理后台请用上面的RulePublisherFactory加SqlPublisherConfig。

下面这段代码只是给你识别和迁移旧调用用的,不建议新写:

SqlRulePublisher publisher = new SqlRulePublisher();
long v = publisher.publishChain("orderChain", "THEN(a, b)");
publisher.publishScript(scriptRecord);   // 传 ScriptRecord
publisher.removeChain("orderChain");
publisher.removeScript("s1");

迁移的时候把连接参数放进SqlPublisherConfig,再把ScriptRecord转成PublishScriptRequest就行。

# 停用(enable=0)

v1的Publisher没有提供enableChain和enableScript这两个API,留作后续。如果只是想临时停用而不删除,可以直接写存储把enable置成0:

  • SQL:UPDATE lf_chain SET enable=0 WHERE application_name=? AND chain_id=?
  • PostgreSQL:UPDATE lf_chain SET enable=FALSE WHERE application_name=? AND chain_id=?
  • MongoDB:把对应文档的enable改成false,并且递增version
  • Redis:HSET {prefix}:{app}:chain:{id} enable 0
  • zk和etcd:把对应meta节点里的enable标志置0,编码格式见各后端的*RecordCodec
  • Nacos:当前的Catalog协议不接受enable=false的记录,所以不支持直改停用,请用removeChain和removeScript

注意

直接改enable不会产生变更日志和通知,各节点要等下一个对账周期才能感知,默认最多60秒。zk和etcd如果改了meta节点的内容会触发watch,那就是秒级感知。另外,已经在缓存里的编译产物在感知之前还会继续执行。

想让停用立即生效,请用removeChain,删除会走变更通知,秒级收敛;或者停用之后再按下面的规范补一条变更日志。

# 绕过API直接写存储的规范

如果你已经有自己的管理后台,不想引Java客户端,那直接写存储也可以,但必须把Publisher的事务和原子语义完整复制过去,缺一步都会导致节点收敛失败。

SQL直写要在一个事务里完成下面这些:

  1. 事务开始后先执行SELECT lock_id FROM lf_change_lock WHERE lock_id = 1 FOR UPDATE,持锁到提交或回滚。自定义了表前缀记得同步替换表名,另外不要按application_name去拆锁。
  2. UPSERT lf_chain或lf_script行,version = version + 1。这里要靠行锁下的原子自增,不要先SELECT出来再在Java里加一,并发发布会丢更新。同时重算并写入content_md5,chain是MD5(el_data)且不含route_data,script是MD5(script_data)。算法必须和Publisher一致,否则会产生虚假的对账diff。
  3. INSERT INTO lf_change_log (application_name, target_type, target_id, op, version) VALUES (...)。
  4. 提交事务,要回滚就一起回滚。
  5. 删除的场景:拿到同一把顺序锁之后,DELETE内容行并INSERT一条op=DELETE的change_log,同样在一个事务里完成。

PostgreSQL直写遵循同一套事务协议。MongoDB直写则必须在一个多文档事务里完成正文CAS、lf_sequence自增和lf_change_log插入。

Redis直写必须用一段Lua脚本完成HSET内容、SADD索引、INCR seq、ZADD changelog这四步,可以参考模块内的lua/publish-chain.lua。不要用普通命令拼,拼出来的话多个命令之间存在竞态,可能让别的客户端读到内容已更新但seq还没推的中间态。

zk和etcd直写必须在一个事务里(zk用multi-op,etcd用Txn)同时写meta和content,保证两者版本一致。

Nacos不支持直改Catalog,请只用RulePublisher。

# content_md5这道双保险

对账的时候先比version,相同的话再比content_md5。除了Nacos,比的都是content_md5列或字段里存着的值,引擎不会去拉取内容重算哈希。所以这道双保险防的是version判据失灵、但指纹仍然可信的情况:

  • 备份恢复或者跨环境导表,版本号恰好撞车了(都是v7)但内容不同,这时md5不一样,对账能纠正过来。
  • 写方更新了内容和md5,但version没加上去(工具有bug,或者并发丢了更新),同样md5不一样,对账能纠正。

它防不住只改内容、version和content_md5都不动的裸改

两个元数据都没变,对账每一轮都会判定为无变化,于是你的改动永远不会生效。这是手改库最常见的坑。

临时运维时手动改一条规则,最小的正确姿势是这样:

UPDATE lf_chain
SET el_data     = 'THEN(a, c, b, s1)',
    version     = version + 1,     -- 必须:对账感知变更的主判据
    content_md5 = MD5(el_data)     -- 建议:保持指纹与内容一致
WHERE application_name = 'your-app' AND chain_id = 'chain1';

只做这一步的话,最迟reconcile-seconds(默认60秒)会生效。想在seq轮询周期内(SQL默认3秒)就生效,那还得按上面的规范补一条change_log。lf_script同理,只是指纹算的是MD5(script_data)。

停用一条chain只要enable = 0就够了,它会从manifest里消失,对账按DELETE处理,不用动version。

这些是兜底手段,不是鼓励你绕过规范,规范路径才是快路径。

Nacos是个例外,它每次读取都会对Catalog内的正文重新计算MD5并校验,指纹不匹配会直接拒绝整份Catalog。

# 发布校验与依赖顺序

Publisher保证的是单个目标的存储原子性和版本并发控制,它不负责解析或者编译业务规则:

  • 它会校验必填字段、长度、后端键名这些存储约束,但不校验EL语法,也不会去确认Java组件、子chain或脚本节点是否存在。
  • 一次API调用只原子发布一个chain或一个script。目前没有把多条相互依赖的规则作为一个bundle同时切换的事务API,所以多次调用之间始终存在最终一致性窗口。
  • 新增或升级依赖时,按先脚本和叶子子chain、再引用它们的父chain这个顺序发布;删除时反过来,先把父chain对依赖的引用去掉,再删脚本或子chain。
  • 发布前建议在隔离环境用和生产相同的Java组件、脚本插件跑一次冷加载测试。管理后台收到PublishResult只表示存储写入成功了,不代表所有执行节点都编译成功。
  • 回滚应该把已验证的旧正文作为一个新版本重新发布,不要把存储里的version直接改小。
帮助我们改善此文档 (opens new window)
🗃存储结构参考
⚖️一致性与收敛模型

← 🗃存储结构参考 ⚖️一致性与收敛模型→

Theme by Vdoing | Copyright © 2020-2026 铂赛东 | MIT License
沪ICP备18012955号-2