1. 什么是Task?

在前面的章节里描写叙述过几个角色,Driver(Client),Master,Worker(Executor),Driver会提交Application到Master进行Worker上的Executor上的调度,显然这些都不是Task.

Spark上的几个关系能够这样理解:

  • Application: Application是Driver在构建SparkContent的上下文的时候创建的,就像申报员,如今要构建一个能完毕任务的集群,须要申报的是这次须要多少个Executor(能够简单理解为虚拟的机器)。每一个Executor须要多少CPU,多少内存。

  • Job: 这是Driver在调用Action的时候生成的Job。让DAGScheduler线程进行最后的调度,代表着这时候RDD的状态分析完了。须要进行最后的Stage分析了,Job并非提交给Executor运行的,一个Application存在多个Job
  • Task: 一个Job能够分成多个Task, 相当于这些Task分到了一个Group里,这个Group的ID就是Job ID

2. Task的类型

Task的类型和Stage相关,关于Stage。以及Stage之间的相关依赖构成任务的不同提交,就不在这篇描写叙述了

ShuffleMapStage 转化成 ShuffleMapTask

ResultStage 转化成为 ResultTask

当Spark上的action算子,通过DAG进行提交任务的时候,会通过Stage来决定提交什么类型的任务,详细的实现都在DAGScheduler.scala 的submitMissingTasks方法中。

3. 同一个Stage的Task数量

Spark是一个分布式的运行任务的框架。那么同一个Stage的并行任务的拆分就很的重要。在任务的分解中并不仅仅是stage的步骤的分解,同一时候也是对同一个Stage中的要分析的数据分解,而对数据的分解直接决定对同一个Stage所提交的任务的数量。

对Stage的任务拆解决定着任务的之间的关系,而对同一个Stage的分析数据进行拆解控制着任务的数量。

比方基于拆解的分析数据的而运行的算子象map。这些任务都是独立的,并没有对数据进行最后的归并和整理,这些task是全然能够进行并行计算的,对同一个Stage的task的数量在Spark上是能够控制的。

在这里以ParallelCollectionRDD为简单的样例,先看DAGScheduler.submitMissingTasks的方法

  1. private def submitMissingTasks(stage: Stage, jobId: Int) {
  2. logDebug("submitMissingTasks(" + stage + ")")
  3. // Get our pending tasks and remember them in our pendingTasks entry
  4. stage.pendingPartitions.clear()
  5.  
  6. // First figure out the indexes of partition ids to compute.
  7. val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()
  8.  
  9. 。。。
  10.  
  11. 。。。
  12.  
  13.  
  14.  
  15. 。。
  16.  
  17. val tasks: Seq[Task[_]] = try {
  18. stage match {
  19. case stage: ShuffleMapStage =>
  20. partitionsToCompute.map { id =>
  21. val locs = taskIdToLocations(id)
  22. val part = stage.rdd.partitions(id)
  23. new ShuffleMapTask(stage.id, stage.latestInfo.attemptId,
  24. taskBinary, part, locs, stage.latestInfo.taskMetrics, properties, Option(jobId),
  25. Option(sc.applicationId), sc.applicationAttemptId)
  26. }
  27.  
  28. case stage: ResultStage =>
  29. partitionsToCompute.map { id =>
  30. val p: Int = stage.partitions(id)
  31. val part = stage.rdd.partitions(p)
  32. val locs = taskIdToLocations(id)
  33. new ResultTask(stage.id, stage.latestInfo.attemptId,
  34. taskBinary, part, locs, id, properties, stage.latestInfo.taskMetrics,
  35. Option(jobId), Option(sc.applicationId), sc.applicationAttemptId)
  36. }
  37. }
  38. } catch {
  39. case NonFatal(e) =>
  40. abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}", Some(e))
  41. runningStages -= stage
  42. return
  43. }

生产task的数量是由val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()来决定的。在ShuffleMapStage里

  1. override def findMissingPartitions(): Seq[Int] = {
  2. val missing = (0 until numPartitions).filter(id => outputLocs(id).isEmpty)
  3. assert(missing.size == numPartitions - _numAvailableOutputs,
  4. s"${missing.size} missing, expected ${numPartitions - _numAvailableOutputs}")
  5. missing
  6. }

