背景
看了很多网上讲解flink max与maxBy博客,有很多讲的实在看不下去了,于是找flink官方文档学习了一下,并且看了一下flink这块源码实现,这里把自己的理解分享一下。
几个要点:
- max/maxBy均只能对Tuple类型数据集生效
- max只能作用于Tuple数据集的某一列
- maxBy可以作用于整个Tuple行
max
比如从所有订单中筛选出最高的金额,注意这里只是要最大的那笔金额,不需要拿到这笔订单,Demo如下:
import com.alibaba.security.blink.bean.Order;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.LocalEnvironment;
import org.apache.flink.api.java.operators.DataSource;
import org.apache.flink.api.java.operators.MapOperator;
import org.apache.flink.api.java.tuple.Tuple2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* @author shaoxian.ssx
* @date 2021/11/7
*/
public class SimpleMax {
private final static Logger LOGGER = LoggerFactory.getLogger(SimpleMax.class);
public static void main(String[] args) throws Exception {
LocalEnvironment env = ExecutionEnvironment.createLocalEnvironment();
Order order1 = new Order(1001L, "张三", "192.168.1.10", 30d);
Order order2 = new Order(1002L, "李四", "192.168.1.212", 127d);
Order order3 = new Order(1001L, "张三", "192.168.1.50", 100d);
Order order4 = new Order(1003L, "王五", "192.168.1.71", 30d);
DataSource<Order> dataSource = env.fromElements(order1, order2, order3, order4);
// 告诉flink使用Order对象byrId值进行groupBy
MapOperator<Order, Tuple2<Double, Order>> mapOperator = dataSource.map(new MapFunction<Order, Tuple2<Double, Order>>() {
@Override
public Tuple2<Double, Order> map(Order value) throws Exception {
return new Tuple2<>(value.getTotal(), value);
}
});
List<Tuple2<Double, Order>> result = mapOperator.max(0).collect();
result.forEach(e -> LOGGER.info("order={}", e));
}
}
/**
* @author shaoxian.ssx
* @date 2021/11/8
*/
import java.util.StringJoiner;
/**
* 必须为public类型,否则flink校验类型会报错
*/
public class Order {
private Long byrId;
private String name;
private String ip;
// 订单总金额
private double total;
private int ipCount = 1;
// 必须提供无参数构造方法
public Order() {
}
public Order(Long byrId, String name, String ip, double total) {
this.byrId = byrId;
this.name = name;
this.ip = ip;
this.total = total;
}
// 省略setter、getter方法
@Override
public String toString() {
return new StringJoiner(", ", Order.class.getSimpleName() + "[", "]")
.add("byrId=" + byrId)
.add("name='" + name + "'")
.add("ip='" + ip + "'")
.add("ipCount=" + ipCount)
.add("double=" + total)
.toString();
}
}
输出结果如下:
21:24:19,118 [ main] INFO com.alibaba.security.blink.SimpleMax - order=(127.0,Order[byrId=1003, name='王五', ip='192.168.1.71', ipCount=1, double=30.0])
因为上面Order对象属性太多了,不好画图,这里简化数据集将max作用过程表示如下:
也就是说max只能作用于它指定的那列,其它列它不保证,因此max最终结果是max方法指定的那列返回整列最大的那个值,其它列按照迭代顺序返回最后一个被迭代的元素。比如上面我的代码里返回第一个为整列最大值127,第二列为集合迭代最后一个元素“王五”;下面图片中的例子也是一样的,max返回指定Tuple2中的第一列,因此第一列返回最大值3,第二列为迭代的最后一个元素[2,2]。
maxBy
maxBy相对来说理解更容易点,比如通常从mysql中查询最大的那条记录,写法如下:select * from xxxTable order by xxxColumn desc limit 1。上面例子用flink写法如下(获取单笔金额最大的那笔订单,注意这里我要的是整笔订单而不单单只是要知道最大金额是多少):
import com.alibaba.security.blink.bean.Order;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.LocalEnvironment;
import org.apache.flink.api.java.operators.DataSource;
import org.apache.flink.api.java.operators.MapOperator;
import org.apache.flink.api.java.tuple.Tuple2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* @author shaoxian.ssx
* @date 2021/11/7
*/
public class SimpleMax {
private final static Logger LOGGER = LoggerFactory.getLogger(SimpleMax.class);
public static void main(String[] args) throws Exception {
LocalEnvironment env = ExecutionEnvironment.createLocalEnvironment();
Order order1 = new Order(1001L, "张三", "192.168.1.10", 30d);
Order order2 = new Order(1002L, "李四", "192.168.1.212", 127d);
Order order3 = new Order(1001L, "张三", "192.168.1.50", 100d);
Order order4 = new Order(1003L, "王五", "192.168.1.71", 30d);
DataSource<Order> dataSource = env.fromElements(order1, order2, order3, order4);
MapOperator<Order, Tuple2<Double, Order>> mapOperator = dataSource.map(new MapFunction<Order, Tuple2<Double, Order>>() {
@Override
public Tuple2<Double, Order> map(Order value) throws Exception {
return new Tuple2<>(value.getTotal(), value);
}
});
// 这里使用maxBy对行进行排序,获取最大那行
List<Tuple2<Double, Order>> result = mapOperator.maxBy(0).collect();
result.forEach(e -> LOGGER.info("order={}", e));
}
}
上面代码输出结果如下:
22:15:30,414 [ main] INFO com.alibaba.security.blink.SimpleMax - order=(127.0,Order[byrId=1002, name='李四', ip='192.168.1.212', ipCount=1, double=127.0])
沿用上图表示如下:
也就是maxBy方法是找到指定列最大的那个值关联的一整条记录,而不仅仅是那一列的那一个元素。
max原理
为了得出前面结论以及做出验证,这里看了一下max在flink中实现代码,主要max主要代码见org.apache.flink.api.java.operators.AggregateOperator.AggregatingUdf#reduce,这里简单分析下:
// records为前面DataSet中一行行数据
@Override
public void reduce(Iterable<T> records, Collector<T> out) {
final AggregationFunction<Object>[] aggFunctions = this.aggFunctions;
final int[] fieldPositions = this.fieldPositions;
T outT = null;
for (T record : records) {
outT = record;
// 如果max方法里面写了2个参数,这里fieldPositions值就是2
for (int i = 0; i < fieldPositions.length; i++) {
// 拿到每个Tuple对象里的第N个元素
Object val = record.getFieldNotNull(fieldPositions[i]);
// 调MaxAggregationFunction函数对值进行排序
aggFunctions[i].aggregate(val);
}
}
for (int i = 0; i < fieldPositions.length; i++) {
// 获取到数据集指定列的最大值
Object aggVal = aggFunctions[i].getAggregate();
// 将计算出来的最大值设值到指定列上,此时outT指向迭代器最后一个元素
outT.setField(aggVal, fieldPositions[i]);
aggFunctions[i].initializeAggregate();
}
// 返回最终max结果
out.collect(outT);
}
maxBy原理
maxBy本质上就是对Tuple类型数据某个位置元素进行比较排序,类似于索引,最终返回最大的那个元素,源码实现如下(SelectByMaxFunction.java):
// 两个入参为DataSet中两个Tuple元素
public T reduce(T value1, T value2) throws Exception {
for (int index = 0; index < fields.length; index++) {
// 获取到maxBy方法参数
int position = this.fields[index];
// 获取到该索引位置处的元素值
Comparable comparable1 = value1.getFieldNotNull(position);
Comparable comparable2 = value2.getFieldNotNull(position);
// 比较值
int comp = comparable1.compareTo(comparable2);
// 返回最大的那个Tuple值
if (comp > 0) {
return value1;
} else if (comp < 0) {
return value2;
}
}
return value1;
}
总结
max与maxBy都能用来排序,他们都只能对Tuple类型数据源生效,max用来对指定那一列/多列进行排序,其它列不保证,因此返回结果中指定的一列/多列是最大值,其它列为数据源迭代中的最后一条记录。maxBy可以根据指定的一列/多列进行排序,最终返回的是最大的那列对应记录。