Flink学习记录之max与maxBy

背景

看了很多网上讲解flink max与maxBy博客,有很多讲的实在看不下去了,于是找flink官方文档学习了一下,并且看了一下flink这块源码实现,这里把自己的理解分享一下。

几个要点:

  1. max/maxBy均只能对Tuple类型数据集生效
  2. max只能作用于Tuple数据集的某一列
  3. 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可以根据指定的一列/多列进行排序,最终返回的是最大的那列对应记录。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值