能够看到详细是由numPartitions来决定的。在来看numPartitions

  1. val numPartitions = rdd.partitions.length

由rdd.partitions来决定的,对ShuffleMapStage来说rdd就是最后一个value类型的transformation 的RDD。比方常见的MapPartitionsRDD

在MapPartitionsRDD来说的partitions

  1. override def getPartitions: Array[Partition] = firstParent[T].partitions

是transformation的算子链中的第一个。我们以ParallelCollectionRDD为样例,比方常见的相应的样例:

  1. sparkcontext.parallelize(exampleApacheLogs)

在ParallelCollectionRDD中

  1. override def getPartitions: Array[Partition] = {
  2. val slices = ParallelCollectionRDD.slice(data, numSlices).toArray
  3. slices.indices.map(i => new ParallelCollectionPartition(id, i, slices(i))).toArray
  4. }

在ParallelCollectionRDD中数据的Partitions是由numSlices来决定的

  1. def parallelize[T: ClassTag](
  2. seq: Seq[T],
  3. numSlices: Int = defaultParallelism): RDD[T] = withScope {
  4. assertNotStopped()
  5. new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())
  6. }

numSlices 是能够在parallelize函数中传入,而默认使用defaultParallelism的參数控制

  1. def defaultParallelism: Int = {
  2. assertNotStopped()
  3. taskScheduler.defaultParallelism
  4. }
  5. override def defaultParallelism(): Int = backend.defaultParallelism()

在CoarseGrainedSchedulerBackend.scala 的类中:

  1. override def defaultParallelism(): Int = {
  2. conf.getInt("spark.default.parallelism", math.max(totalCoreCount.get(), 2))
  3. }

默认的值是受下面控制:

  1. 配置文件spark.default.parallelism
  2. totalCoreCount 的值: CoarseGrainedSchedulerBackend是一个executor管理的backend,里面维护着executor的信息。totalCoreCount就是executor汇报上来的核数,注意由于executor汇报自己是在application分配好后发生的,汇报的信息和获取totalCoreCount的线程是异步的。也就是假设executor没有汇报上来。totalCoreCount.get()的值并不准确(根绝Master对executor的分配策略。是无法保证分配多少个executor, 在这里spark更依赖executor主动的向driver汇报),这里的策略是无法保证准确的获取executor的核数。

  3. 假设没有设置spark.default.parallelism,最小值是2

依赖于rdd.partitions的策略,最后决定task的分配数量。

4. Task的提交和调度分配

在本篇中主要描写叙述集群下的任务调度

4.1 Task的提交

在DAGScheduler将一个Stage中所分配的Task形成一个TaskSet进行提交,在TaskSet里所保存的是Task的集合。还有Stage的Id。以及JobId,注意在这里JobId是作为一个优先级的參数,作为后序队列调度的參数。

在TaskSchedulerImpl.scala中

  1. override def submitTasks(taskSet: TaskSet) {
  2. val tasks = taskSet.tasks
  3. logInfo("Adding task set " + taskSet.id + " with " + tasks.length + " tasks")
  4. this.synchronized {
  5. val manager = createTaskSetManager(taskSet, maxTaskFailures)
  6. val stage = taskSet.stageId
  7. val stageTaskSets =
  8. taskSetsByStageIdAndAttempt.getOrElseUpdate(stage, new HashMap[Int, TaskSetManager])
  9. stageTaskSets(taskSet.stageAttemptId) = manager
  10. val conflictingTaskSet = stageTaskSets.exists { case (_, ts) =>
  11. ts.taskSet != taskSet && !ts.isZombie
  12. }
  13. if (conflictingTaskSet) {
  14. throw new IllegalStateException(s"more than one active taskSet for stage $stage:" +
  15. s" ${stageTaskSets.toSeq.map{_._2.taskSet.id}.mkString(",")}")
  16. }
  17. schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties)
  18.  
  19. if (!isLocal && !hasReceivedTask) {
  20. starvationTimer.scheduleAtFixedRate(new TimerTask() {
  21. override def run() {
  22. if (!hasLaunchedTask) {
  23. logWarning("Initial job has not accepted any resources; " +
  24. "check your cluster UI to ensure that workers are registered " +
  25. "and have sufficient resources")
  26. } else {
  27. this.cancel()
  28. }
  29. }
  30. }, STARVATION_TIMEOUT_MS, STARVATION_TIMEOUT_MS)
  31. }
  32. hasReceivedTask = true
  33. }
  34. backend.reviveOffers()
  35. }

