本文开头附:Flink 学习路线系列 ^ _ ^
本文示例参考自:1.Flink异步I/O官方文档 2.HttpAsyncClient(异步HttpClient) 3.高德地图(地理/逆地理编码)
备注:
标题中所指的外部数据,可以指代1.数据库(MySQL、ES等)
、2.三方API(高德地图API等)
。使用异步I/O访问,前提是:数据库/三方API需要支持异步访问。
当我们使用 Flink 在与外部系统进行交互时(例如:使用存储在数据库中的数据来丰富流事件),这时便需要注意 Flink系统
与外部系统
的通信延迟了。
我们使用 MapFunction() 的方式与外部数据库交互,使用的 同步交互 的方式。即:将请求发送到数据库,并MapFunction等待直到收到响应。在许多情况下,这种等待占据了功能的绝大部分时间。
与数据库的异步交互,意味着单个并行函数实例可以同时处理许多请求并同时接收响应。这样,等待时间便可以与发送其他请求和接收响应重叠。至少,等待时间将被分摊到多个请求上。在大多数情况下,会使系统有更高的吞吐量。
备注: 图片中的 database 并不仅仅是代表读取数据库,还可以代表读取三方API 等。
提高查询的速度
Flink 实时读取 Kafka 中的数据,将数据中的经纬度调用高德地图API,转换成省份信息。
Kafka 中的数据结构为:100001,Lucy,2020-02-14 13:28:27,116.310003,39.991957
,字段分别代表 id
、名字
、时间
、经度
、纬度
<!-- 添加异步HttpClient支持 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.4</version>
</dependency>
<!-- 引入fastJson(因为需要对高德地图API返回的Json数据进行操作) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
/**
* TODO 读取 Kafka 数据,异步调用高德API,将经纬度转换成省份输出
*
* log日志格式:100001,Lucy,2020-02-14 13:28:27,116.310003,39.991957
*
* 高德API:
* https://restapi.amap.com/v3/geocode/regeo?output=json&location=116.310003,39.991957&key=<你申请的key>>&radius=1000&extensions=all
*
* @author liuzebiao
* @Date 2020-2-14 13:22
*/
public class AsyncQueryAmapLocation {
public static void main(String[] args) throws Exception {
/**1.创建流实时环境**/
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
/**2.读取 KafkaSource 数据**/
//Kafka props
Properties properties = new Properties();
//指定Kafka的Broker地址
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.204.210:9092,192.168.204.211:9092,192.168.204.212:9092");
//指定组ID
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "flinkDemoGroup");
//如果没有记录偏移量,第一次从最开始消费
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
FlinkKafkaConsumer<String> kafkaSource = new FlinkKafkaConsumer("locations", new SimpleStringSchema(), properties);
//2.通过addSource()方式,创建 Kafka DataStream
DataStreamSource<String> kafkaDataStream = env.addSource(kafkaSource);
/**3.异步处理数据**/
/**此处使用 unorderedWait()无序等待,即:发起请求的顺序和结果返回的顺序可能不一样**/
/**你也可以使用 orderedWait()有序等待,即:发起请求的顺序和结果返回的顺序一致,这个相对于无序来说,性能可能就有一定的损耗了**/
// apply the async I/O transformation
DataStream<DataLocation> resultStream =
AsyncDataStream.unorderedWait(kafkaDataStream, new AsyncDatabaseRequest(), 0, TimeUnit.MILLISECONDS, 10);//10代表最多允许有10个异步请求,超过10个就会阻塞
//0代表超时时间。0表示不设置超时时间
resultStream.print();
env.execute("AsyncQueryAmapLocation");
}
}
/**
* TODO 发起 读取数据库/三方API 异步请求
* 备注:此部分也可以在上面一段代码中编写,因为重写的方法太多,显得混乱,所以在此处可以单独写一个类出来
* @author liuzebiao
* @Date 2020-2-14 13:40
*/
public class AsyncDatabaseRequest extends RichAsyncFunction<String,DataLocation> {
private transient CloseableHttpAsyncClient httpAsyncClient = null;
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
//初始化异步的HttpClient
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(3000) //设置Socket超时时间
.setConnectTimeout(3000) //设置连接超时时间
.build();
httpAsyncClient = HttpAsyncClients.custom()
.setMaxConnTotal(20)//设置最大连接数
.setDefaultRequestConfig(requestConfig).build();
httpAsyncClient.start();
}
/**
* 完成对 Kafka 中读取到的数据的操作
* @param line
* @param resultFuture
* @throws Exception
*/
@Override
public void asyncInvoke(String line, ResultFuture<DataLocation> resultFuture) throws Exception {
// 100001,Lucy,2020-02-14 13:28:27,116.310003,39.991957
String[] fields = line.split(",");
//id
String id = fields[0];
//name
String name = fields[1];
//date
String date = fields[2];
//longitude
String longitude = fields[3];
//latitude
String latitude = fields[4];
String amap_url = "https://restapi.amap.com/v3/geocode/regeo?output=json&location="+longitude+","+latitude+"&key=<你申请的key>&radius=1000&extensions=all";
// Execute request
final HttpGet request1 = new HttpGet(amap_url);
Future<HttpResponse> future = httpAsyncClient.execute(request1, null);
// set the callback to be executed once the request by the client is complete
// the callback simply forwards the result to the result future
CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
HttpResponse response = future.get();
String province = null;
if (response.getStatusLine().getStatusCode() == 200) {
//获取请求的Json字符串
String result = EntityUtils.toString(response.getEntity());
//将Json字符串转成Json对象
JSONObject jsonObject = JSON.parseObject(result);
//获取位置信息
JSONObject regeocode = jsonObject.getJSONObject("regeocode");
if (regeocode != null && !regeocode.isEmpty()) {
JSONObject address = regeocode.getJSONObject("addressComponent");
//获取省份信息
province = address.getString("province");
}
}
return province;
} catch (Exception e) {
// Normally handled explicitly.
return null;
}
}
}).thenAccept( (String dbResult) -> {
resultFuture.complete(Collections.singleton(DataLocation.of(id,name,date,dbResult)));
});
}
@Override
public void close() throws Exception {
super.close();
httpAsyncClient.close();
}
}
/**
* TODO 实体类
*
* @author liuzebiao
* @Date 2020-2-14 13:58
*/
public class DataLocation {
public String id;
public String name;
public String date;
public String province;
public DataLocation() {
}
public DataLocation(String id, String name, String date, String province) {
this.id = id;
this.name = name;
this.date = date;
this.province = province;
}
public static DataLocation of(String id, String name, String date, String province) {
return new DataLocation(id, name, date, province);
}
@Override
public String toString() {
return "DataLocation{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", date='" + date + '\'' +
", province='" + province + '\'' +
'}';
}
}
Kafka 中数据:
100001,Lucy,2020-02-14 13:28:27,116.310003,39.991957
100001,Lucy,2020-02-14 13:28:27,118.980206,36.387564
100001,Lucy,2020-02-14 13:28:27,104.236553,33.120868
100001,Lucy,2020-02-14 13:28:27,103.05003,39.21682
100001,Lucy,2020-02-14 13:28:27,112.124737,38.566952
100001,Lucy,2020-02-14 13:28:27,106.565655,29.269022
100001,Lucy,2020-02-14 13:28:27,121.089581,23.96805
100001,Lucy,2020-02-14 13:28:27,114.162701,22.433236
Flink笔记(十六):Flink 异步查询 MySQL 数据
Flink笔记(十七):Flink 异步查询 ES 数据(预留)
博主写作不易,来个关注呗
求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙
博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