从零构建FLINK整合Drools动态规则实时运营系统(项目案例)-第6篇(V1.0版开发篇)

前言

项目介绍在线视频: https://www.bilibili.com/video/BV1zv41157yY

本案例是一个专注于flink动态规则计算的项目,核心技术组件涉及flink、hbase、clickhouse、drools等
项目可根据各类个性化需求进行二次开发后,直接用于实时运营,实时风控、交通监控等场景的线上生产

列位看官,为了能够更好地理解后续《动态规则版实时运营系统》的设计思想和代码实现,
我们先来开发一个简化版且没有动态规则功能的实时运营系统;


规则匹配state查询逻辑设计


行为数据state窗口设计

存储上线以来的所有用户明细(显然不合适,但是先从简单做起)

行为次数类条件查询设计

代码片段;详细完整代码请参见项目工程

/**
 * @author 涛哥
 * @nick_name "deep as the sea"
 * @contact qq:657270652 wx:doit_edu
 * @site www.doitedu.cn
 * @date 2021-03-28
 * @desc 用户行为次数类条件查询服务实现:在flink的state中统计行为次数
 */
public class UserActionCountQueryServiceStateImpl implements UserActionCountQueryService {
​
​
    /**
     * 查询规则参数对象中,要求的用户行为次数类条件是否满足
     * 同时,将查询到的真实次数,set回 规则参数对象中
     *
     * @param eventState 用户事件明细存储state
     * @param ruleParam  规则整体参数对象
     * @return 条件是否满足
     */
    public boolean queryActionCounts(ListState<LogBean> eventState, RuleParam ruleParam) throws Exception {// 取出各个用户行为次数原子条件
        List<RuleAtomicParam> userActionCountParams = ruleParam.getUserActionCountParams();// 取出历史明细数据
        Iterable<LogBean> logBeansIterable = eventState.get();// 统计每一个原子条件所发生的真实次数,就在原子条件参数对象中:realCnts
        queryActionCountsHelper(logBeansIterable, userActionCountParams);// 经过上面的方法执行后,每一个原子条件中,都拥有了一个真实发生次数,我们在此判断是否每个原子条件都满足
        for (RuleAtomicParam userActionCountParam : userActionCountParams) {
            if (userActionCountParam.getRealCnts() < userActionCountParam.getCnts()) {
                return false;
            }
        }// 如果到达这一句话,说明上面的判断中,每个原子条件都满足,则返回整体结果true
        return true;
    }

行为次序类条件查询设计

从零构建FLINK整合Drools动态规则实时运营系统(项目案例)-第6篇(V1.0版开发篇)_第1张图片

