ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

规则引擎集成实战:API、SDK与消息队列三种模式解析

规则引擎集成实战:API、SDK与消息队列三种模式解析 我从去年开始在公司内部推JVS规则引擎起因特别朴素运营每隔两周就要调一次满减规则后端就得跟着改一次代码、发一次版测试和运维两头都不满意。规则引擎这东西你把它说得再玄乎落到自己系统里的第一道坎永远是——怎么把它接进来。当时我把手头的几个方案都翻了一遍最终选了JVS不只是因为它开源、控制台能在线配置规则关键是它真正提供了可被业务系统调用的执行能力不是那种只能演示的玩具。真正动手之后我才发现光是“集成”这一个动作就能拆出三种完全不同的玩法RESTful API调用、SDK本地嵌入、消息队列异步执行。这篇文章我不讲花活就讲这三种模式各自适合什么场景、怎么接入、踩过哪些坑以及最后怎么选型。1. 先把规则引擎这件事拆清楚规则、执行器与集成边界1.1 JVS规则引擎解决了什么问题从硬编码到在线编排很多团队之所以决定引入规则引擎肯定是被“硬编码规则”折磨过。最典型的状态是满减逻辑写在订单服务的某个Service里风控规则写在另一个服务里积分规则又散落在定时任务中。改一个门槛要动代码、走发布流程而且不同系统的规则很容易出现口径不一致——比如A系统认为满300减30B系统还停留在满200减20。JVS规则引擎做的事情是把“规则的定义”和“业务的代码”分开。规则本身不再是一个个if-else而是以结构化配置的形式存放在规则中心里由统一的引擎执行。我在实际使用中最常用到三类规则单条表达式规则类似orderAmount 100 memberLevel gold这种条件判断适合简单场景。决策表按行列表格来配置条件组合适合维度多、组合多的场景比如按区域、重量、会员等级计算运费。脚本规则在规则块里写一段自定义脚本处理那些表达式和决策表表达不了的计算逻辑。控制台里能在线编辑、测试、发布、停用形成了一整套闭环。但是光有规则引擎本身还不够它必须被嵌入到你的业务流程里才能产生价值。这就是集成要做的事也是我写这篇文章的核心原因。1.2 集成模式选型前必懂的四个边界我开始搭接入方案的时候第一反应是“是不是官方有标准接入方式”。翻了一圈发现JVS规则引擎的执行能力可以通过多种方式对外暴露并没有唯一标准答案。所以我在内部做技术评审时拉了一个清单先把四个边界问题确认清楚才好往下选。第一个边界是配置和执行的分离。JVS控制台是“配置面”它负责把规则写好、测好、发布出去但“执行面”可以落在独立服务里也可以落在你的业务进程里。所谓三种集成模式本质上是解决“执行面”以什么方式接入你的系统。第二个边界是同步和异步的边界。业务方是需要在一次调用里立刻拿到规则结果还是只需要触发一个事件、让规则引擎慢慢算结果晚一点落库也没关系这个决定了整个链路的形态。第三个边界是技术栈边界。规则引擎服务本身是Java技术栈如果你的调用方也是JavaSDK是可能的选项但如果你的团队里有Python、Go写的服务或者外部系统要接入那HTTP API往往是最稳妥的。第四个边界是规则变更速度的边界。规则多久变一次变更之后业务系统能否接受几秒甚至几分钟的延迟如果你希望规则发布后立刻生效那么SDK本地缓存的更新机制就是一个必须考察的点这也是我在后面章节里会重点提到的坑。这四个边界想清楚之后再去看三种集成模式你会发现自己选择的依据清晰很多。2. 模式一RESTful API调用——把规则执行变成一次普通接口请求2.1 什么场景下应该选HTTPRESTful API接入是最直观的一种模式。规则引擎部署成一个独立的Web服务业务系统把规则编码和业务数据通过HTTP请求发给它它执行完规则之后把结果以JSON格式返回。这个模式最大的优势就是门槛低调用方不需要引入任何特定语言SDK只要会发HTTP请求就能接入。在我实际接触的项目里有几种情况特别适合走HTTP跨语言接入。比如一个数据中台的服务是用Python写的它要判断一批用户是否命中“高价值客户”规则直接调API就好没必要专门为它写一个Python版本的SDK。运营类的低频调用。运营后台经常需要“人工触发一次规则计算”比如手动给某个用户重算积分、测试某个新规则的效果。这种场景一天就几百次调用专门引入SDK反而增加了部署复杂度。不想绑定特定规则引擎版本。通过API模式业务系统只需要知道接口约定后续规则引擎升级、替换甚至换成别的引擎影响面都能被控制在接口适配层。这个模式也有明显的代价每次规则执行多一跳网络多一次HTTP连接开销而且对规则引擎服务本身的高可用要求更高。如果你们的调用量在日均几十万次以内网络开销基本可以忽略但如果到了百万级甚至更高就要谨慎评估了。2.2 接入前需要准备的三件事不要一上来就写代码调接口先把下面三件事确认好能省掉后面不少返工。第一规则引擎服务的部署和网络规划。规则执行服务建议挂在你们的内网网关后面不要暴露到公网。部署方式一般走Docker或容器编排存储用MySQL规则配置都存在数据库里。我当时是把规则执行服务单独划了一个命名空间和业务服务之间走Kubernetes Service发现没有走外网域名这样既安全又稳定。第二确认规则编码规范。每一条规则必须有唯一的规则编码我建议编码命名直接体现业务含义比如ORDER_FULL_REDUCE、MEMEBER_LEVEL_UP不要用无意义的自增ID。不然时间一久你根本不知道这个规则代码是干嘛的。第三搞清楚接口的入参和出参结构。JVS的执行接口一般会接收规则编码和业务上下文数据把一段Map或JSON数据传进去引擎在里面用这些字段做判断。返回结果通常包含是否命中、命中的具体规则、输出参数、决策结果以及一些执行元信息。正式开发前先在控制台或Swagger文档里跑通一个最简单的例子再开始写代码。2.3 一个真实调用示例满减规则的请求与响应我把当时接入时用的一条满减规则整理成示例结构基本是这样字段命名在不同版本可能略有差异以你们部署版本的实际接口为准请求体{ ruleCode: ORDER_FULL_REDUCE, bizData: { userId: U10086, orderAmount: 328, memberLevel: gold, couponCount: 2 } }响应体{ success: true, matched: true, ruleCode: ORDER_FULL_REDUCE, ruleVersion: 20250410-01, outputParams: { discountAmount: 30, finalAmount: 298, reduceLevel: L2 }, hitRules: [ { ruleName: 黄金会员满300减30, ruleId: 1024 } ] }这里的逻辑是规则引擎拿到orderAmount328和memberLevelgold之后匹配到“黄金会员满300减30”这条规则最终算出优惠30元实付298元。我在对接过程中特别关注的是ruleVersion这个字段。规则是会随时变的如果业务系统在结算单里没有记录当时命中的规则版本后续对账或者售后追溯时很容易扯皮。在后面讲坑的那一节我会再展开说这个问题。2.4 HTTP模式最容易踩的两个坑第一个坑是超时设置。我们内部早期有个服务把规则引擎调用的超时设为3秒某次规则集里挂了一个超大决策表一次执行要跑近一秒结果直接把下游一堆接口拖慢了。后来我们做了两件事一是把超时压到800ms对超过阈值的规则单独告警二是把大规则集按业务拆分成多个小规则集避免一次执行涉及上千条决策。规则引擎应该是毫秒级的如果经常跑出几百毫秒甚至一秒别急着加超时先优化规则本身。第二个坑是重复请求。运营后台通常都有“重算”按钮如果调用方不去重同一笔订单很容易被重复计算积分、重复发券。这个问题的本质是HTTP接口天然无状态所以业务接入方必须自己在接口层处理幂等——用一个业务事件ID做去重或者落一张执行记录表。我们的最终方案是在业务系统里维护了一张规则执行流水表以bizId ruleCode做唯一索引这样无论API被调多少次真正生效的只有一次。3. 模式二SDK本地嵌入——规则逻辑跑进业务进程内部3.1 什么情况下需要SDK而不是HTTP当调用频率足够高HTTP模式就有点撑不住了。倒不是说硬件扛不住而是每次执行都带网络开销和连接池管理业务方还得考虑超时、重试、熔断这些事情。如果你的系统是Java技术栈并且规则调用是高频路径SDK嵌入是更合适的方案。SDK模式的本质是把规则引擎的“执行端”打包成Jar包放进你的业务进程。业务代码直接在你的堆内存里调用规则执行方法没有网络跳转没有JSON序列化和反序列化开销延迟基本就是一次本地方法调用加表达式计算的时间。需要注意的是SDK并不等于把规则库也拷进本地进程来处理所有事情。它通常的设计是启动时从规则中心拉取最新的规则定义缓存在本地执行时优先用本地缓存的规则规则中心有变更后通过某种机制定时拉取、长轮询或者消息通知把增量更新同步到各客户端。我当时接入时是按这个思路理解的具体同步机制要以你们所用版本的源码或官方实现为准但这套“远程配置、本地执行”的设计是规则引擎客户端最常见的做法。3.2 接入过程的三个关键步骤第一步引入Maven坐标。官方会发布规则引擎Client的Jar包坐标类似下面这样具体版本号以官方仓库为准dependency groupIdcom.jvs/groupId artifactIdjvs-rule-client/artifactId version2.1.0/version /dependency这里一定要先去仓库把版本确认好不要照抄网上的旧坐标。版本选错后面会因为API变动浪费很多时间。第二步初始化客户端。一般会通过一个Builder模式来构造ClientJvsRuleClient ruleClient JvsRuleClient.builder() .serverUrl(http://jvs-rule-center:8080) .appKey(控制台分配的AppKey) .secret(对应的Secret) .build();serverUrl指向规则中心AppKey和Secret是访问控制台签发规则用的凭证。初始化建议做成单例不要每次执行规则都New一个Client否则连接管理和本地缓存都白做了。第三步执行规则。代码看起来和本地调用一个工具方法差不多MapString, Object bizData new HashMap(); bizData.put(userId, U10086); bizData.put(orderAmount, 328); bizData.put(memberLevel, gold); RuleResponse response ruleClient.execute(ORDER_FULL_REDUCE, bizData);注意这里的类名只是示意实际类名以你拉到的Jar包为准。接入完成后业务代码里不再有“满300减30”这种魔法数字只有一行规则的调用这正是规则引擎嵌入的价值所在。3.3 接入SDK后必须处理的两个隐患SDK模式远不是加个依赖、调个方法就完事它有两个特别容易翻车的地方。第一个是依赖冲突。规则引擎SDK通常会传递依赖JSON序列化库、表达式引擎等基础组件这跟业务系统里已有的版本经常“打架”。我当时就遇到过业务服务为了性能把Jackson从2.9升到2.12结果SDK里老版本的Jackson方法出现NoSuchMethodError排查了半天才发现是传递依赖版本不一致。解决办法是使用Maven的exclusion排除掉SDK内部的依赖统一用业务方自己管理的版本。如果你们用了Spring Boot的依赖管理更要在引入前检查版本BOM是否有冲突。第二个是规则热更新。因为SDK本地有缓存控制台发布新规则之后客户端不一定立刻生效。这个“滞后窗口”可以是几秒也可能是几分钟取决于它的同步机制。我们当时的做法是规则发布后先等1到2分钟再放量同时在发布脚本里显式调用一个主动刷新接口让指定的服务节点强制拉取最新规则。如果你的场景要求规则变更“秒级生效”最好在选型阶段就和官方确认清楚同步策略不要等上线了才发现规则改了业务没反应。4. 模式三消息队列异步执行——高吞吐与最终一致性的打法4.1 为什么好端端的同步链路要改成异步前面两种模式都是同步调用业务线程发请求、等结果。但真实业务里有大量场景并不需要同步等待结果。举个例子用户下单成功后系统要计算积分、更新会员等级、判断是否触发新人礼包。这些操作如果全部放在下单请求的链路里同步执行一次下单要串行调好几个规则接口响应时间明显变差而且积分计算这种逻辑本来就适合做异步用户根本不需要在提交订单的瞬间看到积分到账。我当初接手的一个项目就是这样每天结算时要把几十万条订单数据跑一遍积分规则如果全部走HTTP同步接口即使并发拉满也会占用大量连接资源。后来改成消息队列模式业务数据定时批量推送到MQ规则引擎侧消费消息算出结果批量落库整个系统的吞吐一下就上来了。这其实就是把“规则执行”从一次接口调用变成了一次事件处理。4.2 异步集成的一条完整数据链路我基于自己的实践把异步集成的链路拆成四步实际项目中基本就是这个骨架业务系统在业务动作完成后把需要的业务上下文封装成一条消息发到消息队列的Topic里规则引擎侧做一个消费端监听这个Topic消费端解析消息之后调用本地或者远程的规则引擎执行规则执行结果写回结果表或者发送回传消息。消息结构大致是这样{ messageId: uuid-xxx, bizType: ORDER_CREATED, bizId: ORDER20250410001, ruleCode: SCORE_CALC_RULE, bizData: { userId: U10086, orderAmount: 328, memberLevel: gold } }这里有一个细节值得注意消息里最好带ruleCode和bizData两个核心段。bizData是要参与规则判断的业务上下文ruleCode指定了要执行哪条规则或者哪个规则集。如果你把bizData设计成通用扩展字段未来上线新规则时业务系统完全不用改代码只需要往消息里多塞几个字段即可。如果想要复用规则引擎已有的HTTP执行能力也可以做一个“异步网关”服务这个服务消费MQ然后调用规则引擎内部API把结果写回结果表。这样做的好处是不需要二次开发规则引擎的消费端我们在项目里就是这么落地的。4.3 异步方案的三个拦路虎幂等、顺序与重试异步模式看似简单但它把“可靠性”的责任从调用方转移到了消费端这三个问题无论如何都要处理。第一是幂等。MQ本身是至少一次投递语义消息可能重复消费。消费端拿到消息后第一步应该是检查幂等表以messageId或bizId作为唯一键判断这条消息是不是已经处理过了。如果没有幂等保护消息重复一次用户积分就会翻倍到时候对账就是事故。第二是顺序。同一个用户的多条规则事件之间可能存在先后依赖比如先判断“是否新客”再决定“是否发放新人券”。如果这两条消息被不同消费者并行处理顺序就乱了。解决办法是把消息队列的分区键设置为userId保证同一个用户的规则消息被投递到同一个分区消费端单线程消费这个分区顺序就保住了。第三是重试与死信。消费端在执行业务逻辑时可能因为规则引擎接口抖动而失败这时不能直接丢弃消息。我的习惯做法是失败后进入重试队列指数退避重试间隔分别设为1秒、5秒、30秒最多重试3次超过次数进入死信队列由人工或者定时补偿任务去处理。这样既不会无限重试拖垮系统也不会让失败数据悄无声息地消失。5. 三种集成模式怎么选一张对比表加四个决策问题5.1 横向对比表三种模式放在一起对比差别其实很清晰对比维度RESTful APISDK本地嵌入消息队列异步调用延迟毫秒级网络开销最低本地计算秒级甚至更长受队列堆积影响业务耦合度低只需懂HTTP中需引入SDK依赖低通过事件解耦跨语言支持好各语言都能调主要面向Java好依赖消息协议运维成本需维护独立服务依赖随业务系统发版需维护MQ集群与消费端高可用要求高规则引擎故障直接影响调用方相对高需处理本地缓存相对低可做重试补偿典型场景运营后台触发、跨语言调用高频实时调用批量结算、积分异步计算5.2 选型前先问自己的四个问题与其对着表格纠结不如先回答下面这四个问题答案出来之后选型基本上就定了。第一这个规则的调用量到底有多大如果每天只有几千次、几万次完全没有必要为了“性能”去上SDK或者MQHTTP API足够可靠如果每天百万次并且处在实时主链路上SDK才是合理的选项如果调用量极大但是不要求实时那就考虑MQ。第二执行结果多久必须可见用户在前端页面里点一下“试算运费”你让他等3秒体验基本就毁了这种场景必须同步但如果是“下单后计算积分”延迟10秒用户根本感知不到异步完全可行。第三调用方和规则引擎是不是同一个技术栈如果把规则执行能力开放给公司里多个异构系统HTTP是最通用的方式如果只有Java系统用SDK能带来更好的性能。第四规则变更的频率和时效要求是什么规则三天两头变而且希望发布后立刻生效HTTP模式最简单SDK模式必须把本地缓存同步机制的时效性搞清楚MQ模式天然有队列延迟不适合对时效特别敏感的场景。5.3 我们的项目是这样混合使用的在实际项目里我们并没有只选一种模式而是按不同的链路做了拆分。订单价格试算接口是用户实时操作的必须同步返回优惠后的价格这路走的是HTTP API每日结算积分是批量任务数据量大、不要求实时这路走的是MQ异步。两个通道共用同一个规则中心和同一套规则定义但执行路径完全隔离。这样做最大的好处是批量任务高峰期把队列塞满了不会影响到用户实时的试算接口反过来实时的连接池再怎么高并发也不会挤掉批量任务的资源。这种混合模式并不是一开始就设计出来的而是压测之后发现单一路径总有瓶颈后来才拆开的。如果你刚开始接入我建议不要一上来就搞混合。先把HTTP API跑通让业务能看到规则引擎的收益等量上来了再逐步加SDK或者MQ这样每一步都有明确的驱动因素而不是为了技术而技术。6. 落地到线上之后最容易翻车的四类问题6.1 规则版本和业务数据版本对不上这是我踩过最深的坑。规则是在线发布的发布的那一瞬间新规则就对所有请求生效了但历史业务数据并不会跟着变。比如3月1日下的订单3月5日做结算时规则已经改成新的满减门槛了。如果这笔订单没有记录它创建时用的规则版本结算结果就会用“新规则”去算“旧订单”用户看到的价格跟下单时不一致这就是客诉。所以在设计表结构的时候业务记录里至少要有两个字段一个是规则编码一个是规则版本号。规则引擎执行接口一般会返回版本信息把它存下来后续追溯时用这个版本号到规则引擎的历史版本里查才能保证每一笔业务的结果都可以复现。6.2 规则引擎的故障会拖垮整条主链路规则引擎再怎么说也只是个中间件它不是数据库也不是注册中心。它挂了主业务应该降级而不是跟着一起挂。但很多团队第一次接入时都会忽略这一点直接把规则执行的结果当成主流程的必要条件。我们的做法是在调用规则引擎的外面统一包了一层降级逻辑。如果规则引擎异常或者超时捕获异常、记录日志、返回一个默认结果比如“不命中规则”“无优惠”“积分0”然后触发告警让值班人员处理。这样规则引擎出问题的时候最坏的情况是某段时间业务没有按新规则走但主流程不会中断用户也不会感知到系统挂了。降级逻辑虽然“丑”但比直接抛异常强太多。6.3 日志难排查规则出问题只能靠猜规则引擎执行失败最让人头疼的是业务日志里只有一行“调用规则引擎异常”根本看不出来到底是哪条规则、哪个条件、哪条数据出了问题。出现问题的原因非常多样可能是字段为空、类型不匹配、脚本执行异常也可能就是规则本身配置错了。我在接入时做了一件事强烈建议你也做所有经过规则引擎的请求都带一个traceId并在业务侧把传入规则引擎的那张完整的业务上下文快照打印出来。规则引擎服务侧也把入参、命中路径、输出结果都打日志。这样排查问题的时候只要拿着业务侧的traceId去规则引擎日志里搜整条链路的输入和输出都清清楚楚。如果平台版本本身不输出命中路径日志至少在网关层自己记一份不然规则一复杂效率真的是靠猜。6.4 规则灰度发布不能依赖平台要自己掌握JVS控制台本身发布规则是全量生效的这个动作在早期风险并不大但当规则影响价格、积分、优惠券这类直接涉及用户利益的场景时全量发布一次失误就是事故。所以我在团队内部定了一个规矩规则的灰度逻辑不依赖平台而是在接入层自己控制。具体做法是在业务侧维护一个灰度开关按用户ID哈希取模决定走新规则还是旧规则。先放5%的流量观察规则结果是否有异常再逐步放大确认无误后全量。如果平台支持指定用户白名单更好可以在白名单里先给内部测试账号跑一遍。规则引擎本身只是一个执行器它只负责算得快、算得对放量节奏和回滚策略必须由接入方自己掌握。我自己做下来最大的体会是集成模式没有排名只有匹配。选择哪一种不是看哪个更“高级”而是取决于你的调用量、时效要求、技术栈和规则变更方式。如果让我给一个最朴素的建议那就是从HTTP API起步跑通整条链路后再根据瓶颈选择SDK或MQ不要在一开始就把架构撑得太满。另外一个小技巧无论选哪种模式都建议在业务接口层沉淀一套规则冒烟测试用例。JVS控制台里的调试只能覆盖单条规则但线上业务往往是多条规则组合生效所以在自己的接口层写一套自动化脚本每次规则变更后自动跑一遍才能真正把“规则自由变”变成“规则放心变”。
返回列表