将TaskSet 封装成TaskSetManager,通过schedulableBuilder去加入TaskSetManager到队列中,在Spark中,有两种形态

  1. FIFOSchedulableBuilder: 单一pool
  2. FairSchedulableBuilder:   多个pool

4.1.1 FairSchedulableBuilder pool池

通过fairsscheduler.xml的模版来设置參数来控制pool的调度

  1. <allocations>
  2. <pool name="production1">
  3. <schedulingMode>FAIR</schedulingMode>
  4. <weight>3</weight>
  5. <minShare>4</minShare>
  6. </pool>
  7. <pool name="production2">
  8. <schedulingMode>FAIR</schedulingMode>
  9. <weight>5</weight>
  10. <minShare>2</minShare>
  11. </pool>
  12. </allocations>

參数的定义:

  • name:   调度池的名称,可依据该參数使用指定pool,EX: sc.setLocalProperty("spark.scheduler.pool", "production1")
  • weight:  调度池的权重。调度池依据该參数分配资源。
  • minShare: 调度池须要的最小资源数(CPU核数),公平调度器首先会尝试为每一个调度池分配最少minShare资源,然后剩余资源才会依照weight大小继续分配
  • schedulingMode: 调度池内的调度模式

在TaskSchedulerImpl在submitTasks加入TaskSetManager到pool后,调用了backend.reviveOffers();

  1. override def reviveOffers() {
  2. driverEndpoint.send(ReviveOffers)
  3. }

  1.  

是向driver的endpoint发送了reviveoffers的消息,Spark中的很多操作都是通过消息来传递的,哪怕DAGScheduler的线程和endpoint的线程都是同一个Driver进程

4.2 Task的分配

Netty 的dispatcher线程接受到revievoffers的消息后,CoarseGrainedSchedulerBackend

  1. case ReviveOffers =>
  2. makeOffers()

调用了makeoffers函数

  1. private def makeOffers() {
  2. // Filter out executors under killing
  3. val activeExecutors = executorDataMap.filterKeys(executorIsAlive)
  4. val workOffers = activeExecutors.map { case (id, executorData) =>
  5. new WorkerOffer(id, executorData.executorHost, executorData.freeCores)
  6. }.toIndexedSeq
  7. launchTasks(scheduler.resourceOffers(workOffers))
  8. }

makeOffers里进行了资源的调度,netty中接收全部的信息,同一时候也在CoarseGrainedSchedulerBackend中维护着executor的状态map:executorDataMap,executor的状态是executor主动汇报的。

