Flink 流表与维表join(流批join)——使用异步IO(async io)实现

Flink流批join简单demo介绍
博客介绍了Flink流批join,此前文章讲过流流join和翻车的流批join。此次给出简单demo,模拟了MySQL维度表和Kafka点击流数据,展示了异步io函数、主函数及结果,但未提及与异步io相关的很多细节和参数,还给出了参考文章和官网链接。

前言

之前的文章中讲过了Flink 流流join

也讲过了翻车版本的流批join。为什么翻车了,那篇文章也说了。如果事实表和维度表进行join,Flink会认为这是一个批处理程序。也就是说程序会自己暂停。

流批join的需求还蛮多的,比如我们有一张用户点击网站的数据,还有一张用户表在MySQL中,我们需要关联MySQL中的数据来丰富实时流数据,这就需要用到流批join了。

 

数据

MySQL数据

模拟维度表。

Kafka数据

模拟点击流。

 

代码

异步io函数

package it.kenn.asyncio;


import it.kenn.pojo.Click;
import org.apache.flink.api.java.tuple.Tuple5;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.async.ResultFuture;
import org.apache.flink.streaming.api.functions.async.RichAsyncFunction;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;


/**
 * @date 2020-12-27
 * 异步io测试
 * 参考文档:https://www.cnblogs.com/zz-ksw/p/13228642.html
 */
public class AsyncDatabaseRequest extends RichAsyncFunction<Click, Tuple5<String, Integer,String, String,String>> {

    private transient Connection client;

    @Override
    public void open(Configuration parameters) throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        client = DriverManager.getConnection("jdbc:mysql://localhost:3306/aspirin?useSSL=false", "root", "root");
        client.setAutoCommit(false);
    }

    @Override
    public void close() throws Exception {
        client.close();
    }

    @Override
    public void asyncInvoke(Click input, ResultFuture<Tuple5<String, Integer,String, String,String>> resultFuture) throws Exception {
        List<Tuple5<String, Integer, String, String, String>> list = new ArrayList<>();
        Statement statement = client.createStatement();
        ResultSet resultSet = statement.executeQuery("select user_name, age, gender from user_data_for_join where user_name= '" + input.getUser() + "'");

        if (resultSet != null && resultSet.next()) {
            String name = resultSet.getString("user_name");
            int age = resultSet.getInt("age");
            String gender = resultSet.getString("gender");
            Tuple5<String, Integer,String, String,String> res = Tuple5.of(name, age, gender, input.getSite(), input.getTime());
            list.add(res);
        }

        // 将数据搜集
        resultFuture.complete(list);
    }

    @Override
    public void timeout(Click input, ResultFuture<Tuple5<String, Integer,String, String,String>> resultFuture) throws Exception {

    }
}

主函数

 

package it.kenn.asyncio;

import it.kenn.pojo.Click;
import org.apache.flink.api.java.tuple.Tuple5;
import org.apache.flink.streaming.api.datastream.AsyncDataStream;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

import java.util.concurrent.TimeUnit;

import static org.apache.flink.table.api.Expressions.$;

/**
 * 利用异步io实现事实表与维表的join操作
 */
public class AsyncJoin {

    private static String kafkaTable = "CREATE TABLE KafkaTable (\n" +
            "  `user` STRING,\n" +
            "  `site` STRING,\n" +
            "  `time` STRING\n" +
            ") WITH (\n" +
            "  'connector' = 'kafka',\n" +
            "  'topic' = 'test-old',\n" +
            "  'properties.bootstrap.servers' = 'localhost:9092',\n" +
            "  'properties.group.id' = 'testGroup',\n" +
            "  'scan.startup.mode' = 'earliest-offset',\n" +
            "  'format' = 'json'\n" +
            ")";

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        StreamTableEnvironment tableEnvironment = StreamTableEnvironment.create(env);
        //注册表
        tableEnvironment.executeSql(kafkaTable);
        Table kafkaTable = tableEnvironment.from("KafkaTable").select($("user"), $("site"), $("time"));
        //将table转为datastream
        DataStream<Click> clickDataStream = tableEnvironment.toAppendStream(kafkaTable, Click.class);
        SingleOutputStreamOperator<Tuple5<String, Integer, String, String, String>> asyncStream = 
                //注意这里将事实表和维度表连接起来的是AsyncDataStream.unorderedWait方法
                AsyncDataStream.unorderedWait(clickDataStream, new AsyncDatabaseRequest(), 1000, TimeUnit.MILLISECONDS, 100);

