一 DWS层-访客主题计算
1 写入OLAP数据库
(1)增加ClickhouseUtil
a JdbcSink.sink( )的四个参数说明
- 参数1: 传入Sql,格式如:insert into xxx values(?,?,?,?)
- 参数2: 可以用lambda表达实现(jdbcPreparedStatement, t) -> t为数据对象,要装配到语句预编译器的参数中。
- 参数3:设定一些执行参数,比如重试次数,批次大小。
- 参数4:设定连接参数,比如地址,端口,驱动名。
b ClickhouseUtil中获取JdbcSink函数的实现
package com.hzy.gmall.realtime.utils;
/**
* 操作ClickHouse的工具类
*/
public class ClickhouseUtil {
public static <T> SinkFunction<T> getJdbcSink(String sql){
// "insert into visitor_stats_2022 values(?,?,?,?,?,?,?,?,?,?,?,?)"
SinkFunction<T> sinkFunction = JdbcSink.<T>sink(
sql,
new JdbcStatementBuilder<T>() {
// 参数 T obj:就是流中的一条数据
@Override
public void accept(PreparedStatement ps, T obj) throws SQLException {
// 获取流中obj属性值,赋值给问号
}
},
// 构造者设计模式
new JdbcExecutionOptions.Builder()
// 5条数据为一批,一批处理一次
// 4个并行度,每一个slot数量到5,才会保存到ClickHouse
.withBatchSize(5)
// // 2s后直接保存到ClickHouse
// .withBatchIntervalMs(2000)
// // 最大重试次数为3
// .withMaxRetries(3)
.build(),
new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
.withDriverName("ru.yandex.clickhouse.ClickHouseDriver")
.withUrl(GmallConfig.CLICKHOUSE_URL)
.build()
);
return sinkFunction;
}
}
c 构造者设计模式
在设置一个对象属性值时,通常方式有两种:
第一种
Animal animal=new Animal("XX","XXkg","XX");
第二种
Animal animal=new Animal();
animal.setAge("XX");
animal.setweight("XXkg");
animal.setFood("XX");
- 第一种方式::相当于在构造函数里传递参数,但这样加入参数的时候,如果属性过多,不能明确的知道往这个对象里加入了哪种属性的内容。
- 第二种方式:虽然可以根据set函数名看到将要设置的值是什么值,但是这种写法,略显冗余。
在设计模式中有构造者模式(builder),在类的构造器或静态工厂具有多个参数。设计这种类时,builder模式就是个不错的选择。
构造者就是在外部类中创建的一个内部类,然后在内部类中对属性赋值,一次赋值完成后,返回的类型是内部类自己,而上面的第二种赋值方式没有返回值,不可以链式赋值,而构造者模式可以进行链式赋值。在经过许多步赋值操作之后,通过.build()方法通过内部类去创建外部类对象。
d 赋值给问号占位符并创建TransientSink注解
该注解标记不需要保存的字段。
由于之前的ClickhouseUtil工具类的写入机制就是把该实体类的所有字段按次序一次写入数据表。但是实体类有时会用到一些临时字段,计算中有用但是并不需要最终保存在临时表中。我们可以把这些字段做一些标识,然后再写入的时候判断标识来过滤掉这些字段。
为字段打标识通常的办法就是给字段加个注解,这里就增加一个自定义注解@TransientSink来标识该字段不需要保存到数据表中。
new JdbcStatementBuilder<T>() {
// 参数 T obj:就是流中的一条数据
// 获取流中对象obj的属性值,赋值给问号占位符
@Override
public void accept(PreparedStatement ps, T obj) throws SQLException {
// 获取流中对象所属类的属性
Field[] fields = obj.getClass().getDeclaredFields();
int skipNum = 0;
// 对属性数组进行遍历
for (int i = 0; i < fields.length; i++) {
// 获取每一个属性对象
Field field = fields[i];
// 判断该属性是否有@trannsient注解修饰
TransientSink transientSink = field.getAnnotation(TransientSink.class);
if (transientSink != null){
skipNum++;
continue;
}
// 设置私有属性的访问权限
field.setAccessible(true);
try {
// 获取对象的属性值
Object fieldValue = field.get(obj);
// 将属性的值赋值给问号占位符
// JDBC相关操作和查询结果集的列从1开始
ps.setObject(i+1-skipNum,fieldValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
e 在GmallConfig中配置ClickHouse的连接地址
public static final String CLICKHOUSE_URL = "jdbc:clickhouse://hadoop101:8123/default";
f 为主程序增加写入ClickHouse的Sink
// TODO 9 将聚合统计之后的数据写到ClickHouse
reduceDS.addSink(
ClickhouseUtil.getJdbcSink("insert into visitor_stats_2022 values(?,?,?,?,?,?,?,?,?,?,?,?)")
);
(2)整体测试
- 启动ZK、Kafka、logger.sh、ClickHouse、【HDFS】
- 运行BaseLogApp
- 运行UniqueVisitApp
- 运行UserJumpDetailApp
- 运行VisitorStatsApp
- 运行rt_applog目录下的jar包
- 查看控制台输出
- 查看ClickHouse中visitor_stats_2022表数据
ClickHouse中数据如下:
二 DWS层-商品主题计算
统计主题 | 需求指标 | 输出方式 | 计算来源 | 来源层级 |
---|---|---|---|---|
商品 | 点击 | 多维分析 | page_log | dwd |
曝光 | 多维分析 | page_log | dwd | |
收藏 | 多维分析 | favor_info | dwd | |
加入购物车 | 多维分析 | cart_info | dwd | |
下单 | 可视化大屏 | order_wide | dwm | |
支付 | 多维分析 | payment_wide | dwm | |
退款 | 多维分析 | order_refund_info | dwd | |
评价 | 多维分析 | comment_info | dwd |
与访客的dws层的宽表类似,也是把多个事实表的明细数据汇总起来组合成宽表
1 需求分析与思路
- 从Kafka主题中获得数据流
- 把Json字符串数据流转换为统一数据对象的数据流
- 把统一的数据结构流合并为一个流
- 设定事件时间与水位线
- 分组、开窗、聚合
- 写入ClickHouse
整体流程如下图: