Scala 从入门到实战

一、基础语法

Scala 是一种多范式编程语言,它支持面向对象编程和函数式编程,并且提供了丰富的数据类型和灵活的语法。以下是 Scala 中常用的数据类型及其常见的语法用法:

常用的数据类型

  1. 基本数据类型

    • Int: 整数类型,通常是32位有符号整数。
    • Long: 长整数类型,64位有符号整数。
    • Float: 单精度浮点数。
    • Double: 双精度浮点数。
    • Boolean: 布尔类型,取值为 truefalse
    • Char: 单个字符,用单引号括起来,如 'a'
    • String: 字符串类型,用双引号括起来,如 "Hello, Scala"
  2. 复合数据类型

    • Array: 数组,包含相同类型的元素。
    • List: 链表,可以包含不同类型的元素。
    • Tuple: 元组,可以包含多个不同类型的值,一旦创建就不能改变。
    • Set: 集合,包含不重复元素的无序集合。
    • Map: 映射表,包含键值对的集合。
  3. 特殊数据类型

    • Option: 表示可能有或者没有值的容器类型。
    • Either: 表示可能是一种类型或另一种类型的值。
  4. 函数相关

    • Function: 函数类型,在 Scala 中函数是一等公民,可以像变量一样传递和使用。
    • PartialFunction: 部分函数类型,定义了一个函数的子集,只对部分输入值定义了行为。

常用的语法

  1. 变量和常量定义

    • 使用 var 定义可变变量,使用 val 定义不可变常量。
      var x: Int = 10
      val y: String = "Scala"
  2. 函数定义

    • 使用 def 关键字定义函数。
      def add(x: Int, y: Int): Int = x + y
  3. 控制结构

    • 条件表达式
      val result = if (x > 10) "Greater" else "Smaller"
    • 循环结构
      for (i <- 1 to 5) {
      println(i)
      }
  4. 模式匹配

    • 使用 match 关键字进行模式匹配。
      val day = 3
      val dayType = day match {
      case 1 => "Monday"
      case 2 => "Tuesday"
      case _ => "Other day"
      }
  5. 集合操作

    • 数组
      val numbers = Array(1, 2, 3, 4, 5)
    • 列表
      val fruits = List("apple", "banana", "orange")
    • 映射
      val ages = Map("Alice" -> 30, "Bob" -> 25, "Charlie" -> 35)
  6. 函数式编程特性

    • 高阶函数
      def applyOperation(x: Int, f: Int => Int): Int = f(x)
    • 匿名函数
      val addOne = (x: Int) => x + 1
  7. 类和对象

    • 类的定义
      class Person(var name: String, var age: Int)
    • 对象的定义
      object MathUtils {
      def add(x: Int, y: Int): Int = x + y
      }

Scala 的语法支持强大而灵活,结合了面向对象和函数式编程的特性,使得它在大数据处理、并发编程和领域特定语言等领域有广泛的应用。

二、spark和scala之间的关系

  • Spark 和Hadoop 的根本差异是多个作业之间的数据通信问题 : Spark 多个作业之间数据通信是基于内存,而 Hadoop 是基于磁盘。
  • Spark本身就是使用Scala语言开发的,spark和flink的底层通讯都是基于的高并发架构akka开发,然而akka是用scala开发的,Scala与Spark可以实现无缝结合,因此Scala顺理成章地成为了开发Spark应用的首选语言
  • 在IDEA中开发Spark,可以使用两种方式环境方式:
    ①、一是使用本地Scala库,建立Scala项目,导入Spark jar包。
    ②、一种是通过Maven引入Scala、Spark依赖。我们本次使用Maven的方式,符合Java开发者的习惯于行业规范。

先简单了解下scala的语言特性和项目建立:

  • Scala源文件以 “.scala" 为扩展名。
  • Scala程序的执行入口是main()函数。
  • Scala语言严格区分大小写。
  • Scala方法由一条条语句构成,每个语句后不需要分号(Scala语言会在每行后自动加分号),这也体现出Scala的简洁性。
  • 如果在同一行有多条语句,除了最后一条语句不需要分号,其它语句需要分号

object 关键字解读

在Scala中,object 关键字用于定义一个单例对象。它和Java中的 classinterface 有显著的不同。以下是 object 关键字在Scala中的用法及其与Java的主要区别:

1. 单例对象(Singleton Object)

在Scala中,object 定义了一个类的单例对象。这意味着每次访问这个对象时,都是访问同一个实例。Scala的object 可以看作是一个静态成员的容器,但它不仅仅是静态的,也可以拥有方法、字段和初始化代码。

语法示例

object MySingleton {
  def greet(name: String): Unit = {
    println(s"Hello, $name!")
  }
}

使用示例

MySingleton.greet("Alice") // 输出: Hello, Alice!

2. 伴生对象(Companion Object)

在Scala中,object 也常用于伴生对象。伴生对象是与某个类同名的object,它和类存在于同一个源文件中。伴生对象可以访问类的私有成员,并且可以用于实现工厂方法或静态方法。

