1.访客跳出明细介绍

首先要识别哪些是跳出行为,要把这些跳出的访客最后一个访问的页面识别出来。那么就要抓住几个特征:

该页面是用户近期访问的第一个页面,这个可以通过该页面是否有上一个页面(last_page_id)来判断,如果这个表示为空,就说明这是这个访客这次访问的第一个页面。

首次访问之后很长一段时间(自己设定),用户没继续再有其他页面的访问

这第一个特征的识别很简单,保留 last_page_id 为空的就可以了。但是第二个访问的判断,其实有点麻烦,首先这不是用一条数据就能得出结论的,需要组合判断,要用一条存在的数据和不存在的数据进行组合判断。而且要通过一个不存在的数据求得一条存在的数据。更麻烦的他并不是永远不存在,而是在一定时间范围内不存在。那么如何识别有一定失效的组合行为呢?

最简单的办法就是 Flink 自带的 CEP 技术。这个 CEP 非常适合通过多条数据组合来识别某个事件。

用户跳出事件,本质上就是一个条件事件加一个超时事件的组合。

  • 流程图

2.代码实现

创建任务类UserJumpDetailApp.java,从kafka读取页面日志

import com.zhangbao.gmall.realtime.utils.MyKafkaUtil;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.runtime.state.filesystem.FsStateBackend;
import org.apache.flink.streaming.api.CheckpointingMode;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;

/**
* @author zhangbao
* @date 2021/10/17 10:38
* @desc
*/
public class UserJumpDetailApp {
   public static void main(String[] args) {
       //webui模式,需要添加pom依赖
       StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//       StreamExecutionEnvironment env1 = StreamExecutionEnvironment.createLocalEnvironment();
       //设置并行度
       env.setParallelism(4);
       //设置检查点
//       env.enableCheckpointing(5000, CheckpointingMode.EXACTLY_ONCE);
//       env.getCheckpointConfig().setCheckpointTimeout(60000);
//       env.setStateBackend(new FsStateBackend("hdfs://hadoop101:9000/gmall/flink/checkpoint/userJumpDetail"));
//       //指定哪个用户读取hdfs文件
//       System.setProperty("HADOOP_USER_NAME","zhangbao");

       //从kafka读取数据源
       String sourceTopic = "dwd_page_log";
       String group = "user_jump_detail_app_group";
       String sinkTopic = "dwm_user_jump_detail";
       FlinkKafkaConsumer<String> kafkaSource = MyKafkaUtil.getKafkaSource(sourceTopic, group);
       DataStreamSource<String> kafkaDs = env.addSource(kafkaSource);

       kafkaDs.print("user jump detail >>>");

       try {
           env.execute("user jump detail task");
      } catch (Exception e) {
           e.printStackTrace();
      }
  }
}

3. flink CEP编程

官方文档:https://nightlies.apache.org/flink/flink-docs-release-1.12/dev/libs/cep.html

处理流程

1.从kafka读取日志数据

2.设定时间语义为事件时间并指定事件时间字段ts

3.按照mid分组

4.配置CEP表达式

  • 1.第一次访问的页面:last_page_id == null

  • 2.第一次访问的页面在10秒内,没有进行其他操作,没有访问其他页面

5.根据表达式筛选流

6.提取命中的数据

  • 设定超时时间标识 timeoutTag

  • flatSelect 方法中,实现 PatternFlatTimeoutFunction 中的 timeout 方法。

  • 所有 out.collect 的数据都被打上了超时标记

  • 本身的 flatSelect 方法因为不需要未超时的数据所以不接受数据。

  • 通过 SideOutput 侧输出流输出超时数据

7.将跳出数据写回到kafka

package com.zhangbao.gmall.realtime.app.dwm;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhangbao.gmall.realtime.utils.MyKafkaUtil;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkGenerator;
import org.apache.flink.api.common.eventtime.WatermarkGeneratorSupplier;
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.configuration.Configuration;
import org.apache.flink.runtime.state.filesystem.FsStateBackend;
import org.apache.flink.streaming.api.CheckpointingMode;
import org.apache.flink.streaming.api.datastream.DataStream;
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;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;

