Spark运行环境
Spark最常见的运行环境是Yarn,但也有本地模式、独立部署模式等运行环境。本地环境不是通常想的那样:本地IDEA运行一个Scala程序,本地运行环境指的是一直在本地存在的,想什么时候用就什么时候用,而不是运行一个程序之后就没了。本地环境如下图所示:
可以将上文中的案例用一行语句在本地环境运行:
sc.textFile("data/word.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_ + _).collect()
本地环境提交应用程序(使用apache提供的已有例子):
bin/spark-submit --class org.apache.spark.examples.SparkPi --master local[2] ./examples/jars/spark-examples_2.12-3.0.0.jar 10
// 参数10表示10个任务
独立部署模式是使用spark节点自身的集群模式,是一种经典的master-slave模式,集群模式除了高可用之外,还可以配置历史服务。集群部署好之后,也可以像上面那样提交应用程序,命令类似,各参数含义如下:
独立部署模式由Spark自身提供计算资源,无需其他框架提供资源。但为了将资源调度跟计算本身分离开来,让Spark更好的承担计算,需要引入资源调度框架,国内最常用的就是Yarn。
Spark核心组件
Spark结构中提现了master-slave的模式,图中的Driver就是master,负责管理作业调度,Executor就是slave,负责执行任务。Driver主要职责:
1. 将用户程序转换为作业(job)
2. 在Executor直接调度任务(task)
3. 跟踪Executor的执行情况
4. 通过UI展示允许情况
Executor是工作节点(Worker Node)中的一个JVM进程,负责运行具体的任务,任务彼此之间相互独立。如果Executor节点发生了故障,Spark会将出错节点上的任务调度到其他Executor节点上运行。 前面所述的资源就是Executor的内存大小和使用的虚拟CPU核数。
Yarn的部署模式:Client或者Cluster模式,如果Driver在集群里就是Cluster模式,如果不在就是Client模式。
分布式计算
先简单看一个客户端向服务端发送任务的例子。先写一个任务类Task:
class Task extends Serializable {
val datas = List(1,2,3,4)
val logic : (Int) => Int = _ * 2
def comput() = {
datas.map(logic);
}
}
再写客户端(Driver)类的main方法:
val client = new Socket("localhost", 9999)
val out : OutputStream = client.getOutputStream
val objOut = new ObjectOutputStream(out)
val task = new Task()
// 将任务对象传给服务端Executor
objOut.writeObject(task)
objOut.flush()
objOut.close()
out.close()
最后是服务端(Executor)的main方法:
val server = new ServerSocket(9999)
val client : Socket = server.accept()
val in : InputStream = client.getInputStream
val objIn = new ObjectInputStream(in)
val task : Task = objIn.readObject().asInstanceOf[Task]
val results : List[Int] = task.compute()
in.close()
objIn.close()
server.close()
上述的计算只有一个节点来完成,我们可以把任务分解,让多个节点进行并行计算,这就是分布式计算,引入SubTask类:
class SubTask {
var datas : List[Int] = _
var logic : (Int) => Int = _
def compute() = {
datas.map(logic)
}
}
客户端此时应该发送两个任务给服务端:
val client1 = new Socket("localhost", 9999)
val client2 = new Socket("localhost", 8888)
val out1 : OutputStream = client1.getOutputStream
val out2 : OutputStream = client2.getOutputStream
val objOut1 = new ObjectOutputStream(out1)
val objOut2 = new ObjectOutputStream(out2)
val task = new Task()
val subTask1 = new SubTask()
subTask1.logic = task.logic
subTask1.datas = task.datas.take(2)
val subTask2 = new SubTask()
subTask2.logic = task.logic
subTask2.datas = task.datas.takeRight(2)
// 将任务对象传给服务端Executor
objOut.writeObject(subTask1)
objOut.writeObject(subTask2)
objOut1.flush()
objOut1.close()
out1.close()
objOut2.flush()
objOut2.close()
out2.close()
服务端的代码不需要修改,需要拷贝一份,改个端口号即可,意味着两台服务器。这样一个简单的分布式计算的例子就类似于Spark的工作流程,我们不需要写单独的Task/SubTask,因为Spark已经封装了几个数据结构供我们使用。