Flink实时数仓项目—DWD层设计与实现
前言
前面已经完成了两个功能,下面实现订单宽表的功能,比较复杂,所以单独列出来。
一、功能三:订单宽表
1.需求描述
订单在电商中属于比较重要的分析对象,关于订单也有许多的维度统计需求,比如用户、地区、商品、品类、品牌等等。
为了方便以后的统计,减少表的关联,所以提前在DWM层将订单相关的数据整合成一张订单的宽表。
2.需求分析
首先,要思考到底要将哪些数据和订单整合在一起?
订单表和订单明细表具有非常强烈的关联关系,这是两张事实表,这两张表中还有一些维度外键,所以还需要整合这些维度表,如下图:
上图中,订单表和订单明细表属于事实数据,它们存放在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 <