类和伴生对象的定义示例

class Person(val name: String)

object Person {
  def createPerson(name: String): Person = new Person(name)
}

使用示例

val person = Person.createPerson("Bob")
println(person.name) // 输出: Bob

3. 与Java的区别

在Java中,所有的类和对象都是实例化的,Java没有直接对应的单例模式机制(除了static关键字提供的静态成员)。Scala的object关键字提供了更为简洁和强大的方式来实现单例模式。

Java单例模式示例

public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

Scala单例对象示例

Scala中的object直接创建了一个单例对象,简化了代码:

object Singleton {
  def doSomething(): Unit = {
    println("Doing something...")
  }
}

4. 静态成员

在Java中,可以使用static关键字来定义静态成员,这些成员属于类而不是类的实例。Scala的object 可以看作是一个容纳静态成员的容器,所有在object内部定义的成员都是静态的。

Java静态成员示例

public class MyClass {
    public static int staticValue = 10;

    public static void staticMethod() {
        System.out.println("Static method.");
    }
}

Scala对象中的成员示例

object MyClass {
  val staticValue = 10

  def staticMethod(): Unit = {
    println("Static method.")
  }
}

总结

  • Scala的object:用于定义单例对象或伴生对象,简化了单例模式的实现,允许定义静态成员和方法。
  • Java的static:用于定义静态成员和方法,但没有单例对象的直接概念,需要通过额外的设计模式来实现单例模式。

通过Scala的object,可以更自然地实现单例模式并进行更为简洁的代码组织。

提交scala任务到spark

将Scala代码提交到Spark集群进行任务运行通常涉及以下几个步骤。这个过程包括编译代码、打包应用程序、配置Spark环境,以及最终通过spark-submit命令提交任务到集群。下面是详细的步骤:

1. 编写和测试代码

首先,你需要在本地开发环境中编写和测试你的Spark应用程序。假设你已经完成了这部分。

2. 打包Scala应用程序

将你的Scala应用程序打包成一个可执行的JAR文件。这个步骤通常使用构建工具如SBTMaven来完成。

使用SBT

  1. 创建build.sbt文件

    name := "MySparkApp"
    
    version := "1.0"
    
    scalaVersion := "2.12.17" // 确保版本与你的Spark版本兼容
    
    libraryDependencies ++= Seq(
     "org.apache.spark" %% "spark-core" % "3.4.0",
     "org.apache.spark" %% "spark-sql" % "3.4.0",
     "org.apache.spark" %% "spark-hive" % "3.4.0"
    )
    
    assemblyOption in Assembly := (assemblyOption in Assembly).value.copy(includeScala = false)
    
    mainClass in assembly := Some("com.example.SparkApp")
  2. 创建project/plugins.sbt文件(如果使用SBT assembly插件来打包):

    addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.0")
  3. 打包应用程序

    在项目根目录下运行:

    sbt clean assembly

    这将生成一个包含所有依赖项的JAR文件,通常位于target/scala-2.12/目录下。