import java.util.List;
import java.util.Map;

/**
* @author zhangbao
* @date 2021/10/17 10:38
* @desc
*/
public class UserJumpDetailApp {
   public static void main(String[] args) {
       //webui模式,需要添加pom依赖
       StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//       StreamExecutionEnvironment env1 = StreamExecutionEnvironment.createLocalEnvironment();
       //设置并行度
       env.setParallelism(4);
       //设置检查点
//       env.enableCheckpointing(5000, CheckpointingMode.EXACTLY_ONCE);
//       env.getCheckpointConfig().setCheckpointTimeout(60000);
//       env.setStateBackend(new FsStateBackend("hdfs://hadoop101:9000/gmall/flink/checkpoint/userJumpDetail"));
//       //指定哪个用户读取hdfs文件
//       System.setProperty("HADOOP_USER_NAME","zhangbao");

       //从kafka读取数据源
       String sourceTopic = "dwd_page_log";
       String group = "user_jump_detail_app_group";
       String sinkTopic = "dwm_user_jump_detail";
       FlinkKafkaConsumer<String> kafkaSource = MyKafkaUtil.getKafkaSource(sourceTopic, group);
       DataStreamSource<String> jsonStrDs = env.addSource(kafkaSource);

       /*//测试数据
       DataStream<String> jsonStrDs = 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} "
       );
       dataStream.print("in json:");*/

       //对读取到的数据进行结构转换
       SingleOutputStreamOperator<JSONObject> jsonObjDs = jsonStrDs.map(jsonStr -> JSON.parseObject(jsonStr));

//       jsonStrDs.print("user jump detail >>>");
       //从flink1.12开始,时间语义默认是事件时间,不需要额外指定,如果是之前的版本,则要按以下方式指定事件时间语义
       //env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

       //指定事件时间字段
       SingleOutputStreamOperator<JSONObject> jsonObjWithTSDs = jsonObjDs.assignTimestampsAndWatermarks(
               WatermarkStrategy.<JSONObject>forMonotonousTimestamps().withTimestampAssigner(
                       new SerializableTimestampAssigner<JSONObject>() {
                           @Override
                           public long extractTimestamp(JSONObject jsonObject, long l) {
                               return jsonObject.getLong("ts");
                          }
                      }
      ));

       //按照mid分组
       KeyedStream<JSONObject, String> ketByDs = jsonObjWithTSDs.keyBy(
               jsonObject -> jsonObject.getJSONObject("common").getString("mid")
      );

       /**
        * flink CEP表达式
        * 跳出规则,满足两个条件:
        * 1.第一次访问的页面:last_page_id == null
        * 2.第一次访问的页面在10秒内,没有进行其他操作,没有访问其他页面
        */
       Pattern<JSONObject, JSONObject> pattern = Pattern.<JSONObject>begin("first")
              .where( // 1.第一次访问的页面:last_page_id == null
                   new SimpleCondition<JSONObject>() {
                       @Override
                       public boolean filter(JSONObject jsonObject) throws Exception {
                           String lastPageId = jsonObject.getJSONObject("page").getString("last_page_id");
                           System.out.println("first page >>> "+lastPageId);
                           if (lastPageId == null || lastPageId.length() == 0) {
                               return true;
                          }
                           return false;
                      }
                  }
              ).next("next")
              .where( //2.第一次访问的页面在10秒内,没有进行其他操作,没有访问其他页面
                       new SimpleCondition<JSONObject>() {
                           @Override
                           public boolean filter(JSONObject jsonObject) throws Exception {
                               String pageId = jsonObject.getJSONObject("page").getString("page_id");
                               System.out.println("next page >>> "+pageId);
                               if(pageId != null && pageId.length()>0){
                                   return true;
                              }
                               return false;
                          }
                      }
               //时间限制模式,10S
              ).within(Time.milliseconds(10000));

       //将cep表达式运用到流中,筛选数据
       PatternStream<JSONObject> patternStream = CEP.pattern(ketByDs, pattern);

       //从筛选的数据中再提取数据超时数据,放到侧输出流中
       OutputTag<String> timeOutTag = new OutputTag<String>("timeOut"){};
       SingleOutputStreamOperator<Object> outputStreamDS = patternStream.flatSelect(
               timeOutTag,
               //获取超时数据
               new PatternFlatTimeoutFunction<JSONObject, String>() {
                   @Override
                   public void timeout(Map<String, List<JSONObject>> map, long l, Collector<String> collector) throws Exception {
                       List<JSONObject> first = map.get("first");
                       for (JSONObject jsonObject : first) {
                           System.out.println("time out date >>> "+jsonObject.toJSONString());
                           //所有 out.collect 的数据都被打上了超时标记
                           collector.collect(jsonObject.toJSONString());
                      }
                  }
              },
               //获取未超时数据
               new PatternFlatSelectFunction<JSONObject, Object>() {
                   @Override
                   public void flatSelect(Map<String, List<JSONObject>> map, Collector<Object> collector) throws Exception {
                       //不超时的数据不提取,所以这里不做操作
                  }
              }
      );

       //获取侧输出流的超时数据
       DataStream<String> timeOutDs = outputStreamDS.getSideOutput(timeOutTag);
       timeOutDs.print("jump >>> ");
       
       //将跳出数据写回到kafka
       timeOutDs.addSink(MyKafkaUtil.getKafkaSink(sinkTopic));

       try {
           env.execute("user jump detail task");
      } catch (Exception e) {
           e.printStackTrace();
      }
  }
}

