Apache Flink 零基础入门(七)Flink中keyBy三种方式指定key

keyBy 如何指定key

不管是stream还是batch处理,都有一个keyBy(stream)和groupBy(batch)操作。那么该如何指定key?

Some transformations (join, coGroup, keyBy, groupBy) require that a key be defined on a collection of elements. Other transformations (Reduce, GroupReduce, Aggregate, Windows) allow data being grouped on a key before they are applied.

 一些算子(transformations)例如join,coGroup,keyBy,groupBy往往需要定义一个key。其他的算子例如Reduce, GroupReduce, Aggregate, Windows,也允许数据按照key进行分组。

DataSet

DataSet<...> input = // [...]
DataSet<...> reduced = input
  .groupBy(/*define key here*/)
  .reduceGroup(/*do something*/);

DataStream

DataStream<...> input = // [...]
DataStream<...> windowed = input
  .keyBy(/*define key here*/)
  .window(/*window specification*/);

类似于mysql中的join操作:select a.* , b.* from a join b on a.id=b.id

这里的keyBy就是a.id=b.id

有哪几种方式定义Key?

方式一:Tuple

DataStream<Tuple3<Integer,String,Long>> input = // [...]
KeyedStream<Tuple3<Integer,String,Long>,Tuple> keyed = input.keyBy(0)

可以传字段的位置

DataStream<Tuple3<Integer,String,Long>> input = // [...]
KeyedStream<Tuple3<Integer,String,Long>,Tuple> keyed = input.keyBy(0,1)

可以传字段位置的组合

这对于简单的使用时没问题的。但是对于内嵌的Tuple,如下所示:

DataStream<Tuple3<Tuple2<Integer, Float>,String,Long>> ds;

如果使用keyBy(0),那么他就会使用整个Tuple2<Integer, Float>作为key,(因为Tuple2<Integer, Float>是Tuple3<Tuple2<Integer, Float>,String,Long>的0号位置)。如果想要指定key到Tuple2<Integer, Float>内部中,可以使用下面的方式。

方式二:字段表达式

我们可以使用基于字符串字段表达式来引用内嵌字段去定义key。

之前我们的算子写法是这样的:

text.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String value, Collector<Tuple2<String, Integer>> out) throws Exception {
                String[] tokens = value.toLowerCase().split(",");
                for(String token: tokens) {
                    if(token.length() > 0) {
                        out.collect(new Tuple2<String, Integer>(token, 1));
                    }
                }
            }
        }).keyBy(0).timeWindow(Time.seconds(5)).sum(1).print().setParallelism(1);

其中的new FlatMapFunction<String, Tuple2<String, Integer>>表示输入是一个String,输出是一个Tuple2<String, Integer>。这里我们重新定义一个内部类:

public static class WC {
        private String word;
        private int count;

        public WC() {
        }

        public WC(String word, int count) {
            this.word = word;
            this.count = count;
        }

        @Override
        public String toString() {
            return "WC{" +
                    "word='" + word + '\'' +
                    ", count=" + count +
                    '}';
        }

        public String getWord() {
            return word;
        }

        public void setWord(String word) {
            this.word = word;
        }

        public int getCount() {
            return count;
        }

        public void setCount(int count) {
            this.count = count;
        }
    }

修改算子的写法:

        text.flatMap(new FlatMapFunction<String, WC>() {
            @Override
            public void flatMap(String value, Collector<WC> out) throws Exception {
                String[] tokens = value.toLowerCase().split(",");
                for (String token : tokens) {
                    if (token.length() > 0) {
                        out.collect(new WC(token, 1));
                    }
                }
            }
        }).keyBy("word").timeWindow(Time.seconds(5)).sum("count").print().setParallelism(1);

将原来的输出Tuple2<String, Integer>,修改为输出WC类型;将原来的keyBy(0)修改为keyBy("word");将原来的sum(1)修改为sum("count")

因此,在这个例子中我们有一个POJO类,有两个字段分别是"word"和"count",可以传递字段名到keyBy("")中。

语法:

  • 字段名一定要与POJO类中的字段名一致。一定要提供默认的构造函数,和get与set方法。
  • 使用Tuple时,0表示第一个字段
  • 可以使用嵌套方式,举例如下:
public static class WC {
  public ComplexNestedClass complex; //nested POJO
  private int count;
  // getter / setter for private field (count)
  public int getCount() {
    return count;
  }
  public void setCount(int c) {
    this.count = c;
  }
}
public static class ComplexNestedClass {
  public Integer someNumber;
  public float someFloat;
  public Tuple3<Long, Long, String> word;
  public IntWritable hadoopCitizen;
}
  • "count",指向的是WC中的字段count
  • "complex",指向的是复杂数据类型,会递归选择所有ComplexNestedClass的字段
  • "complex.word.f2",指向的是Tuple3中的最后一个字段。
  • "complex.hadoopCitizen",指向的是Hadoop IntWritable type

