前言

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

一、背景

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

  -- 描述 备注
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 exeRule03 SDK客户端示例项目 fin-rule-client-example
4 压测机器 指定预发机器 11.248.242.133 100.99.122.24

5.2 压测平台

forcebot 压测平台

压测脚本:

  压测方法 压测脚本
1 exePackage01 exePackage01.groovy
2 exePackage03 exePackage03.groovy
3 exeRule03 exeRule03.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, Object= 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, Object= 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, Object= 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峰值 tp99 tp999 成功率 内存使用率 cpu使用率
1 10000 1 1 100% 12.6% 31.6%
2 19500 1 1 100% 12.6% 53.7%

白条规则

  tps峰值 tp99 tp999 成功率 内存使用率 cpu使用率
1 2500 1 1 100% 12.5% 9.88%
2 3000 1 1 100% 12.5% 11.1%
3 3500 1 1 100% 12.6% 13.0%
4 4000 1 1 100% 12.5% 13.9%
5 5000 1 1 100% 12.5% 17.9%
6 6300 1 1 100% 12.5% 21.6%
7 8400 1 1 100% 12.5% 26.9%
8 10500 1 1 100% 12.5% 33.7%
9 14500 1 1 100% 12.5% 46.1%
10 19200 1 1 100% 12.5% 59.2%

白条规则 aviator执行

  tps峰值 tp99 tp999 成功率 内存使用率 cpu使用率
1 6100 1 1 100% 12.4% 18.4%
2 10500 1 1 100% 12.4% 29.6%
3 20000 1 1 100% 12.5% 51.9%
  压测tps指标 执行方式 SGM监控 JDOS实例监控
1 6000 aviator执行
2 6000 package执行
3 10000 aviator执行
4 10000 package执行
5 20000 aviator执行
6 20000 package执行

作者:京东科技 王芳

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

ZCube:在我的优惠券中的落地实践 | 京东云技术团队的更多相关文章

  1. springboot升级过程中踩坑定位分析记录 | 京东云技术团队

    作者:京东零售 李文龙 1.背景 " 俗话说:为了修复一个小bug而引入了一个更大bug " 因所负责的系统使用的spring框架版本5.1.5.RELEASE在线上出过一个偶发的 ...

  2. Dubbo Mesh 在闲鱼生产环境中的落地实践

    本文作者至简曾在 2018 QCon 上海站以<Service Mesh 的本质.价值和应用探索>为题做了一次分享,其中谈到了 Dubbo Mesh 的整体发展思路是“借力开源.反哺开源” ...

  3. 从单个系统到云翼一体化支撑,京东云DevOps推进中的一波三折

    作者:王利莹 采访嘉宾:京东云DevOps团队负责人 郑永宽 今年,IDC 特别针对中国地区发布了<IDC MarketScape:中国 DevOps 云市场2019,厂商评估>研究报告, ...

  4. TFS在项目中DevOps落地进程(下)

    紧接上篇 TFS在项目中Devops落地进程(上) 再接着说TFS相关之前先插入一个番外篇,虽然跟TFS关系不大但跟DevOps关系很大,觉得有必要在此乱入一下. 番外篇--监控之Applicatio ...

  5. 如何解决 Iterative 半监督训练 在 ASR 训练中难以落地的问题丨RTC Dev Meetup

    前言 「语音处理」是实时互动领域中非常重要的一个场景,在声网发起的「RTC Dev Meetup丨语音处理在实时互动领域的技术实践和应用」活动中,来自微软亚洲研究院.声网.数美科技的技术专家,围绕该话 ...

  6. 深度召回模型在QQ看点推荐中的应用实践

    本文由云+社区发表 作者:腾讯技术工程 导语:最近几年来,深度学习在推荐系统领域中取得了不少成果,相比传统的推荐方法,深度学习有着自己独到的优势.我们团队在QQ看点的图文推荐中也尝试了一些深度学习方法 ...

  7. [转载]DevOps在传统企业的落地实践及案例分享

    内容来源:2017年6月10日,优维科技高级解决方案架构师黄星玲在“DevOps&SRE 超越传统运维之道”进行<DevOps在传统企业的落地实践及案例分享>演讲分享.IT 大咖说 ...

  8. DEVOPS落地实践分享

    DEVOPS落地实践分享 转载本文需注明出处:微信公众号EAWorld,违者必究. 引言: DevOps的理念已经说了很多年,其带来的价值逐渐被接受,很多企业也逐渐引入了DevOps.目前普元DevO ...

  9. SpringCloud落地实践

    这几年微服务架构越来越火.伴随着微服务概念的提示,越来越多的组织为了方便开发,结合实际提供很多微服务机构, 之前工作中一直使用dubbo作为微服务框架, dubbo只是专注于服务之间的通讯,所以更灵活 ...

  10. python coding style guide 的高速落地实践

    python coding style guide 的高速落地实践 机器和人各有所长,如coding style检查这样的可自己主动化的工作理应交给机器去完毕,故发此文帮助你在几分钟内实现coding ...