测试数据

将从kafka读取数据的方式切换成固定数据内容,如下:

//测试数据
       DataStream<String> jsonStrDs = 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} "
      );
       dataStream.print("in json:");

然后从dwm_user_jump_detail主题消费数据

./kafka-console-consumer.sh --bootstrap-server hadoop101:9092,hadoop102:9092,hadoop103:9092 --topic dwm_user_jump_detail

8.Flink实时项目之CEP计算访客跳出的更多相关文章

  1. 7.Flink实时项目之独立访客开发

    1.架构说明 在上6节当中,我们已经完成了从ods层到dwd层的转换,包括日志数据和业务数据,下面我们开始做dwm层的任务. DWM 层主要服务 DWS,因为部分需求直接从 DWD 层到DWS 层中间 ...

  2. 4.Flink实时项目之数据拆分

    1. 摘要 我们前面采集的日志数据已经保存到 Kafka 中,作为日志数据的 ODS 层,从 kafka 的ODS 层读取的日志数据分为 3 类, 页面日志.启动日志和曝光日志.这三类数据虽然都是用户 ...

  3. 5.Flink实时项目之业务数据准备

    1. 流程介绍 在上一篇文章中,我们已经把客户端的页面日志,启动日志,曝光日志分别发送到kafka对应的主题中.在本文中,我们将把业务数据也发送到对应的kafka主题中. 通过maxwell采集业务数 ...

  4. 10.Flink实时项目之订单维度表关联

    1. 维度查询 在上一篇中,我们已经把订单和订单明细表join完,本文将关联订单的其他维度数据,维度关联实际上就是在流中查询存储在 hbase 中的数据表.但是即使通过主键的方式查询,hbase 速度 ...

  5. 11.Flink实时项目之支付宽表

    支付宽表 支付宽表的目的,最主要的原因是支付表没有到订单明细,支付金额没有细分到商品上, 没有办法统计商品级的支付状况. 所以本次宽表的核心就是要把支付表的信息与订单明细关联上. 解决方案有两个 一个 ...

  6. 3.Flink实时项目之流程分析及环境搭建

    1. 流程分析 前面已经将日志数据(ods_base_log)及业务数据(ods_base_db_m)发送到kafka,作为ods层,接下来要做的就是通过flink消费kafka 的ods数据,进行简 ...

  7. 9.Flink实时项目之订单宽表

    1.需求分析 订单是统计分析的重要的对象,围绕订单有很多的维度统计需求,比如用户.地区.商品.品类.品牌等等.为了之后统计计算更加方便,减少大表之间的关联,所以在实时计算过程中将围绕订单的相关数据整合 ...

  8. 6.Flink实时项目之业务数据分流

    在上一篇文章中,我们已经获取到了业务数据的输出流,分别是dim层维度数据的输出流,及dwd层事实数据的输出流,接下来我们要做的就是把这些输出流分别再流向对应的数据介质中,dim层流向hbase中,dw ...

  9. 1.Flink实时项目前期准备

    1.日志生成项目 日志生成机器:hadoop101 jar包:mock-log-0.0.1-SNAPSHOT.jar gmall_mock ​ |----mock_common ​ |----mock ...

