一、什麼是RDD算子
答:所謂RDD算子,就是RDD中定義的函數,可以對RDD中的元素進行轉換和操作。
二.算子的分類
算子分爲兩類:轉換算子(Transformation)和行動算子(Action)。
- 轉換算子(Transformation):操作時延遲計算的,也就是一個RDD轉換爲另外一個RDD不是馬上執行的,需要等到行動算子(Action)執行的時候,纔會真正觸發。
- 行動算子(Action):Action算子的執行會觸發Spark提交作業。
三.導包
本地導入目前spark最新版本,spark1.6進行測試
Java代碼
- <dependency>
- <groupId>org.apache.spark</groupId>
- <artifactId>spark-core_2.10</artifactId>
- <version>1.6.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.spark</groupId>
- <artifactId>spark-sql_2.10</artifactId>
- <version>1.6.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.hadoop</groupId>
- <artifactId>hadoop-client</artifactId>
- <version>2.5.2</version>
- </dependency>
四.轉換算子(Transformation)
溫馨提示:這裏演示使用javaAPI來使用算子,在javaAPI中目前沒有處理key-value的算子,只有處理value數據類型的算子,也就是說如下API沒有提供
- mapValues()
- combineByKey()
- reduceByKey()
- partitionBy()
- Cogroup()
- Join()
4.1.輸入分區與輸出分區一對一型
4.1.1.map()
將原來RDD的每個數據項通過map中的用戶自定義函數f映射轉變爲一個新的元素。源碼中的map算子相當於初始化一個RDD,新RDD叫作MappedRDD(this, sc.clean(f))。
圖(4-1-1)中的每個方框表示一個RDD分區,左側的分區經過用戶自定義函數f:T->U映射爲右側的新的RDD分區。但是實際只有等到Action算子觸發後,這個f函數纔會和其他函數在一個Stage中對數據進行運算。V1輸入f轉換輸出V’1。
圖(4-1-1)
演示代碼如下:
Java代碼
- /**
- * 通過Map算子,將RDD中json字符串對象轉換爲java對象
- *
- * @author Ickes
- *
- */
- public class MapDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("map").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<String> data = Arrays.asList(
- "{'id':1,'name':'xl1','pwd':'xl123','sex':2}",
- "{'id':2,'name':'xl2','pwd':'xl123','sex':1}",
- "{'id':3,'name':'xl3','pwd':'xl123','sex':2}");
- JavaRDD<String> rddData = sc.parallelize(data);
- rddData.map(new Function<String, User>() {
- @Override
- public User call(String v) throws Exception {
- Gson gson = new Gson();
- return gson.fromJson(v, User.class);
- }
- }).foreach(System.out::println);
- }
- }
打印結果如下:
Java代碼
- User [id=1, name=xl1, pwd=xl123, sex=2]
- User [id=2, name=xl2, pwd=xl123, sex=1]
- User [id=3, name=xl3, pwd=xl123, sex=2]
4.1.2.flatMap()
將原來RDD中的每個元素通過函數f轉換爲新的集合,並將生成的RDD的每個集合中的元素合併爲一個集合。內部創建 FlatMappedRDD(this, sc.clean(f))。
如下圖(4-1-2)中所示:
圖(4-1-2)
演示代碼如下:
Java代碼
- /**
- * 將rdd中的元素,通過逗號分隔;
- * 原始RDD中僅有三個元素,通過flatMap後,新的RDD中有9個元素
- * @author Ickes
- *
- */
- public class FlatMapDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("flatMap").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<String> data = Arrays.asList(
- "aa,bb,cc",
- "cxf,spring,struts2",
- "java,C++,javaScript");
- JavaRDD<String> rddData = sc.parallelize(data);
- rddData.flatMap(new FlatMapFunction<String,String>() {
- @Override
- public Iterable<String> call(String t) throws Exception {
- List<String> list= Arrays.asList(t.split(","));
- return list;
- }
- }).foreach(System.out::println);
- }
- }
返回結果如下:
Java代碼
- aa
- bb
- cc
- cxf
- spring
- struts2
- java
- C++
- javaScript
4.1.3.mapPartitions()
mapPartitions函數獲取到每個分區的迭代器,在函數中通過這個分區整體的迭代器對整個分區的元素進行操作。內部實現是生成MapPartitionsRDD。圖(4-1-3)中的方框代表一個RDD分區。
圖(4-1-3)中,用戶通過函數f (iter )=>iter.filter(_>=3)對分區中的所有數據進行過濾,>=3的數據保留。一個方塊代表一個RDD分區,含有1、2、3的分區過濾只剩下元素3。
圖(4-1-3)
演示代碼如下:
Java代碼
- /**
- * MapPartitions 算子
- * @author Ickes
- */
- public class MapPartitionsDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("MapPartitions").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data = Arrays.asList(1,2,3,4,5,6,7,8);
- JavaRDD<Integer> rddData = sc.parallelize(data);
- rddData.mapPartitions(new FlatMapFunction<Iterator<Integer>,Integer>() {
- /**
- * 其實他跟map的作用一樣,區別在於他的輸入是RDD中分區的迭代器。
- */
- @Override
- public Iterable<Integer> call(Iterator<Integer> t) throws Exception {
- List<Integer> list = new ArrayList<Integer>();
- while(t.hasNext()){
- int num = t.next();
- if(num > 3){
- list.add(num);
- }
- }
- return list;
- }
- }).foreach(System.out::println);
- }
- }
返回結果:
Java代碼
- 4
- 5
- 6
- 7
- 8
4.2.輸入分區與輸出分區多對一型
4.2.1.union()
使用union函數時需要保證兩個RDD元素的數據類型相同,返回的RDD數據類型和被合併的RDD元素數據類型相同,並不進行去重操作,保存所有元素。如果想去重,可以使用distinct()。
圖(4-2-1)中左側的大方框代表兩個RDD,大方框內的小方框代表RDD的分區。右側大方框代表合併後的RDD,大方框內的小方框代表分區。含有V1,V2…U4的RDD和含有V1,V8…U8的RDD合併所有元素形成一個RDD。V1、V1、V2、V8形成一個分區,其他元素同理進行合併。
圖(4-2-1)
演示代碼如下:
Java代碼
- /**
- * Union算子,合併算子
- * @author Ickes
- */
- public class UnionDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("Union").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data1 = Arrays.asList(1,2,3,4,5);
- List<Integer> data2 = Arrays.asList(4,5,6,7,8);
- JavaRDD<Integer> rddData1 = sc.parallelize(data1);
- JavaRDD<Integer> rddData2 = sc.parallelize(data2);
- rddData1.union(rddData2).foreach(System.out::println);
- }
- }
返回結果如下:
Java代碼
- 1
- 2
- 3
- 4
- 5
- 4
- 5
- 6
- 7
- 8
4.2.2.cartesian()
對兩個RDD內的所有元素進行笛卡爾積操作。操作後,內部實現返回CartesianRDD。圖(4-2-2)中左側的大方框代表兩個RDD,大方框內的小方框代表RDD的分區。右側大方框代表合併後的RDD,大方框內的小方框代表分區。
圖(4-2-2)中的大方框代表RDD,大方框中的小方框代表RDD分區。例如,V1和另一個RDD中的W1、W2、Q5進行笛卡爾積運算形成(V1,W1)、(V1,W2)、(V1,Q5)。
圖(4-2-2)
演示代碼如下:
Java代碼
- /**
- * Cartesian 算子,或者笛卡爾積算子
- * @author Ickes
- */
- public class CartesianDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("Cartesian").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data1 = Arrays.asList(1,2,3);
- List<String> data2 = Arrays.asList("aa","bb","cc");
- JavaRDD<Integer> rddData1 = sc.parallelize(data1);
- JavaRDD<String> rddData2 = sc.parallelize(data2);
- rddData1.cartesian(rddData2).foreach(System.out::println);
- }
- }
返回結果如下:
Java代碼
- (1,aa)
- (1,bb)
- (1,cc)
- (2,aa)
- (2,bb)
- (2,cc)
- (3,aa)
- (3,bb)
- (3,cc)
4.3.輸入分區與輸出分區多對多型
4.3.1.groupBy()
將元素通過函數生成相應的Key,數據就轉化爲Key-Value 格式,之後將Key相同的元素分爲一組。
圖(4-3-1)中的方框代表一個RDD分區,相同key的元素合併到一個組。例如,V1,V2合併爲一個Key-Value對,其中key爲“V”,Value爲“V1,V2”,形成V,Seq(V1,V2)。
圖(4-3-1)
演示代碼如下所示:
Java代碼
- /**
- * GroupBy算子:分組算子
- * @author Ickes
- *
- */
- public class GroupByDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("GroupBy").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data1 = Arrays.asList(1,2,3,1,2,1);
- JavaRDD<Integer> rddData = sc.parallelize(data1);
- //jdk1.7
- rddData.groupBy(new Function<Integer,String>() {
- @Override
- public String call(Integer v) throws Exception {
- String s = "key"+v;
- return s;
- }
- }).foreach(System.out::println);
- //jdk1.8
- rddData.groupBy(e -> {return "key"+e;}).foreach(System.out::println);
- }
- }
返回結果如下所示:
Java代碼
- (key2,[2, 2])
- (key3,[3])
- (key1,[1, 1, 1])
4.4.輸出分區爲輸入分區子集型
4.4.1.filter()
filter的功能是對元素進行過濾,對每個元素應用f函數,返回值爲true的元素在RDD中保留,返回爲false的將過濾掉。內部實現相當於生成FilteredRDD(this,sc.clean(f))。
圖4-4-1中的每個方框代表一個RDD分區。T可以是任意的類型。通過用戶自定義的過濾函數f,對每個數據項進行操作,將滿足條件,返回結果爲true的數據項保留。例如,過濾掉V2、V3保留了V1,將區分命名爲V1'。
圖(4-4-1)
演示代碼如下:
Java代碼
- /**
- * Filter算子,過濾算子
- *
- * @author Ickes
- *
- */
- public class FilterDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("GroupBy").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data = Arrays.asList(1, 2, 3, 7, 4, 5, 8);
- JavaRDD<Integer> rddData = sc.parallelize(data);
- // 將RDD中小於3的元素進行過濾
- // jdk1.8以下
- rddData.filter(new Function<Integer, Boolean>() {
- @Override
- public Boolean call(Integer v) throws Exception {
- if (v >= 3) {
- return true;
- }
- return false;
- }
- }).foreach(System.out::println);
- // jdk1.8
- rddData.filter(e -> e >= 3).foreach(System.out::println);
- }
- }
返回結果如下所示:
Java代碼
- 3
- 7
- 4
- 5
- 8
4.4.2.distinct()
distinct將RDD中的元素進行去重操作。圖(4-4-2)中的方框代表RDD分區。
圖(4-4-2)中的每個方框代表一個分區,通過distinct函數,將數據去重。例如,重複數據V1、V1去重後只保留一份V1。
圖(4-4-2)
演示代碼如下所示:
Java代碼
- /**
- * distinct算子,去重操作
- * @author Ickes
- *
- */
- public class DistinctDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("Distinct").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data = Arrays.asList(1,2,3,1,2,1);
- JavaRDD<Integer> rddData = sc.parallelize(data);
- rddData.distinct().foreach(System.out::println);
- }
- }
返回結果如所示:
Java代碼
- 1
- 3
- 2
4.4.3.subtract()
subtract相當於進行集合的差操作,RDD 1去除RDD 1和RDD 2交集中的所有元素。
圖(4-4-3)中左側的大方框代表兩個RDD,大方框內的小方框代表RDD的分區。右側大方框代表合併後的RDD,大方框內的小方框代表分區。V1在兩個RDD中均有,根據差集運算規則,新RDD不保留,V2在第一個RDD有,第二個RDD沒有,則在新RDD元素中包含V2。
圖(4-4-3)
演示代碼如下所示:
Java代碼
- /**
- * Subtract算子,用於求兩個集合的差集,要求兩個集合中的元素類型保持一致
- * @author Ickes
- *
- */
- public class SubtractDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("Subtract").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data1 = Arrays.asList(1,2,3,4,5);
- List<Integer> data2 = Arrays.asList(4,5,6,7,8);
- JavaRDD<Integer> rddData1 = sc.parallelize(data1);
- JavaRDD<Integer> rddData2 = sc.parallelize(data2);
- rddData1.subtract(rddData2).foreach(System.out::println);
- }
- }
返回結果如下所示:
Java代碼
- 1
- 2
- 3
4.4.4.sample()
sample將RDD這個集合內的元素進行採樣,獲取所有元素的子集。用戶可以設定是否有放回的抽樣、百分比、隨機種子,進而決定採樣方式。
* @第一個參數:withReplacement
* true:表示有放回的抽樣;false:表示無放回的抽樣;
* @第二個參數:fraction
* 抽取的百分比,例如0.5就是抽取的50%的數據;
* @第三個參數:seed
* 隨機種子;
圖(4-4-4)中的每個方框是一個RDD分區。通過sample函數,採樣50%的數據。V1、V2、U1、U2、U3、U4採樣出數據V1和U1、U2,形成新的RDD。
圖(4-4-4)
演示代碼如下所示:
Java代碼
- /**
- * Sample算子,抽取樣本的算子
- * @author Ickes
- *
- */
- public class SampleDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("Sample").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data = Arrays.asList(1,2,3,4,5,6);
- JavaRDD<Integer> rddData = sc.parallelize(data);
- /*
- * @第一個參數:withReplacement
- * true:表示有放回的抽樣;false:表示無放回的抽樣;
- * @第二個參數:fraction
- * 抽取的百分比,例如下面的0.5就是抽取的50%的數據;
- * @第三個參數:seed
- * 隨機種子;
- */
- rddData.sample(true,0.5,9).foreach(System.out::println);
- }
- }
返回結果如下所示:
Java代碼
- 1
- 1
- 3
- 5
4.4.5.takeSample()
takeSample()函數和上面的sample函數是一個原理,但是不使用相對比例採樣,而是按設定的採樣個數進行採樣,同時返回結果不再是RDD,而是相當於對採樣後的數據進行Collect(),返回結果的集合爲單機的數組。
圖(4-4-5) 中左側的方框代表分佈式的各個節點上的分區,右側方框代表單機上返回的結果數組。通過takeSample對數據採樣,設置爲採樣一份數據,返回結果爲V1。
圖(4-4-5)
演示代碼如下所示:
Java代碼
- /**
- * TakeSample算子
- * @author Ickes
- */
- public class TakeSampleDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("TakeSample").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data = Arrays.asList(1,2,3,4,5,6);
- JavaRDD<Integer> rddData = sc.parallelize(data);
- /*
- * @第一個參數:withReplacement
- * true:表示有放回的抽樣;false:表示無放回的抽樣;
- * @第二個參數:num
- * 抽取樣本的個數
- */
- rddData.takeSample(true,2).forEach(System.out::println);
- }
- }
返回結果如下所示:
Java代碼
- 6
- 1
4.5.Cache型
4.5.1.cache()
cache將RDD元素從磁盤緩存到內存,相當於persist(MEMORY_ONLY)函數的功能。
4.5.2.persist()
persist函數對RDD進行緩存操作。數據緩存在哪裏由StorageLevel枚舉類型確定。有以下幾種類型的組合,如圖(4-5-2),DISK代表磁盤,MEMORY代表內存,SER代表數據是否進行序列化存儲。
圖(4-5-2)
例如,MEMORY_AND_DISK_SER代表數據可以存儲在內存和磁盤,並且以序列化的方式存儲。其他同理。
圖(4-5-3)中的方框代表RDD分區。disk代表存儲在磁盤,mem代表存儲在內存。數據最初全部存儲在磁盤,通過persist(MEMORY_AND_DISK)將數據緩存到內存,但是有的分區無法容納在內存,例如:圖(4-5-3)中將含有V1,V2,V3的RDD存儲到磁盤,將含有U1,U2的RDD仍舊存儲在內存
圖(4-5-3)
緩存的演示代碼如下所示:
Java代碼
- /**
- * Cache算子,緩存算子
- * @author Ickes
- *
- */
- public class CacheDemo {
- public static void main(String[] args) {
- SparkConf sparkConf = new SparkConf().setAppName("Cache").setMaster(
- "local");
- JavaSparkContext sc = new JavaSparkContext(sparkConf);
- List<Integer> data = Arrays.asList(1,2,3,4,5,6);
- JavaRDD<Integer> rddData1 = sc.parallelize(data);
- JavaRDD<Integer> rddData2 = sc.parallelize(data);
- //cache緩存
- rddData1.cache().foreach(System.out::println);
- //persist緩存
- rddData2.persist(StorageLevel.MEMORY_AND_DISK()).foreach(System.out::println);
- }
- }