使用Flink-CEP标记网页跳出用户代码开发

一、需求说明:

对页面日志数据进行ETL,对跳出用户进行标记后输出到Kafka。

跳出用户定义:
条件1:不是从其他页面跳转过来的页面,是一个首次访问页面。日志数据表现为不存在last_page_id字段。
条件2:距离首次访问结束后10秒内,没有对其他的页面再进行访问。

ps:该需求一般为实时项目中对kafka日志数据进行消费后处理,后续输出到kafka计算页面跳出率用于运营分析使用,该文重点在于代码部分的处理,因此测试数据简化输入及输出。

  • 测试数据说明:
//mid对应设备,page_id对应当前访问页面,ts访问时间戳,last_page_id指来源页面(有该字段代表从其他页面跳转过来)
{common:{mid:101},page:{page_id:home},ts:10000} ,
{common:{mid:102},page:{page_id:home},ts:12000},
{common:{mid:102},page:{page_id:good_list,last_page_id:home},ts:15000} ,
{common:{mid:102},page:{page_id:good_list,last_page_id:detail},ts:300000}

//mid101访问home之后没有访问其他页面,定义为跳出用户,
//mid102在10秒内访问了其他用户定义为非跳出用户,最后的结果输出应该如下:
{"common":{"mid":"101"},"page":{"page_id":"home"},"ts":10000}

二、CEP简单说明:

Flink CEP是一个基于Flink的复杂事件处理库,可以从多个数据流中发现复杂事件,CEP API的核心是Pattern(模式) API,它允许快速定义复杂的事件模式。每个模式包含多个阶段(stage)或者也可称为状态(state)。从一个状态切换到另一个状态,用户可以指定条件,这些条件可以作用在邻近的事件或独立事件上。

三、代码部分:

//新建Maven项目
package com.flink.cep;

import com.alibaba.fastjson.JSONObject;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.cep.CEP;
import org.apache.flink.cep.PatternFlatSelectFunction;
import org.apache.flink.cep.PatternFlatTimeoutFunction;
import org.apache.flink.cep.PatternStream;
import org.apache.flink.cep.pattern.Pattern;
import org.apache.flink.cep.pattern.conditions.SimpleCondition;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;
import java.util.List;
import java.util.Map;
/**
 * @author: Rango
 * @create: 2021-04-18 13:26
 * @description: CEP测试小案例  基于Flink 1.12
 **/
public class FlinkCepTest {
    public static void main(String[] args) throws Exception {

        //TODO 1.建立环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //TODO 2.创建测试数据
        DataStream<String> dataStream = env
                .fromElements(
                        "{\"common\":{\"mid\":\"101\"},\"page\":{\"page_id\":\"home\"},\"ts\":10000} ",
                        "{\"common\":{\"mid\":\"102\"},\"page\":{\"page_id\":\"home\"},\"ts\":12000}",
                        "{\"common\":{\"mid\":\"102\"},\"page\":{\"page_id\":\"good_list\",\"last_page_id\":" +
                                "\"home\"},\"ts\":15000} ",
                        "{\"common\":{\"mid\":\"102\"},\"page\":{\"page_id\":\"good_list\",\"last_page_id\":" +
                                "\"detail\"},\"ts\":30000}"
                );

        //TODO 3.转换数据为JSON格式,这里使用阿里的fastjson
        SingleOutputStreamOperator<JSONObject> jsonObjDS = dataStream.map(jsonStr -> JSONObject.parseObject(jsonStr));

        //TODO 4.指定事件时间字段
        //ps:1.12以前版本需要指定事件时间语义env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        SingleOutputStreamOperator<JSONObject> jsonObjWithTSDS = jsonObjDS.assignTimestampsAndWatermarks(
                WatermarkStrategy.<JSONObject>forMonotonousTimestamps().withTimestampAssigner(
                        new SerializableTimestampAssigner<JSONObject>() {
                            @Override
                            public long extractTimestamp(JSONObject element, long recordTimestamp) {
                                return element.getLong("ts");
                            }
                        }
                ));

