Flink实时数仓项目—DWD层设计与实现

Flink实时数仓项目—DWD层设计与实现

  • 前言
  • 一、功能三:订单宽表
    • 1.需求描述
    • 2.需求分析
    • 3.思路分析与代码实现
      • 3.1 实体类的创建
      • 3.2 读取Kafka订单数据和订单明细数据
      • 3.3 双流Join—关联事实表
      • 3.4 关联维度表
  • 二、优化:关联维度表优化—旁路缓存
    • 1.旁路缓存选型
    • 2.Redis设计
    • 3.代码实现
  • 三、优化:关联维度表优化—异步查询
    • 1.优化原因
    • 2.优化方法
    • 3.代码实现


前言

前面已经完成了两个功能,下面实现订单宽表的功能,比较复杂,所以单独列出来。


一、功能三:订单宽表

1.需求描述

订单在电商中属于比较重要的分析对象,关于订单也有许多的维度统计需求,比如用户、地区、商品、品类、品牌等等。
为了方便以后的统计,减少表的关联,所以提前在DWM层将订单相关的数据整合成一张订单的宽表。

2.需求分析

首先,要思考到底要将哪些数据和订单整合在一起?
订单表和订单明细表具有非常强烈的关联关系,这是两张事实表,这两张表中还有一些维度外键,所以还需要整合这些维度表,如下图:
Flink实时数仓项目—DWD层设计与实现_第1张图片
上图中,订单表和订单明细表属于事实数据,它们存放在Kafka的DWD相关主题中。地区表、用户表、品牌表、分类表、SPU表和商品表属于维度数据,它们存放在HBase中。
从上面可以看出来,我们总共需要做两件事:
1)将两张事实表关联起来(实际上就是流与流之间的关联)
2)将事实表关联后的数据跟维度表关联起来(实际上就是流计算中去HBase中查询相关的数据)

3.思路分析与代码实现

3.1 实体类的创建

我们从Kafka中读取出来订单表和订单明细表的数据需要用一个类型来接受,如果还用JSON对象来处理,就比较麻烦,所以为这两个表分别创建一个对应的实体类。
同时,订单表关联形成的宽表也需要建一个对应的实体类。
订单表实体类如下:
因为后面要提取时间戳生成水位线,所以为了方便起见,在实体类中增加了一个时间戳字段

@Data
public class OrderInfo {
   
    //订单id
    Long id;
    //订单省份id
    Long province_id;
    //订单状态
    String order_status;
    //用户的id
    Long user_id;
    //实付总金额
    BigDecimal total_amount;
    //活动减免金额
    BigDecimal activity_reduce_amount;
    //优惠券减免金额
    BigDecimal coupon_reduce_amount;
    //原始总金额
    BigDecimal original_total_amount;
    //运输费用
    BigDecimal feight_fee;
    //到期时间
    String expire_time;
    //创建时间,格式为 yyyy-MM-dd HH:mm:ss
    String create_time;
    //修改时间
    String operate_time;
    //创建日期(通过创建时间格式化得到,格式为:yyyy-MM-dd)
    String create_date; // 把其他字段处理得到
    //创建的小时
    String create_hour;
    //时间戳,方便提取时间戳生成水位线
    Long create_ts;
}

订单明细表实体类如下:
同理,有一个时间戳字段

@Data
public class OrderDetail {
   
    //明细id
    Long id;
    //订单id
    Long order_id;
    //商品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;
}

订单宽表实体类如下:

//订单宽表需要订单表、订单明细表以及一些维度表中的数据
@Data
@AllArgsConstructor
public class OrderWide {
   

    //从订单表提取出来的字段,一般都是id
    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; //yyyy-MM-dd HH:mm:ss
    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;

    //sku的维度信息
    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.create_hour = orderInfo.create_hour;
            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;
        }
    }
}

3.2 读取Kafka订单数据和订单明细数据

读取Kafka的订单数据的和订单明细数据,因为数据可能发生乱序,而间隔连结支持处理时间语义的乱序,所以在读取时提取数据的时间戳并生成水位线,方便处理乱序数据。

    //2、读取Kafka的数据  转换为JavaBean对象并提取时间戳
    String orderInfoSourceTopic = "dwd_order_info";
    String orderDetailSourceTopic = "dwd_order_detail";
    String orderWideSinkTopic = "dwm_order_wide";
    String groupId = "order_wide_group";
    //读取Kafka中订单主题的数据
    SingleOutputStreamOperator<OrderInfo> orderInfoDS = env.addSource(MyKa

你可能感兴趣的:(大数据项目,Flink,实时数仓,大数据,数据仓库,flink)