1.分流維度表sink到hbase
上一篇的結果是維度資料在側輸出流hbaseDs,事實資料在主流filterDs中,如下:
//5.動態分流,事實表寫會kafka,維度表寫入hbase
OutputTag<JSONObject> hbaseTag = new OutputTag<JSONObject>(TableProcess.SINK_TYPE_HBASE){};
//建立自定義mapFunction函式
SingleOutputStreamOperator<JSONObject> kafkaTag = filterDs.process(new TableProcessFunction(hbaseTag));
DataStream<JSONObject> hbaseDs = kafkaTag.getSideOutput(hbaseTag);
filterDs.print("json str --->>");
處理流程如下:
自定義RickSinkFunction類:DimSink.java
-
初始化phoenix連線
-
儲存資料
1.1 配置
在BaseDbTask任務中,我們已經獲取到hbase的輸出流,然後就可以開始hbase的一系列操作了。
新增phoenix依賴包
<!-- phoenix -->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-spark</artifactId>
<version>5.0.0-HBase-2.0</version>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
修改hbase-site.xml,因為要用單獨的 schema,所以在 Idea 程式中也要加入 hbase-site.xml
為了開啟 hbase 的 namespace 和 phoenix 的 schema 的對映,在程式中需要加這個配置檔案,另外在 linux 服務上,也需要在 hbase 以及 phoenix 的 hbase-site.xml 配置檔案中,加上以上兩個配置,並使用 xsync 進行同步。
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://hadoop101:9000/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>hadoop101,hadoop102,hadoop103</value>
</property>
<property>
<name>hbase.table.sanity.checks</name>
<value>false</value>
</property>
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
</configuration>
1.2 建立名稱空間
在phoenix中執行
create schema GMALL_REALTIME;
1.3 DimSink.java
自定義addSink類
package com.zhangbao.gmall.realtime.app.func;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Strings;
import com.zhangbao.gmall.realtime.common.GmallConfig;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author: zhangbao
* @date: 2021/9/4 12:23
* @desc: 將維度表寫入hbase中
**/
@Log4j2
public class DimSink extends RichSinkFunction<JSONObject> {
private Connection conn = null;
@Override
public void open(Configuration parameters) throws Exception {
log.info("建立 phoenix 連線...");
Class.forName("org.apache.phoenix.jdbc.PhoenixDriver");
conn = DriverManager.getConnection(GmallConfig.PHOENIX_SERVER);
log.info("phoenix 連線成功!");
}
@Override
public void invoke(JSONObject jsonObject, Context context) throws Exception {
String sinkTable = jsonObject.getString("sink_table");
JSONObject data = jsonObject.getJSONObject("data");
PreparedStatement ps = null;
if(data!=null && data.size()>0){
try {
//生成phoenix的upsert語句,這個包含insert和update操作
String sql = generateUpsert(data,sinkTable.toUpperCase());
log.info("開始執行 phoenix sql -->{}",sql);
ps = conn.prepareStatement(sql);
ps.executeUpdate();
conn.commit();
log.info("執行 phoenix sql 成功");
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("執行 phoenix sql 失敗!");
}finally {
if(ps!=null){
ps.close();
}
}
}
}
//生成 upsert sql
private String generateUpsert(JSONObject data, String sinkTable) {
StringBuilder sql = new StringBuilder();
//upsert into scheme.table(id,name) values('11','22')
sql.append("upsert into "+GmallConfig.HBASE_SCHEMA+"."+sinkTable+"(");
//拼接列名
sql.append(StringUtils.join(data.keySet(),",")).append(")");
//填充值
sql.append("values('"+ StringUtils.join(data.values(),"','")+"')");
return sql.toString();
}
}
然後在主程式中加入
//6. 將維度表寫入hbase中
hbaseDs.addSink(new DimSink());
1.4 測試
-
需要啟動的服務
hdfs、zk、kafka、Maxwell、hbase,BaseDbTask.java
-
修改配置資料:gmall2021_realtime.table_process
INSERT INTO `gmall2021_realtime`.`table_process` (`source_table`, `operate_type`, `sink_type`, `sink_table`, `sink_columns`, `sink_pk`, `sink_extend`) VALUES ('base_trademark', 'insert', 'hbase', 'dim_base_trademark', 'id,tm_name', 'id', NULL);
此條配置資料代表,如果表base_trademark有插入資料,就把資料同步到hbase中,自動建表,作為維度資料。
-
修改業務庫中表資料:gmall2021.base_trademark
INSERT INTO `gmall2021`.`base_trademark` (`id`, `tm_name`, `logo_url`) VALUES ('15', '55', '55');
-
檢視phoenix資料:
select * from GMALL_REALTIME.BASE_TRADEMARK;
資料已經實時同步到hbase中。
2.分流事實表sink到kafka
2.1 MyKafkaUtil定義新方法
在MyKafkaUtil中定義新的生產者方法,可動態指定topic,如果不指定則生產到預設topic:default_data
/**
* 動態生產到不同的topic,如果不傳topic,則自動生產到預設的topic
* @param T 序列化後的資料,可指定topic
*/
public static <T> FlinkKafkaProducer<T> getKafkaBySchema(KafkaSerializationSchema<T> T){
Properties pros = new Properties();
pros.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,KAFKA_HOST);
return new FlinkKafkaProducer<T>(DEFAULT_TOPIC,T,pros,FlinkKafkaProducer.Semantic.EXACTLY_ONCE);
}
在主任務BaseDbTask中使用
//7. 將事實資料寫回到kafka
FlinkKafkaProducer<JSONObject> kafkaBySchema = MyKafkaUtil.getKafkaBySchema(new KafkaSerializationSchema<JSONObject>() {
@Override
public void open(SerializationSchema.InitializationContext context) throws Exception {
System.out.println("kafka serialize open");
}
@Override
public ProducerRecord<byte[], byte[]> serialize(JSONObject jsonObject, @Nullable Long aLong) {
String sinkTopic = jsonObject.getString("sink_table");
return new ProducerRecord<>(sinkTopic, jsonObject.getJSONObject("data").toString().getBytes());
}
});
kafkaTag.addSink(kafkaBySchema);
2.2 測試
-
需要啟動的服務
hdfs、zk、kafka、Maxwell、hbase,BaseDbTask.java
-
修改配置資訊:gmall2021_realtime.table_process
INSERT INTO `gmall2021_realtime`.`table_process` (`source_table`, `operate_type`, `sink_type`, `sink_table`, `sink_columns`, `sink_pk`, `sink_extend`) VALUES ('order_info', 'insert', 'kafka', 'dwd_order_info', 'id,consignee,consignee_tel,total_amount,order_status,user_id,payment_way,delivery_address,order_comment,out_trade_no,trade_body,create_time,operate_time,expire_time,process_status,tracking_no,parent_order_id,img_url,province_id,activity_reduce_amount,coupon_reduce_amount,original_total_amount,feight_fee,feight_fee_reduce,refundable_time', 'id', NULL);
表示表order_info有插入資料,就會同步到kafka中,topic為dwd_order_info。
-
啟動kafka消費者,檢視是否有資料進來
[zhangbao@hadoop101 root]$ cd /opt/module/kafka/bin/
[zhangbao@hadoop101 bin]$ ./kafka-console-consumer.sh --bootstrap-server hadoop101:9092,hadoop102:9092,hadoop103:9092 --topic dwd_order_info
-
最後啟動業務資料生成服務:mock-db-0.0.1-SNAPSHOT.jar
記得先修改配置檔案的生成日期:2021-09-12
最後檢視kafka消費者可以看到有資料產生,說明流程已經走通。
function | 可轉換結構 | 可過濾資料 | 側輸出 | open | 可以使用狀態 | 輸出至 |
---|---|---|---|---|---|---|
MapFunction | Yes | 下游運算元 | ||||
FilterFunction | Yes | 下游運算元 | ||||
RichMapFunction | Yes | Yes | Yes | 下游運算元 | ||
RichFilterFunction | Yes | Yes | Yes | 下游運算元 | ||
ProcessFunction | Yes | Yes | Yes | Yes | Yes | 下游運算元 |
SinkFunction | Yes | Yes | 外部 | |||
RichSinkFunction | Yes | Yes | Yes | Yes | 外部 |