【实时数仓】DWM层订单宽表之需求分析、订单和订单明细关联源码

一 DWM层-订单宽表

1 需求分析与思路

订单是统计分析的重要的对象,围绕订单有很多的维度统计需求,比如用户、地区、商品、品类、品牌等等。

为了之后统计计算更加方便,减少大表之间的关联,所以在实时计算过程中将围绕订单的相关数据整合成为一张订单的宽表。

在这里插入图片描述

如上图,由于在之前的操作已经把数据分拆成了事实数据和维度数据,事实数据(绿色)进入kafka数据流(DWD层)中,维度数据(蓝色)进入hbase中长期保存。那么在DWM层中要把实时和维度数据进行整合关联在一起,形成宽表。那么这里就要处理有两种关联,事实数据和事实数据关联、事实数据和维度数据关联。

  • 事实数据和事实数据关联,其实就是流与流之间的关联。
  • 事实数据与维度数据关联,其实就是流计算中查询外部数据源。

2 订单和订单明细关联代码实现

(1)从Kafka的dwd层接收订单和订单明细数据

实现思路如下:

在这里插入图片描述

a 创建订单实体类
package com.hzy.gmall.realtime.beans;

import lombok.Data;
import java.math.BigDecimal;

/**
 * Desc: 订单实体类
 */
@Data
public class OrderInfo {
    // 属性名需要与数据库中字段名相同
    Long id;
    Long province_id;
    String order_status;
    Long user_id;
    BigDecimal total_amount;    //实际付款金额
    BigDecimal activity_reduce_amount;
    BigDecimal coupon_reduce_amount;
    BigDecimal original_total_amount;
    BigDecimal feight_fee;
    String expire_time;
    String create_time;
    String operate_time;
    String create_date; // 把其他字段处理得到
    String create_hour;
    Long create_ts; // 通过create_time转换
}
b 创建订单明细实体类
package com.hzy.gmall.realtime.beans;

import lombok.Data;
import java.math.BigDecimal;

/**
 * Desc:订单明细实体类
 */
@Data
public class OrderDetail {
    Long id;
    Long order_id ; // 无外键约束
    Long sku_id;
    BigDecimal order_price ;
    Long sku_num ;
    String sku_name; // 冗余字段,可以减少连接查询的次数
    String create_time;
    BigDecimal split_total_amount;
    BigDecimal split_activity_amount;
    BigDecimal split_coupon_amount;
    Long create_ts; // 通过create_time转换
}
c 在dwm包下创建OrderWideApp读取订单和订单明细数据
package com.hzy.gmall.realtime.app.dwm;
/**
 * 订单宽表的准备
 */
public class OrderWideApp {
    public static void main(String[] args) throws Exception {
        //TODO 1 基本环境准备
        //1.1 流处理环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //1.2 设置并行度
        env.setParallelism(4);

        //TODO 2 检查点设置(略)

        //TODO 3 从kafka中读取数据
        //3.1 声明消费主题以及消费者组
        String orderInfoSourceTopic = "dwd_order_info";
        String orderDetailSourceTopic = "dwd_order_detail";
        String groupId = "order_wide_app_group";

        //3.2 获取kafka消费者对象
        // 订单
        FlinkKafkaConsumer<String> orderInfoKafkaSource = MyKafkaUtil.getKafkaSource(orderInfoSourceTopic, groupId);
        // 订单明细
        FlinkKafkaConsumer<String> orderDetailKafkaSource = MyKafkaUtil.getKafkaSource(orderDetailSourceTopic, groupId);
        //3.3 读取数据,封装为流
        // 订单流
        DataStreamSource<String> orderInfoStrDS = env.addSource(orderInfoKafkaSource);
        // 订单明细流
        DataStreamSource<String> orderDetailStrDS = env.addSource(orderDetailKafkaSource);

        //TODO 4 对流中数据类型进行转换 String -> 实体对象
        //订单
        SingleOutputStreamOperator<OrderInfo> orderInfoDS = orderInfoStrDS.map(
                new RichMapFunction<String, OrderInfo>() {
                    private SimpleDateFormat sdf;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    }

                    @Override
                    public OrderInfo map(String jsonStr) throws Exception {
                        OrderInfo orderInfo = JSON.parseObject(jsonStr, OrderInfo.class);
                        orderInfo.setCreate_ts(sdf.parse(orderInfo.getCreate_time()).getTime());
                        return orderInfo;
                    }
                }
        );
        // 订单明细
        SingleOutputStreamOperator<OrderDetail> orderDetailDS = orderDetailStrDS.map(
                new RichMapFunction<String, OrderDetail>() {
                    private SimpleDateFormat sdf;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    }

                    @Override
                    public OrderDetail map(String jsonStr) throws Exception {
                        OrderDetail orderDetail = JSON.parseObject(jsonStr, OrderDetail.class);
                        orderDetail.setCreate_ts(sdf.parse(orderDetail.getCreate_time()).getTime());
                        return orderDetail;
                    }
                }
        );

        orderInfoDS.print("订单信息:");
        orderDetailDS.print("订单明细:");

        env.execute();
    }
}
d 测试

