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

京东云开发者
• 阅读 227

前言

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

一、背景

1.1 现状

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

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

1.2 挑战点

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

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

二、解决方案

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

2.1 接入ZCube

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

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

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

搭建优惠券运营玩法规则

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

展示逻辑映射规则

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方式灰度上线,配置在白名单内的用户走规则引擎执行的逻辑,否则走原硬编码逻辑。
ZCube:在我的优惠券中的落地实践 | 京东云技术团队

三、结果展示

C端页面展示

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

SGM执行方法监控,tp99基本在1ms,cpu及内存较稳定,对系统原业务逻辑基本无影响

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

四、改造后的优势

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<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峰值 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执行 ZCube:在我的优惠券中的落地实践 | 京东云技术团队 ZCube:在我的优惠券中的落地实践 | 京东云技术团队
2 6000 package执行 ZCube:在我的优惠券中的落地实践 | 京东云技术团队 ZCube:在我的优惠券中的落地实践 | 京东云技术团队
3 10000 aviator执行 ZCube:在我的优惠券中的落地实践 | 京东云技术团队 ZCube:在我的优惠券中的落地实践 | 京东云技术团队
4 10000 package执行 ZCube:在我的优惠券中的落地实践 | 京东云技术团队 ZCube:在我的优惠券中的落地实践 | 京东云技术团队
5 20000 aviator执行 ZCube:在我的优惠券中的落地实践 | 京东云技术团队 ZCube:在我的优惠券中的落地实践 | 京东云技术团队
6 20000 package执行 ZCube:在我的优惠券中的落地实践 | 京东云技术团队 ZCube:在我的优惠券中的落地实践 | 京东云技术团队

作者:京东科技 王芳

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

点赞
收藏
评论区
推荐文章
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
2年前
NEO从源码分析看UTXO交易
_0x00前言_社区大佬:“交易是操作区块链的唯一方式。”_0x01交易类型_在NEO中,几乎除了共识之外的所有的对区块链的操作都是一种“交易”,甚至在“交易”面前,合约都只是一个小弟。交易类型的定义在Core中的TransactionType中:源码位置:neo/Core/TransactionType
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
E小媛同学 E小媛同学
4个月前
运营商三要素API:简化业务办理流程
随着互联网的快速发展,各行各业都在积极推进数字化转型,以提升业务办理效率和用户体验。在这个过程中,运营商三要素API作为一种高效、安全的身份验证方式,正发挥着越来越重要的作用。本文将探讨运营商三要素API如何简化业务办理流程。
京东云开发者 京东云开发者
4个月前
码上加速,低代码解锁高效交付案例
一、背景简介站长工作台,致力于为京东物流所有站长、运营管理人员提供高效工作平台,拥有多元化的业务形态。我们力求提升团队研发效率、实现敏捷业务交付,以打造一支具备灵活性、高度协作和强适应能力的敏捷团队。二、提效案例描述2.1、痛点分析站长工作台的报表页面和任
京东云开发者 京东云开发者
2个月前
营销系统黑名单优化:位图的应用解析 | 京东云技术团队
背景营销系统中,客户投诉是业务发展的一大阻碍,一般会过滤掉黑名单高风险账号,并配合频控策略,来减少客诉,进而增加营销效率,减少营销成本,提升营销质量。营销系统一般是通过大数据分析建模,在CDP(客户数据平台,以客户为核心,围绕数据融合、人群圈选、用户洞察等
京东云开发者 京东云开发者
11个月前
【实践篇】教你玩转JWT认证---从一个优惠券聊起 | 京东云技术团队
JWT,可以说是分布式系统下的一个利器,我在我的很多项目实践中,认证系统的第一选择都是JWT。它的优势会让你欲罢不能,就像你领优惠券一样。
京东云开发者 京东云开发者
9个月前
618技术揭秘 - 大促弹窗搭投实践 | 京东云技术团队
618大促来了,对于业务团队来说,最重要的事情莫过于各种大促营销。如会场、直播带货、频道内营销等等。而弹窗作为一个极其重要的强触达营销工具,通常用来渲染大促氛围、引流主会场、以及通过频道活动来提升频道复访等。因此,如果能将运营的策略及想法快速转化为弹窗的内容并触达给用户,这对于提升运营效率及玩法灵活性的是极其有意义的。
京东云开发者 京东云开发者
5个月前
直播预告丨大模型+Agents疏通京东金融运营堵点
大模型时代,“应用变了”:把大模型装进金融营销分几步?11月24日(周五)14:0015:00开播!数字化打破信息孤岛,也建立更多孤岛运营人员被困在自己的环节里十余个子系统、子模块如何整合?自然语言任务中的“幻觉”如何克服?如何将AI训练成业务运营高手?京