ZCube:在我的优惠券中的落地实践 | 京东云技术团队

前言

我的优惠券作为营销玩法的一种运营工具,在营销活跃场中起到很至关重要的作用。如何更加高效的赋能业务,助理业务发展,灵活扩展业务,是我们一直追求和思考的方向

一、背景

1.1 现状

营销中台作为券的“供应链端”,控制券的所有类型。
我的优惠券作为工具,提供用户已有优惠券的展示列表,不同类型的券利益点不同,运营会提供各自展示规则。
谋略作为用户触达方,为了提高券的核销率,会对用户做过期提醒push,同时触达文案要求跟券的营销文案一致。

1.2 挑战点

  • 1、营销中台每次新增券类型,都需要运营指定营销文案后,由研发硬编码实现。能不能支持业务运营人员根据需求灵活扩展,动态配置营销文案,并且能够及时生效呢?

  • 2、消息中心的push提醒文案需要跟营销展示文案一致,那就由业务侧研发硬编码实现一套,消息中心侧实现一套,并且还得保证两处的规则逻辑一致才可以。
    能不能将这种相同的规则抽取出来,以订阅的方式下发到订阅者上去,既保证规则的唯一性,也能够做到规则共享?

二、解决方案

鉴于上述场景的痛点,我们接入ZCube平台来解决

2.1 接入ZCube

官网地址:https://zcube.jr.jd.com文章介绍:《ZCube:会员权益体系规则引擎原理介绍 【一】

首先,在ZCube平台接入我的优惠券应用

搭建优惠券运营玩法规则

展示逻辑映射规则

image.png

发布知识包

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 压测对象

 --描述备注
1压测案例项目SDK客户端示例项目fin-rule-client-example
2压测机器用户持有优惠券接口接口:com.jd.jdt.rule.engine.core.example.facade.IRuleExeFacade 别名:fin-rule-client-example
3执行知识包01 exePackage01 执行知识包03 exePackage03 执行aviator01 exeRule03SDK客户端示例项目fin-rule-client-example
4压测机器指定预发机器11.248.242.133 100.99.122.24

5.2 压测平台

forcebot 压测平台

压测脚本:

 压测方法压测脚本
1exePackage01exePackage01.groovy
2exePackage03exePackage03.groovy
3exeRule03exeRule03.groovy

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监控:
sgm方法监控

5. 4 结论

demo

 tps峰值tp99tp999成功率内存使用率cpu使用率
11000011100%12.6%31.6%
21950011100%12.6%53.7%

白条规则

 tps峰值tp99tp999成功率内存使用率cpu使用率
1250011100%12.5%9.88%
2300011100%12.5%11.1%
3350011100%12.6%13.0%
4400011100%12.5%13.9%
5500011100%12.5%17.9%
6630011100%12.5%21.6%
7840011100%12.5%26.9%
81050011100%12.5%33.7%
91450011100%12.5%46.1%
101920011100%12.5%59.2%

白条规则 aviator执行

 tps峰值tp99tp999成功率内存使用率cpu使用率
1610011100%12.4%18.4%
21050011100%12.4%29.6%
32000011100%12.5%51.9%
 压测tps指标执行方式SGM监控JDOS实例监控
16000aviator执行
26000package执行
310000aviator执行
410000package执行
520000aviator执行
620000package执行

作者:京东科技 王芳

来源:京东云开发者社区 转载请注明来源

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

【实践篇】教你玩转JWT认证---从一个优惠券聊起 | 京东云技术团队

JWT,可以说是分布式系统下的一个利器,我在我的很多项目实践中,认证系统的第一选择都是JWT。它的优势会让你欲罢不能,就像你领优惠券一样。

618技术揭秘 - 大促弹窗搭投实践 | 京东云技术团队

618大促来了,对于业务团队来说,最重要的事情莫过于各种大促营销。如会场、直播带货、频道内营销等等。而弹窗作为一个极其重要的强触达营销工具,通常用来渲染大促氛围、引流主会场、以及通过频道活动来提升频道复访等。因此,如果能将运营的策略及想法快速转化为弹窗的内容并触达给用户,这对于提升运营效率及玩法灵活性的是极其有意义的。

大模型时代下的新一代广告系统

京东零售广告部承担着京东全站流量变现及营销效果提升的重要职责,广告研发部是京东最核心的技术部门,也是京东最主要的盈利来源之一。作为京东广告部的核心方向,我们基于京东海量的用户和商家数据,探索最前沿的深度学习等算法技术,创新并应用到业务实践中,赋能千万商家和

Haskell爬虫:为电商运营抓取京东优惠券的实战经验

一、需求分析:为什么抓取京东优惠券?京东作为中国领先的电商平台之一,拥有海量的商品和丰富的优惠券资源。这些优惠券信息对于电商运营者来说具有极高的价值。通过分析竞争对手的优惠券策略,运营者可以更好地制定自己的促销方案,优化营销策略,从而在激烈的市场竞争中脱颖