我的情况是,我想基于另一个流输入来调用一个流。两者的流类型不同。以下是我的示例代码。从卡夫卡流接收到某些消息时,我想触发一个流。
在应用程序启动时,我可以从数据库读取数据。然后我又想基于一些kafka消息从数据库中获取数据。当我在流中收到kafka消息时,我想再次从DB获取数据。这是我的实际用例。
如何实现?有可能吗?
public class DataStreamCassandraExample implements Serializable{
private static final long serialVersionUID = 1L;
static Logger LOG = LoggerFactory.getLogger(DataStreamCassandraExample.class);
private transient static StreamExecutionEnvironment env;
static DataStream<Tuple4<UUID,String,String,String>> inputRecords;
public static void main(String[] args) throws Exception {
env = StreamExecutionEnvironment.getExecutionEnvironment();
ParameterTool argParameters = ParameterTool.fromArgs(args);
env.getConfig().setGlobalJobParameters(argParameters);
Properties kafkaProps = new Properties();
kafkaProps.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
kafkaProps.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "group1");
FlinkKafkaConsumer<String> kafkaConsumer = new FlinkKafkaConsumer<>("testtopic", new SimpleStringSchema(), kafkaProps);
ClusterBuilder cb = new ClusterBuilder() {
private static final long serialVersionUID = 1L;
@Override
public Cluster buildCluster(Cluster.Builder builder) {
return builder.addContactPoint("127.0.0.1")
.withPort(9042)
.withoutJMXReporting()
.build();
}
};
CassandraInputFormat<Tuple4<UUID,String,String,String>> cassandraInputFormat =
new CassandraInputFormat<> ("select * from employee_details", cb);
//While Application is start up , Read data from table and send as stream
inputRecords = getDBData(env,cassandraInputFormat);
// If any data comes from kafka means, again i want to get data from table.
//How to i trigger getDBData() method from inside this stream.
//The below code is not working
DataStream<String> inputRecords1= env.addSource(kafkaConsumer)
.map(new MapFunction<String,String>() {
private static final long serialVersionUID = 1L;
@Override
public String map(String value) throws Exception {
inputRecords = getDBData(env,cassandraInputFormat);
return "OK";
}
});
//This is not printed , when i call getDBData() stream from inside the kafka stream.
inputRecords1.print();
DataStream<Employee> empDataStream = inputRecords.map(new MapFunction<Tuple4<UUID,String,String,String>, Tuple2<String,Employee>>() {
private static final long serialVersionUID = 1L;
@Override
public Tuple2<String, Employee> map(Tuple4<UUID,String,String,String> value) throws Exception {
Employee emp = new Employee();
try{
emp.setEmpid(value.f0);
emp.setFirstname(value.f1);
emp.setLastname(value.f2);
emp.setAddress(value.f3);
}
catch(Exception e){
}
return new Tuple2<>(emp.getEmpid().toString(), emp);
}
}).keyBy(0).map(new MapFunction<Tuple2<String,Employee>,Employee>() {
private static final long serialVersionUID = 1L;
@Override
public Employee map(Tuple2<String, Employee> value)
throws Exception {
return value.f1;
}
});
empDataStream.print();
env.execute();
}
private static DataStream<Tuple4<UUID,String,String,String>> getDBData(StreamExecutionEnvironment env,
CassandraInputFormat<Tuple4<UUID,String,String,String>> cassandraInputFormat){
DataStream<Tuple4<UUID,String,String,String>> inputRecords = env
.createInput
(cassandraInputFormat
,TupleTypeInfo.of(new TypeHint<Tuple4<UUID,String,String,String>>() {}));
return inputRecords;
}
}
答案 0 :(得分:0)
这将是一个非常冗长的答案。
要正确使用Flink作为开发人员,您需要对其基本概念有所了解。我建议您从体系结构概述(https://ci.apache.org/projects/flink/flink-docs-release-1.11/concepts/flink-architecture.html)开始,它包含您在编程时进入Flink所需要了解的所有知识。
现在,查看您的代码,由于Flink的读取方式,它不应达到您的期望。您需要了解,Flink在执行代码时至少要走两个大步骤:首先,它会建立一个执行图,仅描述其需要执行的操作。这发生在工作经理级别。第二大步骤是要求一个或多个工人执行该图形。这两个步骤是连续的,您对图形描述所做的任何操作都必须在作业管理器级别完成,而不是在您的操作内部进行。
在您的情况下,图形具有:
线inputRecords = getDBData(env,cassandraInputFormat);
将创建图形的孤立分支。行DataStream<Employee> empDataStream = inputRecords.map...
将map->keyBy->map
的分支附加到该孤立分支。这将构建图的一部分,该图将从Cassandra中读取所有员工记录并应用map->keyBy->map
转换。这不会以任何方式与Kafka源码链接。
现在让我们回到您的需要。我了解您需要在员工ID来自Kafka时为其获取数据并进行一些操作。
处理此问题的最干净方法称为“侧面输入”。这是在构建图形时声明的数据输入,作业管理器负责处理数据的读取及其向工作人员的传输。坏消息是,侧面输入尚未对Flink中的流式作业起作用(https://issues.apache.org/jira/browse/FLINK-2491-该错误导致流式作业无法创建checskpoints,因为侧面输入很快完成,这使该作业处于混乱状态)。>
话虽如此,您仍然还有三个选择。正确的选择取决于员工cassandra表的大小。
第二个选项是将所有员工加载到静态最终变量employees
并在地图函数中使用它。这种方法的缺点是,作业管理器会将此变量的序列化副本发送给所有工作人员,这可能会阻塞您的网络,并可能使RAM过载。如果表的大小很小,并且将来不应该增加,那么在端输入用于流作业之前,这可能是可以接受的工作。如果表的大小很大或将来会发展,则考虑第三个选项。
第三个选项是第二个选项的改进。它使用Flink的广播变量(请参见https://flink.apache.org/2019/06/26/broadcast-state.html和https://ci.apache.org/projects/flink/flink-docs-stable/dev/stream/state/broadcast_state.html)。简短的故事:更好的传输管理与以前一样。 Flink将找到最佳的存储方式并将变量发送给工作人员。这种方法虽然正确实施起来有点复杂。
在通常情况下,不建议使用最后一个选项。它只是在地图操作中调用Cassandra。这不是一个好习惯,因为这会增加所有地图执行的重复延迟(与通过Kafka传递的项一样多的调用)。呼叫意味着建立连接,带有查询的实际请求并等待Cassandra答复并释放连接。图表中的步骤可能需要做很多工作。当您确实找不到任何替代方法时,这是一个考虑的解决方案。
对于您的情况,我建议第三种选择。我猜员工表应该不是很大,使用广播变量是一个不错的选择。