Apache Flink:如何从另一个流调用一个流

时间:2020-06-19 13:10:44

标签: java apache-kafka cassandra stream apache-flink

我的情况是,我想基于另一个流输入来调用一个流。两者的流类型不同。以下是我的示例代码。从卡夫卡流接收到某些消息时,我想触发一个流。

在应用程序启动时,我可以从数据库读取数据。然后我又想基于一些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;

        }          
}



1 个答案:

答案 0 :(得分:0)

这将是一个非常冗长的答案。

要正确使用Flink作为开发人员,您需要对其基本概念有所了解。我建议您从体系结构概述(https://ci.apache.org/projects/flink/flink-docs-release-1.11/concepts/flink-architecture.html)开始,它包含您在编程时进入Flink所需要了解的所有知识。

现在,查看您的代码,由于Flink的读取方式,它不应达到您的期望。您需要了解,Flink在执行代码时至少要走两个大步骤:首先,它会建立一个执行图,仅描述其需要执行的操作。这发生在工作经理级别。第二大步骤是要求一个或多个工人执行该图形。这两个步骤是连续的,您对图形描述所做的任何操作都必须在作业管理器级别完成,而不是在您的操作内部进行。

在您的情况下,图形具有:

  • Kafak来源。
  • 将在工作人员级别调用getDBData()的映射(不好,因为getDBData()通过在每次调用时添加一个新的Input来改变图形)。

线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.htmlhttps://ci.apache.org/projects/flink/flink-docs-stable/dev/stream/state/broadcast_state.html)。简短的故事:更好的传输管理与以前一样。 Flink将找到最佳的存储方式并将变量发送给工作人员。这种方法虽然正确实施起来有点复杂。

在通常情况下,不建议使用最后一个选项。它只是在地图操作中调用Cassandra。这不是一个好习惯,因为这会增加所有地图执行的重复延迟(与通过Kafka传递的项一样多的调用)。呼叫意味着建立连接,带有查询的实际请求并等待Cassandra答复并释放连接。图表中的步骤可能需要做很多工作。当您确实找不到任何替代方法时,这是一个考虑的解决方案。

对于您的情况,我建议第三种选择。我猜员工表应该不是很大,使用广播变量是一个不错的选择。