spark三种连接join

本文主要介绍spark join相关操作。

讲述spark连接相关的三个方法join,left-outer-join,right-outer-join,在这之前,我们用hiveSQL先跑出了结果以方便进行对比。

我们以实例来进行说明。我的实现步骤记录如下。

 

1、数据准备

2、HSQL描述

3、Spark描述

 

1、数据准备

我们准备两张Hive表,分别是orders(订单表)和drivers(司机表),通过driver_id字段进行关联。数据如下:

orders

orders表有两个字段,订单id:order_id和司机id:driver_id。司机id将作为连接键。

通过select可以看到三条数据。


hive (gulfstream_test)> select * from orders;
OK
orders.order_id orders.driver_id
1000    5000
1001    5001
1002    5002
Time taken: 0.387 seconds, Fetched: 3 row(s)

 

drivers

drivers表由两个字段,司机id:driver_id和车辆id:car_id。司机id将作为连接键。

通过select可以看到两条数据。

hive (gulfstream_test)> select * from drivers;
OK
drivers.driver_id       drivers.car_id
5000    100
5003    103
Time taken: 0.036 seconds, Fetched: 2 row(s)

 

 

2、HSQL描述

JOIN

自然连接,输出连接键匹配的记录。

可以看到,通过driver_id匹配的数据只有一条。

hive (gulfstream_test)> select * from orders t1 join drivers t2 on (t1.driver_id = t2.driver_id) ;
OK
t1.order_id     t1.driver_id    t2.driver_id    t2.car_id
1000    5000    5000    100
Time taken: 36.079 seconds, Fetched: 1 row(s)

 

LEFT OUTER JOIN

左外链接,输出连接键匹配的记录,左侧的表无论匹配与否都输出。

可以看到,通过driver_id匹配的数据只有一条,不过所有orders表中的记录都被输出了,drivers中未能匹配的字段被置为空。


hive (gulfstream_test)> select * from orders t1 left outer join drivers t2 on (t1.driver_id = t2.driver_id) ;
OK
t1.order_id     t1.driver_id    t2.driver_id    t2.car_id
1000    5000    5000    100
1001    5001    NULL    NULL
1002    5002    NULL    NULL
Time taken: 36.063 seconds, Fetched: 3 row(s)

 

RIGHT OUTER JOIN

右外连接,输出连接键匹配的记录,右侧的表无论匹配与否都输出。

可以看到,通过driver_id匹配的数据只有一条,不过所有drivers表中的记录都被输出了,orders中未能匹配的字段被置为空。

hive (gulfstream_test)> select * from orders t1 right outer join drivers t2 on (t1.driver_id = t2.driver_id) ;
OK
t1.order_id     t1.driver_id    t2.driver_id    t2.car_id
1000    5000    5000    100
NULL    NULL    5003    103
Time taken: 30.089 seconds, Fetched: 2 row(s)


3、Spark描述

spark实现join的方式也是通过RDD的算子,spark同样提供了三个算子join,leftOuterJoin,rightOuterJoin。

在下面给出的例子中,我们通过spark-hive读取了Hive中orders表和drivers表中的数据,这时候数据的表现形式是DataFrame,如果要使用Join操作:

1)首先需要先将DataFrame转化成了JavaRDD。

2)不过,JavaRDD其实是没有join算子的,下面还需要通过mapToPair算子将JavaRDD转换成JavaPairRDD,这样就可以使用Join了。 

下面例子中给出了三种join操作的实现方式,在join之后,通过collect()函数把数据拉到Driver端本地,并通过标准输出打印。

需要指出的是

1)join算子(join,leftOuterJoin,rightOuterJoin)只能通过PairRDD使用;

2)join算子操作的Tuple2<Object1, Object2>类型中,Object1是连接键,我只试过Integer和String,Object2比较灵活,甚至可以是整个Row。

这里我们使用driver_id作为连接键。 所以在输出Tuple2的时候,我们将driver_id放在了前面。

一个例子:

package com.spark.sparkStreaming;

import java.util.ArrayList;
import java.util.List;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;

import com.google.common.base.Optional;

import scala.Tuple2;

/**
 * 基于transform的实时广告计费日志黑名单过滤
 * 这里案例,完全脱胎于真实的广告业务的大数据系统,业务是真实的,实用
 * @author Administrator
 *
 */