随机推荐

  1. 云小课 | 一个三分钟快速定制OCR应用的神器,要不?

    摘要:ModelArts Pro提供了文字识别套件,基于丰富的文字识别算法和行业知识积累,帮助客户快速构建满足不同业务场景需求的文字识别服务.三分钟即可快速定制OCR服务,实现多种版式图像的文字信息结 ...

  2. 海量数据分析快准稳!GaussDB(for MySQL) HTAP只读分析特性详解

    摘要:除了拥有 ClickHouse 本身的极致性能外,GaussDB(for MySQL)的HTAP只读分析在 MaterilizeMySQL引擎的性能和稳定性等方面具有更优秀的表现,为提供更快更准 ...

  3. 火山引擎 DataTester:一次 A/B 测试,帮助产品分享率提升超 20%

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,并进入官方交流群 对 C 端产品而言,增长的核心要素之一是用户活跃度.通过各类激发互动的方式,使信息得以在关系链中流转.传播,达成有效的信息 ...

  4. Hugging News #0506: StarCoder, DeepFloyd/IF 好多新的重量级模型

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  5. peewee 操作 sqlite 锁表问题分析

    在使用python orm 框架 peewee 操作数据库时时常会抛出以一个异常,具体的报错就是 database is locked 初步了解是因为sqlite锁的颗粒度比较大,是库锁.当一个连接在 ...

  6. 阿里云视频云vPaaS低代码音视频工厂:极速智造,万象空间

    当下音视频技术越来越广泛地应用于更多行各业中,但因开发成本高.难度系数大等问题,掣肘了很多企业业务的第二增长需求.阿里云视频云基于云原生.音视频.人工智能等先进技术,提供易接入.强拓展.高效部署和覆盖 ...

  7. SuperSocket 简单示例

    这是一个SuperSocket 简单示例,包括服务端和客户端. 一.首先使用NuGet安装SuperSocket和SuperSocket.Engine 二.实现IRequestInfo(数据包): 数 ...

  8. UVA - 1594 :Ducci Sequence (set应用)

    给定n元组(a1,a2,...,an),ai均为整数,得到下一个序列为(|a1-a2|,|a2-a3|,...,|an-a1|),如此循环下去,必定会出现全零序列或重复序列. 现要求判断给定序列是全零 ...

  9. AtCoder ABC 206

    比赛链接:Here AB水题,跳过 C - Swappable 在数组中找到满足条件的数对 \((i,j)\) \(1 \le i < j \le N (N\in[2,3e5])\) \(A_i ...

  10. 深入理解web协议(二):DNS、WebSocket

    本文首发于 vivo互联网技术 微信公众号链接:https://mp.weixin.qq.com/s/AkbAN4UZLDf841g1ZLFPBQ作者:Wu Yue 本文系统性的讲述了 DNS 协议与 ...