先说结论:Spark无法做对数据的顺序性有要求的计算,原因是RDD有多个分区,而排序只能保证各个分区数据顺序性,无法保障全局数据的顺序性。
1.缘起
这是个让我困惑很久的结论了,最终的解答源于一个问题:根据车辆的轨迹信息,计算车辆的行驶里程。
解决这个问题需要两步:
①将车辆定位信息按时间戳进行排序;
②使用reduce算子进行距离累加操作。
原代码如下所示:carLocationInfoRDD从HBase中读取数据创建而来。
//5.然后调用高德地图路线规划API,获取规划里程,与实际距离进行对比,超过阈值则异常;
//5.1计算实际里程
// 5.1.1:对所有轨迹信息进行排序,从先到后
// Spark的算子sortBykey和sortBy都是针对pairRDD,所以排序先转成pairRDD,然后再调用soryBy进行排序
JavaPairRDD<Long,CarLocationInfo> javaPairRDD = carLocationInfoRDD.mapToPair(
new PairFunction<CarLocationInfo, Long, CarLocationInfo>() {
@Override
public Tuple2<Long, CarLocationInfo> call(CarLocationInfo carLocationInfo) throws Exception {
return new Tuple2<>(carLocationInfo.getTime(), carLocationInfo);
}
}
);
JavaPairRDD<Long,CarLocationInfo> sortPairRDD = javaPairRDD.sortByKey();
JavaRDD<CarLocationInfo> sortLocationInfoRDD = sortPairRDD.map(
new Function<Tuple2<Long, CarLocationInfo>, CarLocationInfo>() {
@Override
public CarLocationInfo call(Tuple2<Long, CarLocationInfo> tuple) throws Exception {
return tuple._2;
}
}
);
// 5.1.2:分别计算相邻两点之间的距离,并且进行累加操作
CarLocationInfo locationInfo = sortLocationInfoRDD.reduce(
new Function2<CarLocationInfo, CarLocationInfo, CarLocationInfo>() {
@Override
public CarLocationInfo call(CarLocationInfo c1, CarLocationInfo c2) throws Exception {
CarLocationInfo locationInfo = new CarLocationInfo();
double mileage = CalulateTwoLanLon.getDistance(c1.getLon(), c1.getLat(), c2.getLon(), c2.getLat());
locationInfo.setMeileage(mileage);
locationInfo.setLat(c2.getLat());
locationInfo.setLon(c2.getLon());
locationInfo.setTime(c2.getTime());
return locationInfo;
}
}
);
//获取实际里程
double meileage = locationInfo.getMeileage();
public class CarLocationInfo implements Serializable {
private long time;
private double lon;
private double lat;
private double meileage;
private long inFenceTime;
private long startTime;
private long endTime;
private int stopped;
private boolean bFirstPoint;
private boolean bLastPoint;
private List<Trail> trails;
}
出现问题:相同代码,使用yarn-client模式和local模式,结果不一样。
2.解惑
咨询了一下,主要原因在于RDD有多个分区,而排序只能保证各个分区数据顺序性,无法保障全局数据的顺序性。
可以将数据repartition(1)进行排序,这样才能得到准确的结果。
代码如下:
JavaRDD<CarLocationInfo> sortLocationInfoRDD = carLocationInfoRDD.sortBy(
new Function<CarLocationInfo, Object>() {
@Override
public Object call(CarLocationInfo c) throws Exception {
return c.getTime();
}
},true,1);
// 5.1.2:分别计算相邻两点之间的距离,并且进行累加操作
CarLocationInfo locationInfo = sortLocationInfoRDD.reduce(
new Function2<CarLocationInfo, CarLocationInfo, CarLocationInfo>() {
@Override
public CarLocationInfo call(CarLocationInfo c1, CarLocationInfo c2) throws Exception {
CarLocationInfo locationInfo = new CarLocationInfo();
double mileage = c1.getMeileage() + CalulateTwoLanLon.getDistance(c1.getLon(),c1.getLat(),c2.getLon(),c2.getLat());
System.out.println(c1.getMeileage());
locationInfo.setMeileage(mileage);
locationInfo.setLat(c2.getLat());
locationInfo.setLon(c2.getLon());
locationInfo.setTime(c2.getTime());
return locationInfo;
}
}
);
System.out.println("里程合计:"+ locationInfo.getMeileage());
这里使用了sortBy算子,既可以进行排序,又可以实现repartititon(1),问题解决。
3.总结
这个问题迷惑的根源在于对Spark的底层实现不够清楚,需要补足。