启动zookeeper、kafka、maxwell、hdfs,等待退出安全模式,启动Hbase

在配置表table_process中添加两条数据,如下

source_table    operate_type  sink_type  sink_table          sink_columns  sink_pk  sink_extend  
     
order_detail    insert        kafka      dwd_order_detail    id,order_id,sku_id,sku_name,img_url,order_price,sku_num,create_time,source_type,source_id,split_total_amount,split_activity_amount,split_coupon_amount                                                                id       (NULL)       
order_info      insert        kafka      dwd_order_info      id,consignee,consignee_tel,total_amount,order_status,user_id,payment_way,delivery_address,province_id,activity_reduce_amount,coupon_reduce_amount,original_total_amount,feight_fee,feight_fee_reduce,refundable_time  id       (NULL)          

启动BaseDBApp、OrderWideApp,模拟生成业务数据,观察结果。

  • 执行流程

业务数据生成**->Maxwell同步->Kafka的ods_base_db_m主题->BaseDBApp分流写回kafka->dwd_order_info和dwd_order_detail->**OrderWideApp从kafka的dwd层读数据,打印输出

(2)订单和订单明细关联(双流join)

在flink中的流join大体分为两种,一种是基于时间窗口的join(Time Windowed Join),比如join、coGroup等。另一种是基于状态缓存的join(Temporal Table Join),比如intervalJoin。

这里选用intervalJoin,因为相比较窗口join,intervalJoin使用更简单,而且避免了应匹配的数据处于不同窗口的问题。intervalJoin目前只有一个问题,就是还不支持left join。

但是这里订单主表与订单从表之间的关联不需要left join,所以intervalJoin是较好的选择。

详情见官网关于窗口的说明

Interval join 组合元素的条件为:两个流(我们暂时称为 A 和 B)中 key 相同且 B 中元素的 timestamp 处于 A 中元素 timestamp 的一定范围内。

这个条件可以更加正式地表示为 b.timestamp ∈ [a.timestamp + lowerBound; a.timestamp + upperBound]a.timestamp + lowerBound <= b.timestamp <= a.timestamp + upperBound

这里的 a 和 b 为 A 和 B 中共享相同 key 的元素。上界和下界可正可负,只要下界永远小于等于上界即可。 Interval join 目前仅执行 inner join。

当一对元素被传递给 ProcessJoinFunction,他们的 timestamp 会从两个元素的 timestamp 中取最大值 (timestamp 可以通过 ProcessJoinFunction.Context 访问)。

Interval join 目前仅支持 event time。

intervalJoin连接数据的方式如下图:

在这里插入图片描述

上例中,我们 join 了橙色和绿色两个流,join 的条件是:以 -2 毫秒为下界、+1 毫秒为上界。 默认情况下,上下界也被包括在区间内,但 .lowerBoundExclusive().upperBoundExclusive() 可以将它们排除在外。

图中三角形所表示的条件也可以写成更加正式的表达式:

orangeElem.ts + lowerBound <= greenElem.ts <= orangeElem.ts + upperBound
a 设定事件时间水位线
// TODO 5 指定Watermark并提取事件时间字段
//订单
SingleOutputStreamOperator<OrderInfo> orderInfoWithWatermarkDS = orderInfoDS.assignTimestampsAndWatermarks(
        WatermarkStrategy.<OrderInfo>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                .withTimestampAssigner(
                        new SerializableTimestampAssigner<OrderInfo>() {
                            @Override
                            public long extractTimestamp(OrderInfo orderInfo, long recordTimestamp) {
                                return orderInfo.getCreate_ts();
                            }
                        }
                )
);
// 订单明细
SingleOutputStreamOperator<OrderDetail> orderDetailWithWatermarkDS = orderDetailDS.assignTimestampsAndWatermarks(
        WatermarkStrategy.<OrderDetail>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                .withTimestampAssigner(
                        new SerializableTimestampAssigner<OrderDetail>() {
                            @Override
                            public long extractTimestamp(OrderDetail orderDetail, long recordTimestamp) {
                                return orderDetail.getCreate_ts();
                            }
                        }
                )
);
b 创建合并后的宽表实体类
package com.hzy.gmall.realtime.beans;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.lang3.ObjectUtils;

import java.math.BigDecimal;

/**
 * Desc: 订单和订单明细关联宽表对应实体类
 */