通过scheduler.resourceOffers来进行task的资源分配到executor中

  1. def resourceOffers(offers: IndexedSeq[WorkerOffer]): Seq[Seq[TaskDescription]] = synchronized {
  2. // Mark each slave as alive and remember its hostname
  3. // Also track if new executor is added
  4. var newExecAvail = false
  5. for (o <- offers) {
  6. if (!hostToExecutors.contains(o.host)) {
  7. hostToExecutors(o.host) = new HashSet[String]()
  8. }
  9. if (!executorIdToRunningTaskIds.contains(o.executorId)) {
  10. hostToExecutors(o.host) += o.executorId
  11. executorAdded(o.executorId, o.host)
  12. executorIdToHost(o.executorId) = o.host
  13. executorIdToRunningTaskIds(o.executorId) = HashSet[Long]()
  14. newExecAvail = true
  15. }
  16. for (rack <- getRackForHost(o.host)) {
  17. hostsByRack.getOrElseUpdate(rack, new HashSet[String]()) += o.host
  18. }
  19. }
  20.  
  21. // Randomly shuffle offers to avoid always placing tasks on the same set of workers.
  22. val shuffledOffers = Random.shuffle(offers)
  23. // Build a list of tasks to assign to each worker.
  24. val tasks = shuffledOffers.map(o => new ArrayBuffer[TaskDescription](o.cores))
  25. val availableCpus = shuffledOffers.map(o => o.cores).toArray
  26. val sortedTaskSets = rootPool.getSortedTaskSetQueue
  27. for (taskSet <- sortedTaskSets) {
  28. logDebug("parentName: %s, name: %s, runningTasks: %s".format(
  29. taskSet.parent.name, taskSet.name, taskSet.runningTasks))
  30. if (newExecAvail) {
  31. taskSet.executorAdded()
  32. }
  33. }
  34.  
  35. // Take each TaskSet in our scheduling order, and then offer it each node in increasing order
  36. // of locality levels so that it gets a chance to launch local tasks on all of them.
  37. // NOTE: the preferredLocality order: PROCESS_LOCAL, NODE_LOCAL, NO_PREF, RACK_LOCAL, ANY
  38. for (taskSet <- sortedTaskSets) {
  39. var launchedAnyTask = false
  40. var launchedTaskAtCurrentMaxLocality = false
  41. for (currentMaxLocality <- taskSet.myLocalityLevels) {
  42. do {
  43. launchedTaskAtCurrentMaxLocality = resourceOfferSingleTaskSet(
  44. taskSet, currentMaxLocality, shuffledOffers, availableCpus, tasks)
  45. launchedAnyTask |= launchedTaskAtCurrentMaxLocality
  46. } while (launchedTaskAtCurrentMaxLocality)
  47. }
  48. if (!launchedAnyTask) {
  49. taskSet.abortIfCompletelyBlacklisted(hostToExecutors)
  50. }
  51. }
  52.  
  53. if (tasks.size > 0) {
  54. hasLaunchedTask = true
  55. }
  56. return tasks
  57. }

  1. 随机化了有效的executor的列表。为了均匀的分配
  2. 获取池里(前面已经提过油两种池)的排号序的taskSetManager的队列
  3. 对taskSetManager里面的task集合进行调度分配

4.2.1 taskSetManager队列的排序

这里的排序是对单个Pool里的taskSetManager进行排序。Spark有两种排序算法

  1. var taskSetSchedulingAlgorithm: SchedulingAlgorithm = {
  2. schedulingMode match {
  3. case SchedulingMode.FAIR =>
  4. new FairSchedulingAlgorithm()
  5. case SchedulingMode.FIFO =>
  6. new FIFOSchedulingAlgorithm()
  7. case _ =>
  8. val msg = "Unsupported scheduling mode: $schedulingMode. Use FAIR or FIFO instead."
  9. throw new IllegalArgumentException(msg)
  10. }
  11. }

在这里就简介FIFOSchedulingAlgorithm的算法

  1. private[spark] class FIFOSchedulingAlgorithm extends SchedulingAlgorithm {
  2. override def comparator(s1: Schedulable, s2: Schedulable): Boolean = {
  3. val priority1 = s1.priority
  4. val priority2 = s2.priority
  5. var res = math.signum(priority1 - priority2)
  6. if (res == 0) {
  7. val stageId1 = s1.stageId
  8. val stageId2 = s2.stageId
  9. res = math.signum(stageId1 - stageId2)
  10. }
  11. res < 0
  12. }
  13. }

这里的priority 就是前面说到的JobID, 也就是JobID越小的排序在前面,在相通JobId下的StageId越小的排序在前面

4.2.2 单个TaskSetManager的task调度