        asyncStream.print("async:");
        env.execute("AsyncIOFunctionTest");
    }
}

结果

总结

上面这是一个简单的demo,与异步io相关的很多细节、参数等都还没有说到。

有参考文章:https://www.cnblogs.com/zz-ksw/p/13228642.html

和官网:https://ci.apache.org/projects/flink/flink-docs-release-1.12/dev/stream/operators/asyncio.html

 

 

 

 

 

 

<think>我们面对的问题是在FlinkSQL中实现式读取Paimon,并每天更新一次的Hive进行Join操作。由于Hive每天更新一次,我们需要考虑如何让式作业感知到Hive的更新。以下是详细的解决方案:###1. **理解需求**- **式数据源**:Paimon使用式读取(Streaming Read)。- ****:Hive,每天更新一次(例如,每天凌晨更新全量数据)。- **Join操作**:将式数据进行关联,并且当更新时,式作业应该能使用最新的数据。 ###2. **FlinkSQL中Join的常见方式**Flink SQL中外部Join通常有两种方式:- **预加载**:在作业启动时加载全量数据到内存,然后定时刷新(适用于小)。- **动态查找(Lookup Join)**:在每条数据到达时,根据Key去外部存储中查询(适用于大,但延迟较高)。由于Hive每天更新一次,我们可以考虑以下两种策略:- **策略一**:使用`LOOKUP`Join,并配置缓存策略,例如设置缓存超时时间为24小时,这样每天缓存失效后重新加载新数据。-**策略二**:将Hive定义为Flink的`Batch`,然后使用`Temporal TableJoin`(时间Join),通过处理时间(ProcessingTime)或事件时间(Event Time)来关联的版本。###3.**具体实现步骤** ####方案一:使用LOOKUPJoin(推荐)Flink SQL支持通过`LOOKUP`关键字定义连接,并可以设置缓存过期时间,让缓存的数据在指定时间后失效,重新加载新的数据。**步骤**:1.创建Paimon式读取。2.创建Hive,并设置`lookup.cache`相关参数。3.执行`LEFTJOIN`或`INNERJOIN`,并指定LOOKUP条件。**示例代码**:```sql--创建Paimon源式读取)CREATETABLE paimon_stream(id INT,name STRING,event_time TIMESTAMP(3),WATERMARK FORevent_timeAS event_time -INTERVAL'5' SECOND) WITH('connector'= 'paimon','path'= 'file:/path/to/paimon_table','streaming-source.enable'= 'true',--启用式读取'streaming-source.monitor-interval' ='1s' --检查间隔,根据实际情况调整);--创建Hive(注意:需要Hive Catalog)CREATE CATALOGmyhive WITH('type' ='hive','default-database'= 'default','hive-conf-dir'= '/path/to/hive/conf');USECATALOGmyhive;CREATETABLE hive_dim (idINT,infoSTRING,update_dateSTRING--用于标识数据更新日期的字段,例如'2024-01-01') WITH('connector'= 'hive','table-name'= 'default.hive_dim_table');--切换回默认catalog(假设Paimon在默认catalog中)USE CATALOGdefault_catalog;--执行LOOKUP JOINSELECTs.id,s.name,d.infoFROM paimon_streamAS sLEFT JOINhive_dimFOR SYSTEM_TIME ASOF s.event_timeAS dON s.id =d.idWITH ('lookup.cache.max-rows'= '10000',--缓存的最大行数'lookup.cache.ttl'= '24h'--缓存过期时间设为24小时);```**说明**:-`FORSYSTEM_TIMEAS OFs.event_time`:示根据数据的事件时间去查找对应时刻的版本(这里我们使用事件时间,但注意Hive处理,没有时间版本,因此通常使用处理时间)。对于Hive,由于它每天更新一次,我们更关心的是当前最新的数据,所以也可以使用处理时间(PROCTIME())来关联。-如果使用处理时间,可以这样写:```sqlSELECTs.id,s.name,d.infoFROM paimon_streamAS sLEFT JOINhive_dimFOR SYSTEM_TIME ASOF PROCTIME() ASdONs.id= d.idWITH('lookup.cache.max-rows' ='10000','lookup.cache.ttl' ='24h');```-设置`lookup.cache.ttl`为24小时,意味着缓存的数据在24小时后过期,这样每天都会重新加载新的数据。**注意事项**:-缓存策略:如果很大,缓存全部数据可能占用大量内存,因此需要合理设置`max-rows`。如果超过缓存大小,则超出部分不会缓存,每次都会查询Hive。-数据一致性:在缓存过期后重新加载时,可能会有一段时间使用旧数据,然后切换到新数据,这会导致短暂的不一致。但由于每天更新一次,且更新通常发生在低峰期,这种不一致可以接受。####方案二:使用TemporalTable Join(需要Hive有版本信息)如果Hive有更新时间字段(例如`update_date`或`update_time`),我们可以将其定义为时态(Temporal Table),然后根据数据的事件时间关联对应版本的。但是,由于Hive每天更新一次,我们通常只关心最新的全量数据,而不是历史版本。因此,这种方法并不常用,除非有多个历史版本且需要精确匹配事件时间。**步骤**:1.定义Hive,并指定时间属性和主键。2.在进行Join时,使用`FORSYSTEM_TIMEAS OF`语句指定时间点。**示例代码**:```sql--创建Hive,并定义一个处理时间字段(假设没有事件时间,我们使用处理时间)CREATE TABLEhive_dim(id INT,info STRING,update_time TIMESTAMP(3),PRIMARYKEY (id)NOT ENFORCED) WITH('connector'= 'hive','table-name'= 'default.hive_dim_table');--将Hive定义为时态CREATE VIEWdim_versionedASSELECT* FROMhive_dimFORSYSTEM_TIMEAS OFupdate_time;--注意:这里需要有代版本的时间字段--进行TemporalJoinSELECTs.id,s.name,d.infoFROM paimon_streamAS sLEFT JOINdim_versioned FORSYSTEM_TIMEAS OFs.event_time ASdONs.id= d.id;```**注意**:这种方法要求有代版本的时间字段(如更新时间戳),并且该字段能够覆盖所有记录的变化。但我们的场景是每天全量更新,所以每条记录的时间戳可能都是同一天的时间(或者没有时间戳),因此这种方法并不适用。我们更推荐使用方案一。###4.**更新策略**- **全量更新**:每天将整个Hive重新加载一次。在Flink中,通过设置缓存TTL为24小时,可以每天刷新一次缓存。- **增量更新**:如果Hive支持增量更新(例如有更新时间戳),我们可以只查询当天变化的记录。但Flink Hive连接器目前不支持增量Lookup,因此还是需要全量缓存。###5.**性能优化**-**缓存大小**:根据大小调整`lookup.cache.max-rows`,避免内存溢出。- **并行度**:如果很大,可以增加作业并行度,每个并行实例缓存部分数据(通过Key分布)。- **异步查询**:FlinkLookupJoin支持异步查询(`async`参数),可以提高吞吐量。但Hive连接器可能不支持异步查询,需要确认连接器能力。 ###6. **注意事项**-**Hive格式**:确保HiveFlink支持的格式(如ORC、Parquet)。- **HiveMetastore连接**:正确配置Hive Catalog,包括HiveMetastore的URI或配置文件。- **时间特性**:如果使用事件时间,确保数据有水印定义。###总结对于式读取Paimon并Join每天更新的Hive,推荐使用**LOOKUPJoin**,并设置缓存TTL为24小时。这样,缓存每天自动失效,重新加载最新数据。同时,根据大小调整缓存行数,避免内存问题。 §§相关问题§§1.在Flink SQL中,如何配置HiveCatalog?2.如果Hive非常大,如何优化Lookup Join的性能?3.除了Hive,FlinkSQL还支持哪些外部? 4.如何监控Flink作业中缓存的使用情况?</think>### 在Flink SQL中实现式读取Paimon并Join Hive(每日更新) #### 1. **核心方案:使用`LOOKUP JOIN` + 缓存刷新** 由于Hive每天更新一次,推荐通过**Flink Hive Catalog**注册,结合`LOOKUP JOIN`的缓存过期机制实现数据更新感知。 ```sql -- 注册Paimon式读取) CREATE TABLE paimon_stream ( id INT, product_id STRING, event_time TIMESTAMP(3), WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND ) WITH ( 'connector' = 'paimon', 'path' = 'hdfs:///path/to/paimon_table', 'streaming-source.enable' = 'true', -- 启用式读取 'streaming-source.monitor-interval' = '1 s' -- 检查间隔 ); -- 注册Hive(每日全量更新) CREATE TABLE hive_dim ( product_id STRING, product_name STRING, category STRING, update_date STRING -- 分区字段(如dt=20240501) ) PARTITIONED BY (update_date) WITH ( 'connector' = 'hive', 'table-name' = 'dbname.dim_table', 'partition' = 'update_date' -- 指定分区字段 ); -- 执行LOOKUP JOIN(关键配置) SELECT s.id, d.product_name, s.event_time FROM paimon_stream s LEFT JOIN hive_dim FOR SYSTEM_TIME AS OF s.event_time AS d ON s.product_id = d.product_id WITH ( 'lookup.cache.max-rows' = '10000', -- 缓存最大行数 'lookup.cache.ttl' = '24h', -- 缓存24小时后过期 'partition.expire-time' = '24 h' -- 分区过期时间 ); ``` #### 2. **关键配置说明** - **缓存过期机制**: - `lookup.cache.ttl=24h`:缓存数据24小时后自动失效,触发重新加载 - `partition.expire-time=24h`:自动清理旧分区(避免内存膨胀) - **分区策略**: - 需按日期分区(如`update_date=20240501`),每日更新新分区 - Flink会自动加载最新分区[^1] - **时间对齐**: - `FOR SYSTEM_TIME AS OF s.event_time`:确保使用数据事件时间对应的版本 #### 3. **更新触发逻辑** ```mermaid graph LR A[每日Hive ETL] -->|写入新分区| B[Hive] B -->|Flink自动检测| C{缓存状态} C -->|缓存过期或新分区| D[重新加载最新分区] D -->|更新内存| E[实时Join数据] ``` #### 4. **性能优化建议** 1. **增量加载**(若Hive支持): ```sql 'lookup.partition-cache.interval' = '12h' -- 每12小时检查新分区 ``` 2. **异步查询**(减少延迟): ```sql 'lookup.async' = 'true' -- 启用异步IO ``` 3. **缓存策略选择**: - `LRU`(默认):适合频繁访问的热数据 - `ALL`:启动时全量加载,适合小 #### 5. **异常处理** - **分区未更新**:通过`try-catch`处理`TableException`,降级到旧分区 - **数据延迟**:设置`watermark`容忍延迟,避免关联失败 - **监控指标**: - `numCacheHit`/`numCacheMiss`:缓存命中率 - `latestPartitionTime`:加载的最新分区时间 #### 6. **完整部署程** 1. **启动Hive Metastore服务** 2. **配置Hive Catalog**: ```sql CREATE CATALOG hive WITH ( 'type' = 'hive', 'hive-conf-dir' = '/path/to/hive/conf' ); USE CATALOG hive; ``` 3. **提交作业**: ```bash ./bin/flink run \ -Dexecution.runtime-mode=streaming \ -c org.apache.flink.SQLJob \ /path/to/job.jar ``` > **注意**:Paimon需使用0.5+版本,Flink需1.16+并配置`flink-table-store-hive-catalog`[^2]
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值