ZCube:在我的优惠券中的落地实践 | 京东云技术团队
- 2024-01-02 北京
本文字数:6107 字
阅读完需:约 20 分钟
前言
我的优惠券作为营销玩法的一种运营工具,在营销活跃场中起到很至关重要的作用。如何更加高效的赋能业务,助理业务发展,灵活扩展业务,是我们一直追求和思考的方向
一、背景
1.1 现状
营销中台作为券的“供应链端”,控制券的所有类型。
我的优惠券作为工具,提供用户已有优惠券的展示列表,不同类型的券利益点不同,运营会提供各自展示规则。
谋略作为用户触达方,为了提高券的核销率,会对用户做过期提醒 push,同时触达文案要求跟券的营销文案一致。
1.2 挑战点
1、营销中台每次新增券类型,都需要运营指定营销文案后,由研发硬编码实现。能不能支持业务运营人员根据需求灵活扩展,动态配置营销文案,并且能够及时生效呢?
2、消息中心的 push 提醒文案需要跟营销展示文案一致,那就由业务侧研发硬编码实现一套,消息中心侧实现一套,并且还得保证两处的规则逻辑一致才可以。
能不能将这种相同的规则抽取出来,以订阅的方式下发到订阅者上去,既保证规则的唯一性,也能够做到规则共享?
二、解决方案
鉴于上述场景的痛点,我们接入 ZCube 平台来解决
2.1 接入 ZCube
官网地址:https://zcube.jr.jd.com文章介绍:《ZCube:会员权益体系规则引擎原理介绍 【一】》
首先,在 ZCube 平台接入我的优惠券应用
搭建优惠券运营玩法规则
展示逻辑映射规则
发布知识包
2.2 应用系统接入 SDK
maven 坐标依赖
<!-- 规则引擎客户端SDK -->
<dependency>
<groupId>com.jd.jdt.rule.core</groupId>
<artifactId>rule-core-client-spring</artifactId>
<version>1.0.1-SNAPSHOT</version>
</dependency>
properties 参数配置
rule.env=prod
rule.appName=jrm_member_center
rule.secret=xxx
rule.packages=xxx
spring xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"
default-lazy-init="false">
<bean id="ruleExecuteService" class="com.jd.jdt.rule.core.client.service.impl.RuleExecuteServiceImpl">
<property name="knowledgeCacheService" ref="knowledgeCachePushService"/>
</bean>
<bean id="knowledgeCachePushService" class="com.jd.jdt.rule.core.client.service.impl.KnowledgeCachePushService">
<property name="cloudFileInnerService" ref="cloudFileInnerService"/>
</bean>
<bean id="cloudFileInnerService" class="com.jd.jdt.rule.core.client.service.impl.CloudFileInnerService">
<constructor-arg index="0" value="#{frozenParamBean['rule.env']}"/>
</bean>
<bean id="ruleClient" class="com.jd.jdt.rule.core.client.etcd.RuleClient">
<constructor-arg index="0" value="#{frozenParamBean['rule.appName']}"/>
<constructor-arg index="1" value="#{frozenParamBean['rule.env']}"/>
<constructor-arg index="2" value="#{frozenParamBean['rule.packages']}"/>
<constructor-arg index="3" ref="knowledgeCachePushService"/>
</bean>
</beans>
api 调用
RuleExecutionResult ruleExecutionResult = ruleExecuteService.fireRules(knowPackageName, param);
2.3 AB 方式灰度上线
为了保证现有功能的稳定,我们采用 AB 方式灰度上线,配置在白名单内的用户走规则引擎执行的逻辑,否则走原硬编码逻辑。
三、结果展示
C 端页面展示
SGM 执行方法监控,tp99 基本在 1ms,cpu 及内存较稳定,对系统原业务逻辑基本无影响
四、改造后的优势
1、营销中台新增品后,只需要在策略中心可视化配置,0 代码
2、策略规则支持热部署,发布审批即生效
3、业务规则以知识库形式存储,所见即所得,便于业务侧优惠券资源治理
五、压测
5.1 压测对象
5.2 压测平台
压测脚本:
exePackage01
package jsf
import com.jd.fastjson.JSON
import com.jd.fastjson.JSONObject
import com.jd.forcebot.engine.TestUtils
import com.jd.forcebot.engine.groovy.Lifecycle
import com.jd.forcebot.engine.groovy.RatePolicy
import com.jd.forcebot.engine.groovy.TestCase
import com.jd.forcebot.engine.groovy.TestSuite
import com.jd.forcebot.toolkit.parameterized.latest.AsciiFileAccessArbitrarily
import com.jdd.test.performance.common.GenericServiceInvoker
import org.slf4j.Logger
@TestSuite(value = "forcebot", lifecycle = Lifecycle.THREAD, ratePolicy = RatePolicy.STANDARD)
class TestQueryRightsCardDetail {
public final Logger logger = TestUtils.LOGGER;
public static GenericServiceInvoker genericServiceInvoker;
public static AsciiFileAccessArbitrarily realPinFile = new AsciiFileAccessArbitrarily("test_1w_pin.txt");
static {
genericServiceInvoker = new GenericServiceInvoker();
genericServiceInvoker.initService("com.jd.jdt.rule.engine.core.example.facade.IRuleExeFacade","fin-rule-client-example");
}
String getPin() {
return realPinFile.readLine().trim();
}
@TestCase( record = false)
void queryRightsCardDetail() {
// 真实pin
String pin = getPin();
logger.info("pin: {}", pin);
String tranName = "exePackage01";
JSONObject param = new JSONObject();
param.put("pin", pin);
param.put("clientIP", "127.0.0.1");
String strParam = JSON.toJSONString(param);
try {
TestUtils.transactionBegin(tranName);
Object result = genericServiceInvoker.invoke("exePackage01",new String[]{"java.lang.Integer","java.lang.Integer"},new Object[]{45,45});
logger.info("{} result: {}", tranName, result);
boolean ret = resultCheck(result, tranName);
} catch (Exception ex) {
TestUtils.transactionFailure(tranName);
logger.error(ex.getMessage(), ex);
}
}
boolean resultCheck(Object result, String tranName) {
if (result == null) {
logger.error("{} exec error, result is null.", tranName);
return false;
}
if (!(result instanceof Map)) {
logger.error("{} exec error, result is {}.", tranName, result);
return false;
}
Map<String, Object> resultMap = result;
logger.info(tranName + " result: {}.", resultMap);
if (resultMap == null || resultMap.size() == 0) {
logger.warn("{} exec error, result map size is zero.", tranName);
TestUtils.transactionFailure(tranName);
return false;
}
TestUtils.transactionSuccess(tranName);
// logger.info("{} exec succeed, result: {}.", tranName, resultMap);
return true;
}
}
exePackage03
package jsf
import com.jd.fastjson.JSON
import com.jd.fastjson.JSONObject
import com.jd.forcebot.engine.TestUtils
import com.jd.forcebot.engine.groovy.Lifecycle
import com.jd.forcebot.engine.groovy.RatePolicy
import com.jd.forcebot.engine.groovy.TestCase
import com.jd.forcebot.engine.groovy.TestSuite
import com.jd.forcebot.toolkit.parameterized.latest.AsciiFileAccessArbitrarily
import com.jdd.test.performance.common.GenericServiceInvoker
import org.slf4j.Logger
@TestSuite(value = "forcebot", lifecycle = Lifecycle.THREAD, ratePolicy = RatePolicy.STANDARD)
class TestQueryRightsCardDetail {
public final Logger logger = TestUtils.LOGGER;
public static GenericServiceInvoker genericServiceInvoker;
public static AsciiFileAccessArbitrarily realPinFile = new AsciiFileAccessArbitrarily("test_1w_pin.txt");
static {
genericServiceInvoker = new GenericServiceInvoker();
genericServiceInvoker.initService("com.jd.jdt.rule.engine.core.example.facade.IRuleExeFacade","fin-rule-client-example");
}
String getPin() {
return realPinFile.readLine().trim();
}
@TestCase( record = false)
void queryRightsCardDetail() {
// 真实pin
String pin = getPin();
logger.info("pin: {}", pin);
String tranName = "exePackage03";
JSONObject param = new JSONObject();
param.put("pin", pin);
param.put("clientIP", "127.0.0.1");
param.put("orderType", "ssahhh");
param.put("bizCode", "qwerrtta");
String strParam = JSON.toJSONString(param);
try {
TestUtils.transactionBegin(tranName);
Object result = genericServiceInvoker.invoke("exePackage03","com.jd.jdt.rule.engine.core.example.domain.MqVar",strParam);
logger.info("{} result: {}", tranName, result);
boolean ret = resultCheck(result, tranName);
} catch (Exception ex) {
TestUtils.transactionFailure(tranName);
logger.error(ex.getMessage(), ex);
}
}
boolean resultCheck(Object result, String tranName) {
if (result == null) {
logger.error("{} exec error, result is null.", tranName);
return false;
}
if (!(result instanceof Map)) {
logger.error("{} exec error, result is {}.", tranName, result);
return false;
}
Map<String, Object> resultMap = result;
logger.info(tranName + " result: {}.", resultMap);
if (resultMap == null || resultMap.size() == 0) {
logger.warn("{} exec error, result map size is zero.", tranName);
TestUtils.transactionFailure(tranName);
return false;
}
TestUtils.transactionSuccess(tranName);
// logger.info("{} exec succeed, result: {}.", tranName, resultMap);
return true;
}
}
execRule03
package jsf
import com.jd.fastjson.JSON
import com.jd.fastjson.JSONObject
import com.jd.forcebot.engine.TestUtils
import com.jd.forcebot.engine.groovy.Lifecycle
import com.jd.forcebot.engine.groovy.RatePolicy
import com.jd.forcebot.engine.groovy.TestCase
import com.jd.forcebot.engine.groovy.TestSuite
import com.jd.forcebot.toolkit.parameterized.latest.AsciiFileAccessArbitrarily
import com.jdd.test.performance.common.GenericServiceInvoker
import org.slf4j.Logger
@TestSuite(value = "forcebot", lifecycle = Lifecycle.THREAD, ratePolicy = RatePolicy.STANDARD)
class TestQueryRightsCardDetail {
public final Logger logger = TestUtils.LOGGER;
public static GenericServiceInvoker genericServiceInvoker;
public static AsciiFileAccessArbitrarily realPinFile = new AsciiFileAccessArbitrarily("test_1w_pin.txt");
static {
genericServiceInvoker = new GenericServiceInvoker();
genericServiceInvoker.initService("com.jd.jdt.rule.engine.core.example.facade.IRuleExeFacade","fin-rule-client-example");
}
String getPin() {
return realPinFile.readLine().trim();
}
@TestCase( record = false)
void queryRightsCardDetail() {
// 真实pin
String pin = getPin();
logger.info("pin: {}", pin);
String tranName = "execRule03";
JSONObject params = new JSONObject();
params.put("orderType","1");
params.put("orderType","1");
params.put("bizCode",1);
params.put("subBizCode",0);
params.put("merchantCode","200022");
params.put("subMerchantCode","110597078001");
JSONObject param = new JSONObject();
param.put("ruleId", "bt");
param.put("params", params);
String strParam = JSON.toJSONString(param);
try {
TestUtils.transactionBegin(tranName);
Object result = genericServiceInvoker.invoke("execRule03","com.jd.jdt.rule.engine.core.example.domain.RuleRequest",strParam);
logger.info("{} result: {}", tranName, result);
boolean ret = resultCheck(result, tranName);
} catch (Exception ex) {
TestUtils.transactionFailure(tranName);
logger.error(ex.getMessage(), ex);
}
}
boolean resultCheck(Object result, String tranName) {
if (result == null) {
logger.error("{} exec error, result is null.", tranName);
return false;
}
if (!(result instanceof Map)) {
logger.error("{} exec error, result is {}.", tranName, result);
return false;
}
Map<String, Object> resultMap = result;
logger.info(tranName + " result: {}.", resultMap);
if (resultMap == null || resultMap.size() == 0) {
logger.warn("{} exec error, result map size is zero.", tranName);
TestUtils.transactionFailure(tranName);
return false;
}
TestUtils.transactionSuccess(tranName);
// logger.info("{} exec succeed, result: {}.", tranName, resultMap);
return true;
}
}
模拟调用:easyone接口调用
5. 3 压测指标监控
SGM 监控:
5. 4 结论
demo
白条规则
白条规则 aviator 执行
作者:京东科技 王芳
来源:京东云开发者社区 转载请注明来源
京东科技开发者
拥抱技术,与开发者携手创造未来! 2018-11-20 加入
我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩
评论