Flink关联外部维表

最近在研究flink相关知识, 这里大概就对外部维表关联这块做一些简单的学习吧,到处抄抄,也算是学习了,后面再看看flink与yarn的结合以及适应场景等等。
在flink1.9版本之后 引入了Blink Table/SQL 方面的功能,使得flink对于外部维表的功能变得简单;


	org.apache.flink
	flink-table-planner-blink_${scala.binary.version}
	${flink.version}

上面的依赖为blink的maven依赖;
简单学习一下维表是什么:其实我觉得就当做类似于mysql或者Oracle中的一张表就可以了,当然这种表的存储方式就可能比较多,虽然可能存储的格式不太一样,但是其本质都是一样的,我们常见的可以做为维表的数据库如mysql,Oracle,es,高斯db,hbase,redis等等

flink1.9之后要实现维表的关联只要实现LookupableTableSource 接口,同时实现里面的方法就可以进行了:
LookupableTableSource 中的上个方法分别是:
isAsyncEnabled :主要表示该表是否支持异步访问外部数据源获取数据, 返回true时 ,那么在注册到 TableEnvironment 后,使用时会返回异步函数进行调用,当返回 false 时,则使同步访问函数。

getLookupFunction :方法返回一个同步访问外部数据系统的函数,就是请求必须有响应才会继续往下走,一般不考虑

getAsyncLookupFunction :返回一个异步的函数,异步访问外部数据系统,获取数据,这能极大的提升系统吞吐率。具体是否要实现异步函数方法,这需要用户自己判定是否需要对异步访问的支持,如果同步方法的吞吐率已经满足要求,那可以先不用考虑异步的实现情况。

同步访问函数getLookupFunction:

getLookupFunction 会返回同步方法,这里你需要自定义 TableFuntion 进行实现,TableFunction 本质是 UDTF,输入一条数据可能返回多条数据,也可能返回一条数据。用户自定义 TableFunction 格式如下:

public class MyLookupFunction extends TableFunction {
@Override
public void open(FunctionContext context) throws Exception {
//这里做一些表连接的初始化。如redis,mysql的初始化,还有缓存的初始化等
super.open(context);
}
//每次输入元素都会调用该方法
public void eval(Object... paramas) {
}
}

open 方法在进行初始化算子实例的进行调用,异步外部数据源的client要在类中定义为 transient,然后在 open 方法中进行初始化,这样每个任务实例都会有一个外部数据源的 client。防止同一个 client 多个任务实例调用,出现线程不安全情况。

eval 则是 TableFunction 最重要的方法,它用于关联外部数据。当程序有一个输入元素时,就会调用eval一次,用户可以将产生的数据使用 collect() 进行发送下游。paramas 的值为用户输入元素的值,比如在 Join 的时候,使用 A.id = B.id and A.name = b.name, B 是维表,A 是用户数据表,paramas 则代表 A.id,A.name 的值。
异步访问函数getAsyncLookupFunction :
getAsyncLookupFunction 会返回异步访问外部数据源的函数,如果你想使用异步函数,前提是 LookupableTableSource 的 isAsyncEnabled 方法返回 true 才能使用。使用异步函数访问外部数据系统,一般是外部系统有异步访问客户端,如果没有的话,可以自己使用线程池异步访问外部系统。至于为什么使用异步访问函数,无非就是为了提高程序的吞吐量,不需要每条记录访问返回数据后,才去处理下一条记录。异步函数格式如下:

public class MyAsyncLookupFunction extends AsyncTableFunction {
@Override
public void open(FunctionContext context) throws Exception {
super.open(context);
}
public void eval(CompletableFuture> future, Object... params) {
}
}

异步访问总体与同步相同:

  1. 外部数据源异步客户端初始化。如果是线程安全的(多个客户端一起使用),你可以不加 transient 关键字,初始化一次。否则,你需要加上 transient,不对其进行初始化,而在 open 方法中,为每个 Task 实例初始化一个。

  2. eval 方法中多了一个 CompletableFuture,当异步访问完成时,需要调用其方法进行处理.

为了减少每条数据都去访问外部数据系统,提高数据的吞吐量,一般我们会在同步函数和异步函数中加入缓存,如果以前某个关键字访问过外部数据系统,我们将其值放入到缓存中,在缓存没有失效之前,如果该关键字再次进行处理时,直接先访问缓存,有就直接返回,没有再去访问外部数据系统,然后在进行缓存,进一步提升我们实时程序处理的吞吐量。

一般缓存类型有以下几种类型:
1、数据全部缓存,定时更新()。
2、LRU Cache,设置一个超时时间(适用于数据量少的情况,Google自带的LRUcache可以很好的根据最近访问次数和超时时间刷新缓存)。
3、用户自定义缓存。

这里基本概念就和方法就完了,我在本地有实现一个数据源采用kafka输入,关联的外部维表为mysql,kafka输入的数据格式为user: id,name,groupid,time
mysql中的数据格式为:id,groupid,name,groupname,grouptype
分别用: id;id,groupid; id,groupid,name做了三组关联,分别是关联一个字段,两个字段和三个字段,然后对比了开启LRU缓存和不开启缓存的耗时:
关联一个字段和关联两个字段的耗时基本持平,因为这里只是测试其实这个缓存没体现出LRU缓存的优势,和普通放进map差不多,当然开启缓存要比不开缓存快一倍左右(测试时用户数据为1000,group数据为50),这里做的性能测试其实意义不大,对于大数据量来说,这种性能其实远远不能满足,所以现在考虑使用内存型数据库作为缓存数据库,定时刷新维表数据到缓存数据库,然后提供统一的可执行内存型数据库的语法。

----随便写写,感谢大牛们的贡献

参考:https://zhuanlan.zhihu.com/p/79800113

你可能感兴趣的:(Flink关联外部维表)