@Data
@AllArgsConstructor
public class OrderWide {
    Long detail_id;
    Long order_id ;
    Long sku_id;
    BigDecimal order_price ;
    Long sku_num ;
    String sku_name;
    Long province_id;
    String order_status;
    Long user_id;

    BigDecimal total_amount;
    BigDecimal activity_reduce_amount;
    BigDecimal coupon_reduce_amount;
    BigDecimal original_total_amount;
    BigDecimal feight_fee;
    BigDecimal split_feight_fee;
    BigDecimal split_activity_amount;
    BigDecimal split_coupon_amount;
    BigDecimal split_total_amount;

    String expire_time;
    String create_time;
    String operate_time;
    String create_date; // 把其他字段处理得到
    String create_hour;

    String province_name;//查询维表得到
    String province_area_code;
    String province_iso_code;
    String province_3166_2_code;

    Integer user_age ;
    String user_gender;

    Long spu_id;     //作为维度数据 要关联进来
    Long tm_id;
    Long category3_id;
    String spu_name;
    String tm_name;
    String category3_name;

    public OrderWide(OrderInfo orderInfo, OrderDetail orderDetail){
        mergeOrderInfo(orderInfo);
        mergeOrderDetail(orderDetail);

    }

    // 将订单的信息赋值给订单宽表
    public void  mergeOrderInfo(OrderInfo orderInfo  )  {
        if (orderInfo != null) {
            this.order_id = orderInfo.id;
            this.order_status = orderInfo.order_status;
            this.create_time = orderInfo.create_time;
            this.create_date = orderInfo.create_date;
            this.activity_reduce_amount = orderInfo.activity_reduce_amount;
            this.coupon_reduce_amount = orderInfo.coupon_reduce_amount;
            this.original_total_amount = orderInfo.original_total_amount;
            this.feight_fee = orderInfo.feight_fee;
            this.total_amount =  orderInfo.total_amount;
            this.province_id = orderInfo.province_id;
            this.user_id = orderInfo.user_id;
        }
    }

    // 将订单明细的信息赋值给订单宽表
    public void mergeOrderDetail(OrderDetail orderDetail  )  {
        if (orderDetail != null) {
            this.detail_id = orderDetail.id;
            this.sku_id = orderDetail.sku_id;
            this.sku_name = orderDetail.sku_name;
            this.order_price = orderDetail.order_price;
            this.sku_num = orderDetail.sku_num;
            this.split_activity_amount=orderDetail.split_activity_amount;
            this.split_coupon_amount=orderDetail.split_coupon_amount;
            this.split_total_amount=orderDetail.split_total_amount;
        }
    }

    // firstNonNull获取参数中第一个不为空的值
    public void mergeOtherOrderWide(OrderWide otherOrderWide){
        this.order_status = ObjectUtils.firstNonNull( this.order_status ,otherOrderWide.order_status);
        this.create_time =  ObjectUtils.firstNonNull(this.create_time,otherOrderWide.create_time);
        this.create_date =  ObjectUtils.firstNonNull(this.create_date,otherOrderWide.create_date);
        this.coupon_reduce_amount =  ObjectUtils.firstNonNull(this.coupon_reduce_amount,otherOrderWide.coupon_reduce_amount);
        this.activity_reduce_amount =  ObjectUtils.firstNonNull(this.activity_reduce_amount,otherOrderWide.activity_reduce_amount);
        this.original_total_amount =  ObjectUtils.firstNonNull(this.original_total_amount,otherOrderWide.original_total_amount);
        this.feight_fee = ObjectUtils.firstNonNull( this.feight_fee,otherOrderWide.feight_fee);
        this.total_amount =  ObjectUtils.firstNonNull( this.total_amount,otherOrderWide.total_amount);
        this.user_id =  ObjectUtils.<Long>firstNonNull(this.user_id,otherOrderWide.user_id);
        this.sku_id = ObjectUtils.firstNonNull( this.sku_id,otherOrderWide.sku_id);
        this.sku_name =  ObjectUtils.firstNonNull(this.sku_name,otherOrderWide.sku_name);
        this.order_price =  ObjectUtils.firstNonNull(this.order_price,otherOrderWide.order_price);
        this.sku_num = ObjectUtils.firstNonNull( this.sku_num,otherOrderWide.sku_num);
        this.split_activity_amount=ObjectUtils.firstNonNull(this.split_activity_amount);
        this.split_coupon_amount=ObjectUtils.firstNonNull(this.split_coupon_amount);
        this.split_total_amount=ObjectUtils.firstNonNull(this.split_total_amount);
    }
}
c 设定关联的key
// TODO 6 通过分组指定两流的关联字段 -- order_id
// 订单
KeyedStream<OrderInfo, Long> orderInfoKeyedDS = orderInfoWithWatermarkDS.keyBy(OrderInfo::getId);
// 订单明细
KeyedStream<OrderDetail, Long> orderDetailkeyedDS = orderDetailWithWatermarkDS.keyBy(OrderDetail::getOrder_id);
d 订单和订单明细关联 intervalJoin