    /**
     * 序列匹配,性能改进版
     *
     * @param events
     * @param userActionSequenceParams
     * @return
     */
    public int queryActionSequenceHelper2(Iterable<LogBean> events, List<RuleAtomicParam> userActionSequenceParams) {int maxStep = 0;
        for (LogBean event : events) {
            if (RuleCalcUtil.eventBeanMatchEventParam(event, userActionSequenceParams.get(maxStep))) {
                maxStep++;
            }
            if (maxStep == userActionSequenceParams.size()) break;
        }
        System.out.println("步骤匹配计算完成: 查询到的最大步骤号为: " + maxStep + ",条件中的步骤数为:" + userActionSequenceParams.size());
        return maxStep;
    }


画像属性查询设计

Flink查询hbase基本代码

/**
 * @author 涛哥
 * @nick_name "deep as the sea"
 * @contact qq:657270652 wx:doit_edu
 * @site www.doitedu.cn
 * @date 2021-03-28
 * @desc 用户画像查询服务,hbase查询实现类
 */
public class UserProfileQueryServiceHbaseImpl implements UserProfileQueryService {
​
    Connection conn;
    Table table;/**
     * 构造函数
     */
    public UserProfileQueryServiceHbaseImpl() throws IOException {
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum", "hdp01:2181,hdp02:2181,hdp03:2181");
​
        System.out.println("准备创建hbase连接........");
        conn = ConnectionFactory.createConnection(conf);
        table = conn.getTable(TableName.valueOf("yinew_profile"));
        System.out.println("创建hbase连接完毕.........");
    }/**
     * 传入一个用户号,以及要查询的条件
     * 返回这些条件是否满足
     * TODO 本查询只返回了成立与否,而查询到的画像数据值并没有返回
     * TODO 可能为将来的缓存模块带来不便,有待改造
     *
     * @param deviceId
     * @param ruleParam
     * @return
     */
    @Override
    public boolean judgeProfileCondition(String deviceId, RuleParam ruleParam){// 从规则参数中取出画像标签属性条件
        HashMap<String, String> userProfileParams = ruleParam.getUserProfileParams();// 取出条件中所要求的所有待查询标签名
        Set<String> tagNames = userProfileParams.keySet();// 构造一个hbase的查询参数对象
        Get get = new Get(deviceId.getBytes());
        // 把要查询的标签(hbase表中的列)逐一添加到get参数中
        for (String tagName : tagNames) {
            get.addColumn("f".getBytes(),tagName.getBytes());
        }
​
​
​
        // 调用hbase的api执行查询
        try {
            Result result = table.get(get);
            // 判断结果和条件中的要求是否一致
            for (String tagName : tagNames) {
                // 从查询结果中取出该标签的值
                byte[] valueBytes = result.getValue("f".getBytes(), tagName.getBytes());
                // 判断查询到的value和条件中要求的value是否一致,如果不一致,方法直接返回:false
                if(!(valueBytes!=null && new String(valueBytes).equals(userProfileParams.get(tagName)))){
                    System.out.println("查询了hbase,只是不匹配,真实值:" + new String(valueBytes) + "条件值:" + userProfileParams.get(tagName));
                    return false;
                }
            }
            // 如果上面的for循环走完了,那说明每个标签的查询值都等于条件中要求的值,则可以返回true
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }// 如果到了这,说明前面的查询出异常了,返回false即可
        return false;
    }
}

hbase画像数据查询性能压测

/***
 * @author 涛哥
 * @nick_name "deep as the sea"
 * @contack qq:657270652 wx:doit_edu
 * @site www.51doit.cn
 * @date 2021/3/29
 * @desc hbase查询性能简单测试代码
**/
public class HbaseGetTest {
    public static void main(String[] args) throws IOException {
​
​
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum", "hdp01:2181,hdp02:2181,hdp03:2181");
​
        Connection conn = ConnectionFactory.createConnection(conf);
        Table table = conn.getTable(TableName.valueOf("yinew_profile"));
​
​
        long s = System.currentTimeMillis();
        for(int i=0;i<1000;i++){
            Get get = new Get(StringUtils.leftPad(RandomUtils.nextInt(1, 900000) + "", 6, "0").getBytes());
            int i1 = RandomUtils.nextInt(1, 100);
            int i2 = RandomUtils.nextInt(1, 100);
            int i3 = RandomUtils.nextInt(1, 100);
            get.addColumn("f".getBytes(), Bytes.toBytes("tag"+i1));
            get.addColumn("f".getBytes(), Bytes.toBytes("tag"+i2));
            get.addColumn("f".getBytes(), Bytes.toBytes("tag"+i3));
​
​
            Result result = table.get(get);
            byte[] v1 = result.getValue("f".getBytes(), Bytes.toBytes("tag" + i1));
            byte[] v2 = result.getValue("f".getBytes(), Bytes.toBytes("tag" + i2));
            byte[] v3 = result.getValue("f".getBytes(), Bytes.toBytes("tag" + i3));
        }
        long e = System.currentTimeMillis();
​
        System.out.println(e-s);
        conn.close();}}

完整代码开发

代码结构如下:
从零构建FLINK整合Drools动态规则实时运营系统(项目案例)-第6篇(V1.0版开发篇)_第2张图片

代码片段;完整代码请参见项目工程;

package cn.doitedu.dynamic_rule.engine;
​
import cn.doitedu.dynamic_rule.functions.DeviceKeySelector;
import cn.doitedu.dynamic_rule.functions.Json2BeanMapFunction;
import cn.doitedu.dynamic_rule.functions.RuleProcessFunction;
import cn.doitedu.dynamic_rule.functions.SourceFunctions;
import cn.doitedu.dynamic_rule.pojo.LogBean;
import cn.doitedu.dynamic_rule.pojo.ResultBean;
import org.apache.avro.data.Json;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;/**
 * @author 涛哥
 * @nick_name "deep as the sea"
 * @contact qq:657270652 wx:doit_edu
 * @site www.doitedu.cn
 * @date 2021-03-28
 * @desc 静态规则引擎版本1主程序
 */
public class RuleEngineV1 {public static void main(String[] args) throws Exception {
​
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());// 添加一个消费kafka中用户实时行为事件数据的source
        DataStreamSource<String> logStream = env.addSource(SourceFunctions.getKafkaEventSource());// 将json格式的数据,转成 logbean格式的数据
        SingleOutputStreamOperator<LogBean> beanStream = logStream.map(new Json2BeanMapFunction());// 对数据按用户deviceid分key
        KeyedStream<LogBean, String> keyed = beanStream.keyBy(new DeviceKeySelector());// 开始核心计算处理
        SingleOutputStreamOperator<ResultBean> resultStream = keyed.process(new RuleProcessFunction());// 打印
        resultStream.print();
​
        env.execute();
    }
}

版本改进需求(V1.0弊端)

存在的问题

如果规则中的条件需要查询的数据时间跨度很长,而state不适合存储太大量的明细数据,“V1.0版”的方案就变得不可行了

解决方案

可以引入clickhouse,存储用户历史以来的行为明细数据,并利用clickhouse进行快速聚合计算
用clickhouse支撑“时间跨度长”的条件查询
用state支撑“时间跨度短”的条件查询

你可能感兴趣的:(多易教育课程发布,flink整合drools,flink动态规则,flink,flinkcep,flink实时项目)