使用flink做實時數倉的公司越來越多了,浪尖這邊也是很早就開發了一個flink 全sql平臺來實現實時數倉的功能。說到實時數倉,兩個表的概念大家一定會知道的:事實表和維表。
在實時數倉中,事實表就是flink消費的kafka的topic數據流,而維表和離線數倉一樣,就是mysql等外部存儲的維表。
當flink 事實表需要 使用維表來進行染色的時候,就需要flink 與維表進行join,這是需要注意與外部系統的通信延遲不會影響流應用程序的整體工作。
直接訪問外部數據庫中的數據,例如在MapFunction中,通常意味着同步交互:向數據庫發送請求,並且MapFunction等待直到收到響應。在許多情況下,這種等待佔據了函數的絕大部分時間。
爲了解決這個問題flink支持了異步IO的操作,與數據庫的異步交互意味着單個並行task的實例可以同時處理許多請求並同時接收響應。這樣,可以通過發送其他請求和接收響應來覆蓋等待時間。至少,等待時間在多個請求上均攤。這會使得大多數情況下流量吞吐量更高。
關於異步IO要關注的點,主要是:
- 有序IO的API。orderedWait請求的順序和返回的順序一致。
- 無序IO的API。unorderedWait,主要是請求元素的順序與返回元素的順序不保證一致。
問浪尖比較多的還有兩個參數含義:
- Timeout。請求超時時間。
- Capacity。同時運行的最大異步請求數。
企業中常用的維表存儲慢的都是mysql,pg等數據庫,也有爲了提升速度使用redis的,浪尖這裏主要給出一個基於redis的案例。使用的包主要是:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-redis-client</artifactId>
<version>3.5.2.CR3</version>
</dependency>
完整的案例:
package org.datastream.AsyncIO;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions;
import net.sf.json.JSONObject;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.AsyncDataStream;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.async.ResultFuture;
import org.apache.flink.streaming.api.functions.async.RichAsyncFunction;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer010;
import org.datastream.watermark.KafkaEventSchema;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
/*
關於異步IO原理的講解可以參考浪尖的知乎~:
https://zhuanlan.zhihu.com/p/48686938
*/
public class AsyncIOSideTableJoinRedis {
public static void main(String[] args) throws Exception {
// set up the streaming execution environment
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 選擇設置事件事件和處理事件
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9093");
properties.setProperty("group.id", "AsyncIOSideTableJoinRedis");
FlinkKafkaConsumer010<JSONObject> kafkaConsumer010 = new FlinkKafkaConsumer010<>("jsontest",
new KafkaEventSchema(),
properties);
DataStreamSource<JSONObject> source = env
.addSource(kafkaConsumer010);
SampleAsyncFunction asyncFunction = new SampleAsyncFunction();
// add async operator to streaming job
DataStream<JSONObject> result;
if (true) {
result = AsyncDataStream.orderedWait(
source,
asyncFunction,
1000000L,
TimeUnit.MILLISECONDS,
20).setParallelism(1);
}
else {
result = AsyncDataStream.unorderedWait(
source,
asyncFunction,
10000,
TimeUnit.MILLISECONDS,
20).setParallelism(1);
}
result.print();
env.execute(AsyncIOSideTableJoinRedis.class.getCanonicalName());
}
private static class SampleAsyncFunction extends RichAsyncFunction<JSONObject, JSONObject> {
private transient RedisClient redisClient;
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
RedisOptions config = new RedisOptions();
config.setHost("127.0.0.1");
config.setPort(6379);
VertxOptions vo = new VertxOptions();
vo.setEventLoopPoolSize(10);
vo.setWorkerPoolSize(20);
Vertx vertx = Vertx.vertx(vo);
redisClient = RedisClient.create(vertx, config);
}
@Override
public void close() throws Exception {
super.close();
if(redisClient!=null)
redisClient.close(null);
}
@Override
public void asyncInvoke(final JSONObject input, final ResultFuture<JSONObject> resultFuture) {
String fruit = input.getString("fruit");
// 獲取hash-key值
// redisClient.hget(fruit,"hash-key",getRes->{
// });
// 直接通過key獲取值,可以類比
redisClient.get(fruit,getRes->{
if(getRes.succeeded()){
String result = getRes.result();
if(result== null){
resultFuture.complete(null);
return;
}
else {
input.put("docs",result);
resultFuture.complete(Collections.singleton(input));
}
} else if(getRes.failed()){
resultFuture.complete(null);
return;
}
});
}
}
}