随机推荐

  1. CKKS Part5: CKKS的重缩放

    本文翻译于 CKKS EXPLAINED, PART 5: RESCALING,主要介绍CKKS方案中最重要的技术- rescaling,重缩放技术 介绍 在CKKS的前一篇文章 CKKS Part4 ...

  2. Td 内容不换行,超过部分自动截断,用...表示

    转载请注明来源:https://www.cnblogs.com/hookjc/ <table width="200px" style="table-layout:f ...

  3. Token+Redis实现接口幂等性

    一.什么是 幂等性 在编程中,幂等性的特点就是其任意多次执行的效果和一次执行的效果所产生的影响是一样的. 二.Token+Redis的实现思路 1.数据提交前要向服务的申请 token(用户登录时可以 ...

  4. Java 给Word每一页设置不同文字水印效果

    Word中设置水印时,可预设的文字或自定义文字设置为水印效果,但通常添加水印效果时,会对所有页面都设置成统一效果,如果需要对每一页或者某个页面设置不同的水印效果,则可以参考本文中的方法.下面,将以Ja ...

  5. tarjan——有向图、无向图

    强连通块只存在于有向无环图DAG中 实际上low[i]的理解是:一个强连通块在dfs搜索树中子树的根节点 //把一个点当成根提溜出来,抖搂抖搂成一棵树 void dfs(int u) { //记录df ...

  6. Solution -「Gym 102979E」Expected Distance

    \(\mathcal{Description}\)   Link.   用给定的 \(\{a_{n-1}\},\{c_n\}\) 生成一棵含有 \(n\) 个点的树,其中 \(u\) 连向 \([1, ...

  7. Solution -「JOISC 2021」「LOJ #3491」道路建设

    \(\mathcal{Description}\)   Link.   平面上有 \(n\) 个互不重合的点 \((x_{1..n},y_{1..n})\),求其两两曼哈顿距离的前 \(m\) 小值. ...

  8. Solution -「LOCAL」「cov. 牛客多校 2020 第三场 I」礼物

    \(\mathcal{Description}\)   给定排列 \(\{a_n\}\),求字典序第 \(K\) 大的合法排列 \(\{b_n\}\).称一个排列 \(\{p_n\}\) 合法,当且仅 ...

  9. .NET 云原生架构师训练营(权限系统 代码实现 Identity)--学习笔记

    目录 开发任务 代码实现 开发任务 DotNetNB.Security.Core:定义 core,models,Istore:实现 default memory store DotNetNB.Secu ...

  10. Anchor-free目标检测综述 -- Keypoint-based篇

      早期目标检测研究以anchor-based为主,设定初始anchor,预测anchor的修正值,分为two-stage目标检测与one-stage目标检测,分别以Faster R-CNN和SSD作 ...