        //TODO 5.按照mid进行分组
        KeyedStream<JSONObject, String> keyMyMids = jsonObjWithTSDS.keyBy(
                jsonObj -> jsonObj.getJSONObject("common").getString("mid")
        );

        //TODO 6.配置CEP表达式
        Pattern<JSONObject,JSONObject> pattern = Pattern.<JSONObject>begin("first")
                .where(
                        //条件1:不是从其他页面跳转过来的页面,是一个首次范围页面,也就是没有last_page_id
                        new SimpleCondition<JSONObject>() {
                            @Override
                            public boolean filter(JSONObject jsonObj) throws Exception {
                                //获取last_page_id
                                String lastPageId = jsonObj.getJSONObject("page").getString("last_page_id");
                                //判断是lastPageId是否为空
                                if (lastPageId == null || lastPageId.length() == 0) {
                                    return true;  //返回true代表符合筛选条件
                                }
                                return false;
                            }
                        }
                ).next("next")
                .where(
                        //条件2:首次访问结束后10秒内,没有对其他页面再进行访问
                        new SimpleCondition<JSONObject>() {
                            @Override
                            public boolean filter(JSONObject jsonObj) throws Exception {
                                //获取pageid
                                String pageId = jsonObj.getJSONObject("page").getString("page_id");
                                //判断pageId是否为空
                                if (pageId != null || pageId.length() > 0) {
                                    return true;
                                }
                                return false;
                            }
                        }
                ).within(Time.milliseconds(10000));//10秒

        //TODO 7.根据CEP表达式筛选流
        PatternStream<JSONObject> patternStream = CEP.pattern(keyMyMids, pattern);

        //TODO 8.从筛选之后的流中,提取数据,将超时数据放到侧输出流中
        //侧输出流先定义标签
        OutputTag<String> timeoutTag = new OutputTag<String>("timeout"){};
        //3个参数,标签,超时数据处理类,未超时数据处理类,使用匿名内部类方式
        SingleOutputStreamOperator<String> firstDS = patternStream.flatSelect(
                timeoutTag,
                new PatternFlatTimeoutFunction<JSONObject, String>() {
                    @Override
                    public void timeout(Map<String, List<JSONObject>> map, long l, Collector<String> collector) throws Exception {
                        //获取所有符合first的json对象
                        //说明:该类是处理超时数据,如果超时则不会存在cep表达式里next的数据,也就是只需要通过first就可以获取所需mid
                        List<JSONObject> jsonObjectList = map.get("first");
                        //该方法数据会被参数1标记,因此使用增强打印输出集合里面各数据即可
                        for (JSONObject jsonObject : jsonObjectList) {
                            collector.collect(jsonObject.toJSONString());
                        }
                    }
                },
                new PatternFlatSelectFunction<JSONObject, String>() {
                    @Override
                    public void flatSelect(Map<String, List<JSONObject>> map, Collector<String> collector) throws Exception {
                        //按需求,未超时部分数据无需处理
                    }
                }
        );

        // TODO 9.从侧输出流获取超时数据
        DataStream<String> jumpDS = firstDS.getSideOutput(timeoutTag);
        jumpDS.print(">>>>>>>");
        //后续如果需要发送到消息队列,配置kafka相关依赖,通过addSink方法输出到kafka即可

        env.execute();
    }
}

相关依赖如下:

    <properties>
        <flink.version>1.12.0</flink.version>
        <scala.version>2.12</scala.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-cep_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>
        <!--Flink默认使用的是slf4j记录日志,相当于一个日志的接口,我们这里使用log4j作为具体的日志实现-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            <version>2.14.0</version>
        </dependency>
    </dependencies>

结果输出如下:
在这里插入图片描述


学习交流,有任何问题还请随时评论指出交流。

posted @ 2021-05-07 14:41  Rango_lhl  阅读(214)  评论(1编辑  收藏  举报