使用Maven

  1. 配置pom.xml

    <dependencies>
       <dependency>
           <groupId>org.apache.spark</groupId>
           <artifactId>spark-core_2.12</artifactId>
           <version>3.4.0</version>
       </dependency>
       <dependency>
           <groupId>org.apache.spark</groupId>
           <artifactId>spark-sql_2.12</artifactId>
           <version>3.4.0</version>
       </dependency>
       <dependency>
           <groupId>org.apache.spark</groupId>
           <artifactId>spark-hive_2.12</artifactId>
           <version>3.4.0</version>
       </dependency>
    </dependencies>
    
    <build>
       <plugins>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-shade-plugin</artifactId>
               <version>3.2.4</version>
               <executions>
                   <execution>
                       <phase>package</phase>
                       <goals>
                           <goal>shade</goal>
                       </goals>
                       <configuration>
                           <createDependencyReducedPom>false</createDependencyReducedPom>
                           <filters>
                               <filter>
                                   <artifact>*:*</artifact>
                                   <excludes>
                                       <exclude>META-INF/*.SF</exclude>
                                       <exclude>META-INF/*.DSA</exclude>
                                       <exclude>META-INF/*.RSA</exclude>
                                   </excludes>
                               </filter>
                           </filters>
                       </configuration>
                   </execution>
               </executions>
           </plugin>
       </plugins>
    </build>
  2. 打包应用程序

    在项目根目录下运行:

    mvn clean package

    生成的JAR文件通常位于target/目录下。

3. 配置Spark环境

确保你的Spark集群已正确配置,并且能够访问Hive(如果你使用Hive)。你需要确保以下配置:

  1. Spark配置

    • spark-defaults.conf 配置文件,包含Spark的基本配置。
    • spark-env.sh 配置文件,设置环境变量(如Spark的路径)。
  2. Hive配置(如果使用Hive):

    • 确保hive-site.xml 文件位于Spark的conf目录下,或者你可以将其放在你的JAR文件的资源目录中。

4. 提交任务到Spark集群

使用 spark-submit 命令将你的应用程序提交到Spark集群。

  1. 基本的 spark-submit 命令

    spark-submit \
     --class com.example.SparkApp \
     --master yarn \
     --deploy-mode cluster \
     --conf spark.sql.warehouse.dir=/user/hive/warehouse \
     path/to/your-application.jar
    • --class:指定应用程序的主类。
    • --master:指定Spark集群的管理模式(如yarnstandalone)。
    • --deploy-mode:指定部署模式(clientcluster)。
    • --conf:传递Spark配置参数。
    • path/to/your-application.jar:JAR文件的路径。
  2. 其他常用选项

    • --executor-memory:指定每个executor的内存大小。
    • --executor-cores:指定每个executor的核心数量。
    • --num-executors:指定executor的数量。

    例如:

    spark-submit \
     --class com.example.SparkApp \
     --master yarn \
     --deploy-mode cluster \
     --conf spark.sql.warehouse.dir=/user/hive/warehouse \
     --executor-memory 4G \
     --executor-cores 2 \
     --num-executors 10 \
     path/to/your-application.jar

5. 监控和调试

  1. 查看应用程序日志

    • 在Spark Web UI中查看应用程序的日志和执行状态。Spark Web UI通常位于http://<spark-master-node>:4040
  2. 调试应用程序

    • 使用日志文件和Spark Web UI中的信息来调试和优化应用程序。

6. 清理资源

完成任务后,确保释放集群资源,特别是当你在cluster模式下运行时,以免不必要的资源占用。

以上步骤提供了从开发到部署的完整流程。根据实际的集群配置和应用需求,你可能需要做一些调整。

参数解释

这些参数用于配置Spark作业的资源分配:

  • --executor-memory 4G:为每个executor分配4GB的内存。
  • --executor-cores 2:每个executor使用2个核心进行计算。
  • --num-executors 10:总共启动10个executor进行任务处理。

这些设置帮助优化作业性能,根据集群资源的情况进行调整。

在Apache Spark中,executor是一个重要的概念,它是Spark集群中执行任务的工作进程。以下是关于executor的详细解释:

Executor的功能

  1. 任务执行

    • executor负责执行Spark作业中的任务。每个executor可以并行地处理多个任务,这些任务来自Spark作业的不同分区。
  2. 数据存储

    • executor不仅执行计算任务,还负责存储数据。它会在内存中缓存数据,以便在后续的操作中可以更快地访问这些数据。这个缓存机制是Spark的一个关键特性,可以显著提高性能。
  3. 结果汇总

    • 执行任务的结果最终会汇总到executor中,并通过驱动程序(Driver)进行处理和输出。executor向驱动程序报告任务状态和计算结果。

Executor的配置参数

  • 内存(--executor-memory

    • 为每个executor分配的内存量。例如,--executor-memory 4G表示每个executor有4GB的内存。这个内存用于存储数据和执行任务。
  • 核心数(--executor-cores

    • 每个executor分配的计算核心数量。例如,--executor-cores 2表示每个executor使用2个计算核心。核心数决定了每个executor可以并行处理多少任务。
  • num-executors

    • 总共启动的executor数量。例如,--num-executors 10表示启动10个executorexecutor的数量影响了作业的并行度和总体计算能力。

Executor的生命周期

  • executor是一个长期运行的进程,在整个Spark作业的生命周期内保持活跃。它通常在集群中启动一次,并在作业完成后继续运行,直到集群的资源被释放或作业被结束。

总结

executor是Spark计算框架中的工作单元,负责具体的任务执行、数据存储和结果汇总。合理配置executor的内存、核心数和数量,对于优化Spark作业的性能至关重要。

实践

package com.spark

/**
 * object 单例对象中不可以传参,
 * 如果在创建Object时传入参数,那么会自动根据参数的个数去Object中寻找相应的apply方法
 */
object TestLesson {

  // object 相当于Java 的工具类
  def apply(s: String) = {
    println("name is " + s)
  }

  def apply(s:String, age: Int) = {
    println("name is " + s + ",age=" + age)
  }

  def main(args: Array[String]): Unit = {
    TestLesson("kaiyi")
    TestLesson("kaiyi", 18)
  }

}

说明:apply方法在Scala中不是初始化对象的构造函数。它是一个特定的方法,用于提供一种简洁的对象创建方式。它可以定义在object、class或companion object中,而不仅限于构造函数。apply方法可以有多个重载形式,接受不同数量和类型的参数,并且不一定返回对象的实例。

  • 当你调用TestLesson("kaiyi")时,Scala会自动将其转换为TestLesson.apply("kaiyi")。这会触发TestLesson对象中的apply(s: String)方法,并输出name is kaiyi。
  • 当你调用TestLesson("kaiyi", 18)时,Scala会转换为TestLesson.apply("kaiyi", 18),触发TestLesson对象中的apply(s: String, age: Int)方法,并输出name is kaiyi, age=18。

相关文章:
【Spark】scala基础入门

为者常成,行者常至