TaskSetManager 里保存了TaskSet,也就是DAGScheduler里生成的tasks的集合,在TaskSchedulerImpl.scala中进行了单个的TaskSetManager进行调度
  1. private def resourceOfferSingleTaskSet(
  2. taskSet: TaskSetManager,
  3. maxLocality: TaskLocality,
  4. shuffledOffers: Seq[WorkerOffer],
  5. availableCpus: Array[Int],
  6. tasks: IndexedSeq[ArrayBuffer[TaskDescription]]) : Boolean = {
  7. var launchedTask = false
  8. for (i <- 0 until shuffledOffers.size) {
  9. val execId = shuffledOffers(i).executorId
  10. val host = shuffledOffers(i).host
  11. if (availableCpus(i) >= CPUS_PER_TASK) {
  12. try {
  13. for (task <- taskSet.resourceOffer(execId, host, maxLocality)) {
  14. tasks(i) += task
  15. val tid = task.taskId
  16. taskIdToTaskSetManager(tid) = taskSet
  17. taskIdToExecutorId(tid) = execId
  18. executorIdToRunningTaskIds(execId).add(tid)
  19. availableCpus(i) -= CPUS_PER_TASK
  20. assert(availableCpus(i) >= 0)
  21. launchedTask = true
  22. }
  23. } catch {
  24. case e: TaskNotSerializableException =>
  25. logError(s"Resource offer failed, task set ${taskSet.name} was not serializable")
  26. // Do not offer resources for this task, but don't throw an error to allow other
  27. // task sets to be submitted.
  28. return launchedTask
  29. }
  30. }
  31. }
  32. return launchedTask
  33. }

在这里,我们看到了一个參数CPUS_PER_TASK

  1. val CPUS_PER_TASK = conf.getInt("spark.task.cpus", 1)

在spark里,我们能够设置task所使用的cpu的数量,默认是1个,一个task任务在executor中是启动一个线程来运行的

通过计算每一个executor的剩余资源,决定是否须要从tasksetmanager里分配出task.

  1. def resourceOffer(
  2. execId: String,
  3. host: String,
  4. maxLocality: TaskLocality.TaskLocality)
  5. : Option[TaskDescription] =
  6. {
  7. .....
  8.  
  9. dequeueTask(execId, host, allowedLocality).map { case ((index, taskLocality, speculative)) =>
  10. ......
  11. new TaskDescription(taskId = taskId, attemptNumber = attemptNum, execId,
  12. taskName, index, serializedTask)
  13. }
  14. } else {
  15. None
  16. }
  17. }

核心函数dequeueTask

  1. private def dequeueTask(execId: String, host: String, maxLocality: TaskLocality.Value)
  2. : Option[(Int, TaskLocality.Value, Boolean)] =
  3. {
  4. for (index <- dequeueTaskFromList(execId, host, getPendingTasksForExecutor(execId))) {
  5. return Some((index, TaskLocality.PROCESS_LOCAL, false))
  6. }
  7.  
  8. if (TaskLocality.isAllowed(maxLocality, TaskLocality.NODE_LOCAL)) {
  9. for (index <- dequeueTaskFromList(execId, host, getPendingTasksForHost(host))) {
  10. return Some((index, TaskLocality.NODE_LOCAL, false))
  11. }
  12. }
  13.  
  14. if (TaskLocality.isAllowed(maxLocality, TaskLocality.NO_PREF)) {
  15. // Look for noPref tasks after NODE_LOCAL for minimize cross-rack traffic
  16. for (index <- dequeueTaskFromList(execId, host, pendingTasksWithNoPrefs)) {
  17. return Some((index, TaskLocality.PROCESS_LOCAL, false))
  18. }
  19. }
  20.  
  21. if (TaskLocality.isAllowed(maxLocality, TaskLocality.RACK_LOCAL)) {
  22. for {
  23. rack <- sched.getRackForHost(host)
  24. index <- dequeueTaskFromList(execId, host, getPendingTasksForRack(rack))
  25. } {
  26. return Some((index, TaskLocality.RACK_LOCAL, false))
  27. }
  28. }
  29.  
  30. if (TaskLocality.isAllowed(maxLocality, TaskLocality.ANY)) {
  31. for (index <- dequeueTaskFromList(execId, host, allPendingTasks)) {
  32. return Some((index, TaskLocality.ANY, false))
  33. }
  34. }
  35.  
  36. // find a speculative task if all others tasks have been scheduled
  37. dequeueSpeculativeTask(execId, host, maxLocality).map {
  38. case (taskIndex, allowedLocality) => (taskIndex, allowedLocality, true)}
  39. }