这里设置了正负5秒,以防止在业务系统中主表与从表保存的时间差。

// TODO 7 双流join,使用intervalJoin
// 用订单(一)join订单明细(多)
SingleOutputStreamOperator<OrderWide> orderWideDS = orderInfoKeyedDS
        .intervalJoin(orderDetailkeyedDS)
        .between(Time.seconds(-5), Time.seconds(5))
        .process(
                new ProcessJoinFunction<OrderInfo, OrderDetail, OrderWide>() {
                    @Override
                    public void processElement(OrderInfo orderInfo, OrderDetail orderDetail, Context ctx, Collector<OrderWide> out) throws Exception {
                        out.collect(new OrderWide(orderInfo, orderDetail));
                    }
                }
        );
orderWideDS.print(">>>");
e 测试

目前以完成工作

  • 基本环境准备

  • 设置检查点

  • 从kafka中读取两条流数据并在转换结构的时候补充创建时间(create_ts)

  • 通过keyby设置关联字段 – order_id

  • 双流join

    A.intervalJoin(B)
     .between(下界,上界)
     .process()
    
  • 测试,同2(1)d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
数据仓库(Data Warehouse)简称DW或DWH,是数据库的一种概念上的升级,可以说是为满足新需求设计的一种新数据库,而这个数据库是需容纳更多的数据,更加庞大的数据集,从逻辑上讲数据仓库和数据库是没有什么区别的。为企业所有级别的决策制定过程,提供所有类型数据支撑的战略集合,主要是用于数据挖掘和数据分析,以建立数据沙盘为基础,为消灭消息孤岛和支持决策为目的而创建的。 数据仓库的应用 1.数据分析、数据挖掘、人工智能、机器学习、风险控制、无人驾驶。 2.数据化运营、精准运营。 3.广告精准、智能投放。 随着我们从IT时代步入DT时代,数据积累量也与日俱增,同时伴随着互联网的发展,越来越多的应用场景产生,传统的数据处理、存储方式已经不能满足日益增长的需求。而互联网行业相比传统行业对新生事物的接受度更高、应用场景更复杂, 因此基于大数据构建的数据仓库先在互联网行业得到了尝试。 高性能高扩展的亿级电商全端实时数据仓库全实现(PC、移动、小程序) ,以热门的互联网电商实际业务应用场景为案例讲解,对电商数据仓库的常见实战指标以及难点实战指标进行了详尽讲解,具体指标包括:每日、月大盘收入报、高付费用户分析报、流量域多方位分析、营销域多方位分析、实时排行榜指标分析、用户主题分析、店铺主题时间区间分析等,数据分析涵盖全端(PC、移动、小程序)应用,与互联网企业大数据技术同步,让大家能够真正学到大数据企业级数据仓库的实战经验。本课程凝聚讲师多年一线大数据企业实际项目经验,大数据企业在职架构师亲自授课,全程实操代码,带你体验真实的大数据开发过程,代码现场调试。通过本课程的学习再加上老师的答疑,你完全可以将本案例直接应用于企业。本套课程可以满足世面上绝大多数大数据企业级的数据仓库业务场景,全部代码可以直接部署企业,支撑亿级并发数据分析。该项目代码也是具有极高的商业价值的,大家可以根据自己的业务进行修改,便可以使用。本课程包含的技术:  开发工具为:IDEA、WebStorm Flink1.9.0 Greenplum5.0.0 Hadoop2.6.0 Hbase1.0.0 Kafka2.1.0 Hive1.1.0 HDFS、MapReduce Redis、Flume Sqoop、Zookeeper MyBatis、EhCache SpringBoot2.0.2.RELEASE SpringCloud Finchley.RELEASE Binlog、Canal MySQL、MyCat Vue.js、Nodejs Highcharts课程亮点: 1.与企业对接、真实工业界产品  2.支持海量数据的分析 3.支持全端实时数据分析 4.通用数据仓库分层解决方案 5.数据库实时同步解决方案 6.主流微服务后端系统 7.电商数据仓库实战指标 8.实时加离线多方位分析 9.互联网大数据企业热门技术栈 10.分布式数据库存储解决方案 11.涵盖主流前端技术VUE+jQuery+Ajax+NodeJS 12.大数据热门技术Flink新版本13.集成SpringCloud实现统一整合方案 14.全程代码实操,提供全部代码和资料 15.提供答疑和提供企业技术方案咨询企业一线架构师讲授,代码企业直接复用,提供企业解决方案。  版权归作者所有,盗版将进行法律维权。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OneTenTwo76

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值