public class TransformBlacklist {
	
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		SparkConf conf = new SparkConf()
				.setMaster("local[2]")
				.setAppName("TransformBlacklist");  
		JavaStreamingContext jssc = new JavaStreamingContext(conf, Durations.seconds(5));
		
		// 用户对我们的网站上的广告可以进行点击
		// 点击之后,是不是要进行实时计费,点一下,算一次钱
		// 但是,对于那些帮助某些无良商家刷广告的人,那么我们有一个黑名单
		// 只要是黑名单中的用户点击的广告,我们就给过滤掉
		
		// 先做一份模拟的黑名单RDD--->元组
		List<Tuple2<String, Boolean>> blacklist = new ArrayList<Tuple2<String, Boolean>>();
		blacklist.add(new Tuple2<String, Boolean>("tom", true));  
		final JavaPairRDD<String, Boolean> blacklistRDD = jssc.sc().parallelizePairs(blacklist);
		
		// 这里的日志格式,就简化一下,就是date username的方式
		JavaReceiverInputDStream<String> adsClickLogDStream = jssc.socketTextStream("spark1", 9999);
		
		// 所以,要先对输入的数据,进行一下转换操作,变成,(username, date username)
		// 以便于,后面对每个batch RDD,与定义好的黑名单RDD进行join操作
		JavaPairDStream<String, String> userAdsClickLogDStream = adsClickLogDStream.mapToPair(
				
				new PairFunction<String, String, String>() {

					private static final long serialVersionUID = 1L;

					@Override
					public Tuple2<String, String> call(String adsClickLog)
							throws Exception {
						return new Tuple2<String, String>(
								adsClickLog.split(" ")[1], adsClickLog);
					}
					
				});
		
		// 然后,就可以执行transform操作了,将每个batch的RDD,与黑名单RDD进行join、filter、map等操作
		// 实时进行黑名单过滤
		JavaDStream<String> validAdsClickLogDStream = userAdsClickLogDStream.transform(
				
				new Function<JavaPairRDD<String,String>, JavaRDD<String>>() {

					private static final long serialVersionUID = 1L;

					@Override
					public JavaRDD<String> call(JavaPairRDD<String, String> userAdsClickLogRDD)
							throws Exception {
						// 这里为什么用左外连接?
						// 因为,并不是每个用户都存在于黑名单中的
						// 所以,如果直接用join,那么没有存在于黑名单中的数据,会无法join到
						// 就给丢弃掉了
						// 所以,这里用leftOuterJoin,就是说,哪怕一个user不在黑名单RDD中,没有join到
						// 也还是会被保存下来的
						JavaPairRDD<String, Tuple2<String, Optional<Boolean>>> joinedRDD = 
								userAdsClickLogRDD.leftOuterJoin(blacklistRDD);
						
						// 连接之后,执行filter算子
						JavaPairRDD<String, Tuple2<String, Optional<Boolean>>> filteredRDD = 
								joinedRDD.filter(
										
										new Function<Tuple2<String, 
												Tuple2<String,Optional<Boolean>>>, Boolean>() {

											private static final long serialVersionUID = 1L;

											@Override
											public Boolean call(
													Tuple2<String, 
															Tuple2<String, Optional<Boolean>>> tuple)
													throws Exception {
												// 这里的tuple,就是每个用户,对应的访问日志,和在黑名单中
												// 的状态
												if(tuple._2._2().isPresent() && 
														tuple._2._2.get()) {  
													return false;
												}
												return true;
											}
											
										});
						
						// 此时,filteredRDD中,就只剩下没有被黑名单过滤的用户点击了
						// 进行map操作,转换成我们想要的格式
						JavaRDD<String> validAdsClickLogRDD = filteredRDD.map(
								
								new Function<Tuple2<String,Tuple2<String,Optional<Boolean>>>, String>() {

									private static final long serialVersionUID = 1L;

									@Override
									public String call(
											Tuple2<String, Tuple2<String, Optional<Boolean>>> tuple)
											throws Exception {
										return tuple._2._1;
									}
									
								});
						
						return validAdsClickLogRDD;
					}
					
				});
		
		// 打印有效的广告点击日志
		// 其实在真实企业场景中,这里后面就可以走写入kafka、ActiveMQ等这种中间件消息队列
		// 然后再开发一个专门的后台服务,作为广告计费服务,执行实时的广告计费,这里就是只拿到了有效的广告点击
		validAdsClickLogDStream.print();
		
		jssc.start();
		jssc.awaitTermination();
		jssc.close();
	}
	
}

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页