在Spark中为了尽量分配任务到task所需的资源的本地,依据task里的preferredLocations所保存的须要资源的位置进行分配

  1. 尽量分配到task到task所需资源同样的executor里运行,比方ExecutorCacheTaskLocation,HDFSCacheTaskLocation
  2. 尽量分配到task里task所需资源相通的host里运行
  3. task的数组从最后向前開始分配

分配完生成TaskDescription。里面记录着taskId, execId, task在数组的位置,和task的整个序列化的内容

4.2.3 Launch Tasks

  1. private def launchTasks(tasks: Seq[Seq[TaskDescription]]) {
  2. for (task <- tasks.flatten) {
  3. val serializedTask = ser.serialize(task)
  4. if (serializedTask.limit >= maxRpcMessageSize) {
  5. scheduler.taskIdToTaskSetManager.get(task.taskId).foreach { taskSetMgr =>
  6. try {
  7. var msg = "Serialized task %s:%d was %d bytes, which exceeds max allowed: " +
  8. "spark.rpc.message.maxSize (%d bytes). Consider increasing " +
  9. "spark.rpc.message.maxSize or using broadcast variables for large values."
  10. msg = msg.format(task.taskId, task.index, serializedTask.limit, maxRpcMessageSize)
  11. taskSetMgr.abort(msg)
  12. } catch {
  13. case e: Exception => logError("Exception in error callback", e)
  14. }
  15. }
  16. }
  17. else {
  18. val executorData = executorDataMap(task.executorId)
  19. executorData.freeCores -= scheduler.CPUS_PER_TASK
  20.  
  21. logDebug(s"Launching task ${task.taskId} on executor id: ${task.executorId} hostname: " +
  22. s"${executorData.executorHost}.")
  23.  
  24. executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))
  25. }
  26. }
  27. }

这里的逻辑就相对照较简单,TaskDescription里面包括着executorId。而CoarseGrainedSchedulerBackend里有executor的信息。依据executorId获取到executor的通讯端口,发送LunchTask的信息。

这里有个RPC的消息的大小控制。假设序列化的task的内容超过了最大RPC的消息。这个任务会被丢弃
  1. /** Returns the configured max message size for messages in bytes. */
  2. def maxMessageSizeBytes(conf: SparkConf): Int = {
  3. val maxSizeInMB = conf.getInt("spark.rpc.message.maxSize", 128)
  4. if (maxSizeInMB > MAX_MESSAGE_SIZE_IN_MB) {
  5. throw new IllegalArgumentException(
  6. s"spark.rpc.message.maxSize should not be greater than $MAX_MESSAGE_SIZE_IN_MB MB")
  7. }
  8. maxSizeInMB * 1024 * 1024
  9. }

能够看到最大的消息大小是128M,能够通过spark.rpc.message.maxSize进行配置