scala写法:

object StreamingWCScalaApp {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    // 引入隐式转换
    import org.apache.flink.api.scala._

    val text = env.socketTextStream("192.168.152.45", 9999)
    text.flatMap(_.split(","))
        .map(x => WC(x,1))
        .keyBy("word")
        .timeWindow(Time.seconds(5))
        .sum("count")
        .print()
        .setParallelism(1)

    env.execute("StreamingWCScalaApp");
  }
  case class WC(word: String, count: Int)
}

 方式三:key选择器函数

.keyBy(new KeySelector<WC, Object>() {
            @Override
            public Object getKey(WC value) throws Exception {
                return value.word;
            }
        })

转载于:https://my.oschina.net/duanvincent/blog/3099385

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flink使用KeyBy操作并不会改变数据的分区方式,只是将相同的键值(Key)的数据分到同一个TaskManager处理。如果您需要在KeyBy之后进行条件查询TiDB表的数据,可以使用Flink的`RichFlatMapFunction`或`RichMapFunction`,在函数的`open`方法初始化TiDB连接,并在`flatMap`或`map`方法执行查询操作。 以下是一个示例代码,演示了如何使用`RichFlatMapFunction`和TiDB连接器从TiDB表查询数据: ```java import org.apache.flink.api.common.functions.RichFlatMapFunction; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.TableResult; import org.apache.flink.connector.jdbc.JdbcConnectionOptions; import org.apache.flink.connector.jdbc.JdbcSink; import org.apache.flink.connector.jdbc.JdbcStatementBuilder; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.table.api.Table; import org.apache.flink.types.Row; import org.apache.flink.shaded.guava18.com.google.common.collect.ImmutableList; import java.sql.*; import java.util.Properties; public class QueryTiDBDataWithKeyBy { public static void main(String[] args) { // set up the execution environment StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); StreamTableEnvironment tableEnv = StreamTableEnvironment.create( env, EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build() ); // configure TiDB properties Properties properties = new Properties(); properties.setProperty("database-url", "jdbc:mysql://<tidb_ip>:<tidb_port>/<database_name>"); properties.setProperty("username", "<tidb_username>"); properties.setProperty("password", "<tidb_password>"); properties.setProperty("driver", "com.mysql.jdbc.Driver"); // register TiDB table tableEnv.executeSql( "CREATE TABLE my_table (\n" + " id BIGINT,\n" + " name STRING,\n" + " age INT\n" + ") WITH (\n" + " 'connector' = 'jdbc',\n" + " 'properties' = '" + properties.toString() + "',\n" + " 'table-name' = 'my_table'\n" + ")" ); // KeyBy operation DataStream<Row> stream = tableEnv.toDataStream(tableEnv.from("my_table")) .keyBy(row -> row.getField("age")); // flatMap with TiDB query DataStream<Row> result = stream.flatMap(new RichFlatMapFunction<Row, Row>() { private Connection connection; private PreparedStatement statement; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); connection = DriverManager.getConnection("jdbc:mysql://<tidb_ip>:<tidb_port>/<database_name>", "<tidb_username>", "<tidb_password>"); statement = connection.prepareStatement("SELECT * FROM my_table WHERE age > ?"); } @Override public void flatMap(Row row, Collector<Row> collector) throws Exception { statement.setInt(1, (int) row.getField(0)); ResultSet resultSet = statement.executeQuery(); while (resultSet.next()) { collector.collect(Row.of(resultSet.getLong(1), resultSet.getString(2), resultSet.getInt(3))); } } @Override public void close() throws Exception { super.close(); statement.close(); connection.close(); } }); // print the result to console result.print(); try { env.execute(); } catch (Exception e) { e.printStackTrace(); } } } ``` 在上面的示例,我们首先设置了TiDB连接属性,并在Flink注册了TiDB表。然后,我们使用`keyBy`操作将数据按照年龄分组,并在`flatMap`方法使用TiDB连接器从表查询数据。最后,我们将结果打印到控制台。 请注意,在上面的示例,我们使用了`RichFlatMapFunction`,这是因为在`open`方法需要初始化TiDB连接,而在`flatMap`方法需要执行查询操作。如果您使用`FlatMapFunction`,则无法在`open`方法初始化TiDB连接。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值