大数据:Spark Core(二)Driver上的Task的生成、分配、调度的更多相关文章

  1. Spark Core(二)Driver上的Task的生成、分配、调度(转载)

    1. 什么是Task? 在前面的章节里描述过几个角色,Driver(Client),Master,Worker(Executor),Driver会提交Application到Master进行Worke ...

  2. 王家林 大数据Spark超经典视频链接全集[转]

    压缩过的大数据Spark蘑菇云行动前置课程视频百度云分享链接 链接:http://pan.baidu.com/s/1cFqjQu SCALA专辑 Scala深入浅出经典视频 链接:http://pan ...

  3. 大数据Spark超经典视频链接全集

    论坛贴吧等信息发布参考模板 Scala.Spark史上最全面.最详细.最彻底的一整套视频全集(特别是机器学习.Spark Core解密.Spark性能优化.Spark面试宝典.Spark项目案例等). ...

  4. 【Todo】【读书笔记】大数据Spark企业级实战版 & Scala学习

    下了这本<大数据Spark企业级实战版>, 另外还有一本<Spark大数据处理:技术.应用与性能优化(全)> 先看前一篇. 根据书里的前言里面,对于阅读顺序的建议.先看最后的S ...

  5. 《大数据Spark企业级实战 》

    基本信息 作者: Spark亚太研究院   王家林 丛书名:决胜大数据时代Spark全系列书籍 出版社:电子工业出版社 ISBN:9787121247446 上架时间:2015-1-6 出版日期:20 ...

  6. 大数据 Spark 架构

    一.Spark的产生背景起源 1.spark特点 1.1轻量级快速处理 Saprk允许传统的hadoop集群中的应用程序在内存中已100倍的速度运行即使在磁盘上也比传统的hadoop快10倍,Spar ...

  7. Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈与熟练的掌握Scala语言【大数据Spark实战高手之路】

    Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈 大数据的概念与应用,正随着智能手机.平板电脑的快速流行而日渐普及,大数据中图的并行化处理一直是一个非常热门的话题.图计算正在被广泛地应用于社交 ...

  8. 大数据笔记(二十七)——Spark Core简介及安装配置

    1.Spark Core: 类似MapReduce 核心:RDD 2.Spark SQL: 类似Hive,支持SQL 3.Spark Streaming:类似Storm =============== ...

  9. 大数据笔记(二十八)——执行Spark任务、开发Spark WordCount程序

    一.执行Spark任务: 客户端 1.Spark Submit工具:提交Spark的任务(jar文件) (*)spark提供的用于提交Spark任务工具 (*)example:/root/traini ...

随机推荐

  1. C#中遍历Hashtable的4种方法

    static void Main(string[] args) { Hashtable ht=new Hashtable(); ht.Add("); ht.Add("); ht.A ...

  2. Spring Batch并行与扩展

    1. 概述 Spring Batch提供了多种方式用于处理并行,提高性能.主要分为2大类: - 单个进程,多线程 - 多个进程 因此,可以细分为以下几类: - 多线程Step(Multi-thread ...

  3. andriod自定义视图

    一.通过View实现自定义视图 通过构造函数创建可视界面 public class MyView extends View { // Constructor required for in-code ...

  4. bootstrap插件(对话框)bootbox参数和自定义弹出框宽度设置

    参考:https://hisune.com/post/view/17/bootstrap-plugin-bootbox-and-custom-width-dialog 官网:http://bootbo ...

  5. Base64与MD5的区别

    Base64和MD5都可用于做信息的简单加密,两者的简单差别如下: Base64 可逆性. 可以将图片等二进制文件转换为文本文件. 可以把非ASCII字符的数据转换成ASCII字符,避免不可见字符. ...

  6. backbone的对象继承实现

    通过原型链实现对象的继承,子类通过’__super__‘来访问父类的方法 // protoProps 子类的属性参数 // staticProps 静态属性 var extend = function ...

  7. servlet 过滤器实现 请求转发(跳转);跨域转发请求;tomcat 环境下。

    一般的文章都有 文本内容 和图片的.我想实现一个图片服务(或服务器)来单独处理图片逻辑,和文章处理逻辑分离.于是我想到一个办法,来尝试. 背景: 1. 假如文章的处理在web App,就叫web1 吧 ...

  8. PCL关键点(1)

    关键点也称为兴趣点,它是2D图像或是3D点云或者曲面模型上,可以通过定义检测标准来获取的具有稳定性,区别性的点集,从技术上来说,关键点的数量相比于原始点云或图像的数据量减小很多,与局部特征描述子结合在 ...

  9. Android开发-新建线程崩溃

    一直不满意车机不能实现屏保,最近发现可以通过设置亮度实现,顾萌生了自己写程序的来实现的念头,遂修改原来练手的app.毕竟过去了1年,各类程序.sdk都已经更新了不知道多少版本.经历了痛苦的升级.更新, ...

  10. jquery 发get post请求

    https://www.cnblogs.com/summers/p/3225375.html POST 方法不会缓存数据 $.get(URL,callback); 2个参数 callback 参数是请 ...