首页 文章 精选 留言 我的

精选列表

搜索[源码学习],共10000篇文章
优秀的个人博客,低调大师

Android--Xutils3源码案例调试

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/77842358 下载网址:https://github.com/wyouflf/xUtils3 第一步:新建一个自己的工程; 第二步:点File->New->Import Module,选择自己下载好的xutils3,目录为:D:\adtext\xUtils3-master\sample 第三步:修改库xutils的build.gradle,屏蔽无用代码,将versionName的值改为版本号,修改; apply plugin: 'com.android.library' //apply plugin: 'com.github.dcendents.android-maven' //apply plugin: 'com.jfrog.bintray' // //def siteUrl = 'https://github.com/wyouflf/xUtils3' //def gitUrl = 'https://github.com/wyouflf/xUtils3.git' //group = "org.xutils" //version = "3.5.0" android { compileSdkVersion 25 buildToolsVersion '25.0.0' defaultConfig { minSdkVersion 14 targetSdkVersion 25 versionCode 20170405 versionName "3.5.0" } buildTypes { debug { minifyEnabled false debuggable true } release { minifyEnabled false debuggable true } } sourceSets { main { jniLibs.srcDirs = ['libs'] java.srcDirs = ['src/main/java'] } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_6 targetCompatibility JavaVersion.VERSION_1_6 } } //install { // repositories.mavenInstaller { // // This generates POM.xml with proper parameters // pom { // project { // packaging 'aar' // name 'xUtils' // description 'android orm, bitmap, http, view inject... ' // // #CONFIG# // project title // url siteUrl // // Set your license // licenses { // license { // name 'The Apache Software License, Version 2.0' // url 'http://www.apache.org/licenses/LICENSE-2.0.txt' // } // } // developers { // developer { // id 'wyouflf' // // #CONFIG# // your user id (you can write your nickname) // name 'lei.jiao' // // #CONFIG# // your user name // email 'wyouflf@qq.com' // // #CONFIG# // your email // } // } // scm { // connection gitUrl // developerConnection gitUrl // url siteUrl // } // } // } // } //} //task sourcesJar(type: Jar) { // from android.sourceSets.main.java.srcDirs // classifier = 'sources' //} //task javadoc(type: Javadoc) { // options.encoding = "UTF-8" // source = android.sourceSets.main.java.srcDirs // classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) //} //task javadocJar(type: Jar, dependsOn: javadoc) { // classifier = 'javadoc' // from javadoc.destinationDir //} //artifacts { // archives javadocJar // archives sourcesJar //} //Properties properties = new Properties() //properties.load(project.rootProject.file('local.properties').newDataInputStream()) //bintray { // user = properties.getProperty("bintray.user") // key = properties.getProperty("bintray.apikey") // configurations = ['archives'] // pkg { // repo = "maven" // name = "xUtils" // // #CONFIG# project name in jcenter // websiteUrl = siteUrl // vcsUrl = gitUrl // licenses = ["Apache-2.0"] // publish = true // } //}第四步:修改xutils_sample的 build.gradle,修改红色字体的版本号,要与自己app的build.gradle版本号一致; apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion '25.0.0' defaultConfig { applicationId "org.xutils.sample" minSdkVersion 14 targetSdkVersion 22 // 不使用api23的动态权限策略 versionCode 1 versionName "1.0" } signingConfigs { debugConfig { storeFile file("debug.keystore") storePassword "android" keyAlias "androiddebugkey" keyPassword "android" } } buildTypes { release { debuggable false minifyEnabled true signingConfig signingConfigs.debugConfig proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support:support-v4:25.3.1' compile 'com.android.support:recyclerview-v7:25.3.1' compile 'com.android.support:design:25.3.1' compile project(':xutils') } 第五步:将xutils库作为工程添加到自己app的build.gradle中,在File->Project Structure中选择APP,然后library中添加 初始化时导入: import org.xutils.x;

优秀的个人博客,低调大师

Android -- Camera源码简析,启动流程

com.android.camera.Camera.java,主要的实现Activity,继承于ActivityBase。 ActivityBase 在ActivityBase中执行流程: onCreate中进行判断是否是平板; onResume中判断是否锁屏,锁屏&camera不存在时候,mOnResumePending置为true,否则置为false并执行doOnResume; onWindowFocusChanged中判断是否获取到焦点&mOnResumePending,满足的话执行doOnResume; onPause中将mOnResumePending置为false; Camera.java 接下来分析Camera.java,执行流程: 1、onCreate // 获得摄像头的数量,前置和后置 getPreferredCameraId(); // 获得对焦设置eg:连续对焦或者其它 String[] defaultFocusModes = getResources().getStringArray(R.array.pref_camera_focusmode_default_array); //实例化Focus管理对象 mFocusManager = new FocusManager(mPreferences, defaultFocusModes); // 开启线程来启动摄像头 mCameraOpenThread.start(); // 是否是第三方应用启动拍照功能 mIsImageCaptureIntent = isImageCaptureIntent(); // 设置UI布局文件 setContentView(R.layout.camera); if (mIsImageCaptureIntent) { // 当第三方其送拍照,需要显示不同的UI,比如取消键盘 mReviewDoneButton = (Rotatable) findViewById(R.id.btn_done); mReviewCancelButton = (Rotatable) findViewById(R.id.btn_cancel); findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE); } else { // 反之显示缩略图 mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail); mThumbnailView.enableFilter(false); mThumbnailView.setVisibility(View.VISIBLE); } // 一个能旋转的dialog.比如相机设置的dialog,这个类实现了旋转的父类 mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog); // 设置camera的ID,写道SharedPreference中 mPreferences.setLocalId(this, mCameraId); // 更新preference CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); // 获得相机数 mNumberOfCameras = CameraHolder.instance().getNumberOfCameras(); // 貌似是获得是否是快速拍照 mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false); // 为当前的preview重置曝光值 resetExposureCompensation(); // 隐藏系统导航栏等 Util.enterLightsOutMode(getWindow()); //SurfaceView SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview); SurfaceHolder holder = preview.getHolder(); holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); try { // 这个join语句就是为了保证openCamera的线程执行完后,当前的线程才开始运行。主要是为了确保camera设备被打开了 mCameraOpenThread.join(); // 线程执行完后置为空来让系统回收资源 mCameraOpenThread = null; if (mOpenCameraFail) { // 打开camera失败,显示“无法连接到相机” Util.showErrorAndFinish(this, R.string.cannot_connect_camera); return; } else if (mCameraDisabled) { // 由于安全政策限制,相机已被停用 Util.showErrorAndFinish(this, R.string.camera_disabled); return; } } catch (InterruptedException ex) { // ignore } //开启显示的子线程 mCameraPreviewThread.start(); if (mIsImageCaptureIntent) { //如果是第三方开启的 ,setupCaptureParams 设置拍照的参数 setupCaptureParams(); } else { //设置ModePicker mModePicker = (ModePicker) findViewById(R.id.mode_picker); mModePicker.setVisibility(View.VISIBLE); mModePicker.setOnModeChangeListener(this); mModePicker.setCurrentMode(ModePicker.MODE_CAMERA); } mZoomControl = (ZoomControl) findViewById(R.id.zoom_control); mOnScreenIndicators = (Rotatable) findViewById(R.id.on_screen_indicators); mLocationManager = new LocationManager(this, this); //摄像头ID mBackCameraId = CameraHolder.instance().getBackCameraId(); mFrontCameraId = CameraHolder.instance().getFrontCameraId(); // 在startPreview里面有notify方法 synchronized (mCameraPreviewThread) { try { mCameraPreviewThread.wait(); } catch (InterruptedException ex) { // ignore } } // 初始化各种控制按钮 initializeIndicatorControl(); //初始化拍照声音 mCameraSound = new CameraSound(); try { //确保显示 mCameraPreviewThread.join(); } catch (InterruptedException ex) { // ignore } mCameraPreviewThread = null; 2、surfaceCreated 啥都没做 3、surfaceChanged // 确保在holder中有surface if (holder.getSurface() == null) { Log.d(TAG, "holder.getSurface() == null"); return; } // We need to save the holder for later use, even when the mCameraDevice // is null. This could happen if onResume() is invoked after this // function. mSurfaceHolder = holder; if (mCameraDevice == null) return; if (mPausing || isFinishing()) return; // Set preview display if the surface is being created. Preview was // already started. Also restart the preview if display rotation has // changed. Sometimes this happens when the device is held in portrait // and camera app is opened. Rotation animation takes some time and // display rotation in onCreate may not be what we want. if (mCameraState == PREVIEW_STOPPED) { startPreview(); startFaceDetection(); } else { if (Util.getDisplayRotation(this) != mDisplayRotation) { setDisplayOrientation(); } if (holder.isCreating()) { // Set preview display if the surface is being created and preview // was already started. That means preview display was set to null // and we need to set it now. setPreviewDisplay(holder); } } // If first time initialization is not finished, send a message to do // it later. We want to finish surfaceChanged as soon as possible to let // user see preview first. if (!mFirstTimeInitialized) { mHandler.sendEmptyMessage(FIRST_TIME_INIT); } else { initializeSecondTime(); } 如果是第一次加载,则执行mHandler.sendEmptyMessage(FIRST_TIME_INIT); 对应处理的是initializeFirstTime(); /** * 初始化,第一次初始化 * // Snapshots can only be taken after this is called. It should be called * // once only. We could have done these things in onCreate() but we want to * // make preview screen appear as soon as possible. */ private void initializeFirstTime() { if (mFirstTimeInitialized) return; // Create orientation listenter. This should be done first because it // takes some time to get first orientation. mOrientationListener = new MyOrientationEventListener(Camera.this); mOrientationListener.enable(); // Initialize location sevice. boolean recordLocation = RecordLocationPreference.get( mPreferences, getContentResolver()); // 初始化屏幕最上方的标志,比如开启了曝光值啊,什么的 initOnScreenIndicator(); // 位置服务 mLocationManager.recordLocation(recordLocation); keepMediaProviderInstance(); // 检查存储空间和初始化储存目录 checkStorage(); // Initialize last picture button. mContentResolver = getContentResolver(); if (!mIsImageCaptureIntent) { // no thumbnail in image capture intent // 初始化缩略图 initThumbnailButton(); } // Initialize shutter button. // 初始化拍照按钮并设置监听事件 mShutterButton = (ShutterButton) findViewById(R.id.shutter_button); mShutterButton.setOnShutterButtonListener(this); mShutterButton.setVisibility(View.VISIBLE); // Initialize focus UI. mPreviewFrame = findViewById(R.id.camera_preview); mPreviewFrame.setOnTouchListener(this); // 聚焦框 mFocusAreaIndicator = (RotateLayout) findViewById(R.id.focus_indicator_rotate_layout); CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId]; boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT); mFocusManager.initialize(mFocusAreaIndicator, mPreviewFrame, mFaceView, this, mirror, mDisplayOrientation); // 初始化一个图片的保存线程 mImageSaver = new ImageSaver(); // 设置屏幕亮度 Util.initializeScreenBrightness(getWindow(), getContentResolver()); // 注册SD卡相关的广播,比如拔出存储卡什么的 installIntentFilter(); // 初始化缩放UI initializeZoom(); // 更新屏幕上的闪光灯什么的标记 updateOnScreenIndicators(); // 开始面部检测 startFaceDetection(); // Show the tap to focus toast if this is the first start. // 假如是第一次启动,提示用户“触摸对焦” if (mFocusAreaSupported && mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) { // Delay the toast for one second to wait for orientation. mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000); } mFirstTimeInitialized = true; addIdleHandler(); } 如果不是,则执行initializeSecondTime(); /** * // If the activity is paused and resumed, this method will be called in * // onResume. */ private void initializeSecondTime() { // Start orientation listener as soon as possible because it takes // some time to get first orientation. //方向翻转设置enable,其中包括翻转的时候的动画 mOrientationListener.enable(); // Start location update if needed. boolean recordLocation = RecordLocationPreference.get( mPreferences, getContentResolver()); mLocationManager.recordLocation(recordLocation); //设置SD卡广播 installIntentFilter(); mImageSaver = new ImageSaver(); //初始化Zoom initializeZoom(); //mMediaProviderClient=媒体Provider对象 keepMediaProviderInstance(); //检查硬盘 checkStorage(); //淡出retake和done的Button hidePostCaptureAlert(); if (!mIsImageCaptureIntent) { //如果不是第三方开启,则更新缩略图 updateThumbnailButton(); mModePicker.setCurrentMode(ModePicker.MODE_CAMERA); } } 4、surfaceDestroyed stopPreview(); mSurfaceHolder = null; 我是天王盖地虎的分割线 本文转自我爱物联网博客园博客,原文链接:http://www.cnblogs.com/yydcdut/p/4125128.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

spark源码系列文章------shuffle模块详解

一、Basic shuffle writer实现解析 在Executor上执行shuffle Map Task时,最终会调用shuffleMapTask的runTask,核心逻辑如下: 1.从sparkEnv中获得shuffleManager; 2.从manager中获得writer 3.调用RDD开始计算,运算结果通过writer进行持久化,持久化之前通过Aggregator来确定是否需要进行Map端聚合,然后将结果通过FileShuffleManager#forMapTask的方法写入,写入完成后,会将元数据信息写入MapStatus,然后下游的Task可以通过这个MapStatus取得需要处理的数据。 这样writer通过ShuffleDependency#partitioner来获得下游partition的数量,下游的每个partition都会对应一个文件,文件名字的格式为:“shuffle_”+shuffledId+"_"+mapId+"_"+reduceId。 由于每个shuffle Map Task需要为每个下游的Task创建一个单独的文件,因此文件的数量就是number(shuffle_map_task)*number(following_task)。这样会导致创建和打开许多文件。 后来spark又引入Shuffle Consolidate Writer,原理是core上的第一个Task创建一个文件,该core上的后面的Task的shuffle操作都追加写入改文件,这样文件数量number(core)*number(following_task) 。 //todo

优秀的个人博客,低调大师

Spark源码阅读笔记一——part of core

内部accumulator通过心跳报告给drivertask运行时可以累加accumulator,但是不能读取value,value只能在driver获取spark内部用一个weakhashmap保存accumulator,便于gc的清理 CacheManagerspark的类用于负责传递RDD的分区内容给BlockManager,并保证一个节点不会载入一个rdd的两份拷贝,这个通过一个hashset实现,已载入的rdd会将id保存到set中获取和计算rdd时,先判断是否已经计算,如果没有再从blockmanager获取block然后计算结果。除非是本地模式,不然rdd的计算结果都会缓存如果rdd不需要在内存中缓存,则直接将计算结果通过iterator直接传给blockmanager在rdd需要缓存时,我们必须小心不能在内存中一次性展开全部的partition,否则如果jvm没有足够的空间给这个单个的partition可能会引发OOM异常。取而代之的是,我们展开这些value,小心的、可能的放弃并丢掉这个partition到磁盘如果合适。如果空间足够就全部缓存到内存中,否则如果使用磁盘就放到磁盘,不然直接就返回value DependencyNarrowDependency:一个子partition依赖于多个父partitionShuffleDependency:shuffle stage的输出依赖,在shuffle中,rdd是短暂的因为我们在executor端不需要它 ExecutorAllocationClient与cluster manager请求或杀掉executor的客户端根据我们的调度需要更新集群,依赖于三个信息1 executor的数量,我们需要的全部的executor数,cluster manager不能杀掉任何运行中的executor来达到这个数量,这是我们想要分配的executor数量2 所有要运行的stage中有本地偏好的task数量,包括运行等待和完成的task3 task到运行host的map ExecutorAllocationManager(EAM)一个代理,根据工作负载动态的分配和移除executorEAM维护一个移动的目标executor数量,定期的同步到cluster manager。target的数量从配置的一个初始值开始,并根据等待和运行task数变化在当前的target数量多于需要控制的当前的负载时,会减少target数量。target总是会一次性减到可以运行所以当前运行和等待task的数量当需要响应积压的等待需要调度的task时,会增加target的数量。如果一个队列在N秒内没有排空,则新的executor被加入。如果这个队列仍然在另外的M秒内存在,则更多的executor会被加入。增加的数量在每轮以上一轮的指数级增加,直到达到上限。上限是基于一个配置的属性和当前运行和等待任务的数量。指数增长有双重理由。1 executor应该在开始缓慢的增加,以防万一额外需要的executor数量很小。否则我们增加了多于我们需要的executor数量则我们需要在后面移除他们。2 executor的数量需要快速增加,以防万一executor的数量最大值非常高,否则在繁重的工作负载下性能提升需要很长时间。executor移除的策略很简单,如果一个executor已经空闲了K秒,意味着它没有被调度用于执行任何task,因而移除它。这里没有重试的逻辑,因为我们假定cluster manager最终会异步的执行所有它收到的请求。相关的spark属性如下 成员变量initializing,是否需要一直等待初始化的executor集合被分配,当这个变量为true的时候,我们不会取消未执行的executor请求。这个在下面两种情况会被设置成false1 一个stage被提交2 一个executor的空闲时间超时用于增加减少executor的调度任务是一个定时任务,每100毫秒执行一次调度方法上,首先基于添加时间和我们当前的需要调整我们请求的executor,然后如果一个已存在的executor已经过期了,则杀掉。 updateAndSyncNumExecutorsTarget:更新target数量并同步结果到cluster manager。检查我们已存在的分配和之前的请求超过我们现在的需要。如果满足,truncate target数量并让cluster manager知道以便于它可以取消不需要的等待的请求。如果不满足,并且添加的时间超时,看看我们是否能请求新的executor,并刷新添加时间。 当一个executor(程它为executor X)因为到达了下限而没有被删除,则它不会再被标记位空闲。当有新的executor加入,我们不再在最低下限,则我们必须再次标记executor X为空闲,以使我们不会忘记它是一个被移除的候选。当scheduler的队列是空的时候,就会将addtime设为未设置所有cache的block会被报告给driver,但不包括广播的block当executor执行任务了(busy),就会清除它的idle timeprivate val stageIdToExecutorPlacementHints = new mutable.HashMap[Int, (Int, Map[String, Int])]stageid到tuple的map,tuple是节点和将会在这个节点上运行的task的数量taskstart和blockmanageradded这些事件是在不同的线程执行的,因而顺序不一定,taskstart事件中将对应的executor置为busytaskend,如果executor不再运行任何调度的任务,则标记为idle如果task失败,则会将scheduler置为积压任务的状态,将这个task从这个stage对应的task列表中移除

优秀的个人博客,低调大师

Spark 源码分析 – BlockManagerMaster&Slave

BlockManagerMaster 只是维护一系列对BlockManagerMasterActor的接口, 所有的都是通过tell和askDriverWithReply从BlockManagerMasterActor获取数据 比较鸡肋的类 private[spark] class BlockManagerMaster(var driverActor: ActorRef) extends Logging { /** Remove a dead executor from the driver actor. This is only called on the driver side. */ def removeExecutor(execId: String) /** * Send the driver actor a heart beat from the slave. Returns true if everything works out, * false if the driver does not know about the given block manager, which means the block * manager should re-register. */ def sendHeartBeat(blockManagerId: BlockManagerId): Boolean /** Register the BlockManager's id with the driver. */ def registerBlockManager(blockManagerId: BlockManagerId, maxMemSize: Long, slaveActor: ActorRef) def updateBlockInfo( blockManagerId: BlockManagerId, blockId: String, storageLevel: StorageLevel, memSize: Long, diskSize: Long): Boolean /** Get locations of the blockId from the driver */ def getLocations(blockId: String): Seq[BlockManagerId] /** Get locations of multiple blockIds from the driver */ def getLocations(blockIds: Array[String]): Seq[Seq[BlockManagerId]] /** Get ids of other nodes in the cluster from the driver */ def getPeers(blockManagerId: BlockManagerId, numPeers: Int): Seq[BlockManagerId] /** * Remove a block from the slaves that have it. This can only be used to remove * blocks that the driver knows about. */ def removeBlock(blockId: String) /** * Remove all blocks belonging to the given RDD. */ def removeRdd(rddId: Int, blocking: Boolean) /** * Return the memory status for each block manager, in the form of a map from * the block manager's id to two long values. The first value is the maximum * amount of memory allocated for the block manager, while the second is the * amount of remaining memory. */ def getMemoryStatus: Map[BlockManagerId, (Long, Long)] def getStorageStatus: Array[StorageStatus] /** Stop the driver actor, called only on the Spark driver node */ def stop() { if (driverActor != null) { tell(StopBlockManagerMaster) driverActor = null logInfo("BlockManagerMaster stopped") } } /** Send a one-way message to the master actor, to which we expect it to reply with true. */ private def tell(message: Any) { if (!askDriverWithReply[Boolean](message)) { throw new SparkException("BlockManagerMasterActor returned false, expected true.") } } /** * Send a message to the driver actor and get its result within a default timeout, or * throw a SparkException if this fails. */ private def askDriverWithReply[T](message: Any): T = { // TODO: Consider removing multiple attempts if (driverActor == null) { throw new SparkException("Error sending message to BlockManager as driverActor is null" + "[message =" + message + "]") } var attempts = 0 var lastException: Exception = null while (attempts < AKKA_RETRY_ATTEMPTS) { attempts += 1 try { val future = driverActor.ask(message)(timeout) val result = Await.result(future, timeout) if (result == null) { throw new SparkException("BlockManagerMaster returned null") } return result.asInstanceOf[T] } catch { case ie: InterruptedException => throw ie case e: Exception => lastException = e logWarning("Error sending message to BlockManagerMaster in" + attempts + " attempts", e) } Thread.sleep(AKKA_RETRY_INTERVAL_MS) } throw new SparkException( "Error sending message to BlockManagerMaster [message =" + message + "]", lastException) } } BlockManagerInfo 在BlockManagerMasterActor object中主要就是定义BlockManagerInfo 主要用于管理BlockManager下面的所有block的BlockStatus和hb, 更新和删除 为何要定义在这个地方? private[spark] object BlockManagerMasterActor { case class BlockStatus(storageLevel: StorageLevel, memSize: Long, diskSize: Long) class BlockManagerInfo( val blockManagerId: BlockManagerId, timeMs: Long, val maxMem: Long, val slaveActor: ActorRef) extends Logging { private var _remainingMem: Long = maxMem //BlockManager的memory大小 private var _lastSeenMs: Long = timeMs //BlockManager的heartbeat, 会被不停的更新 // Mapping from block id to its status. private val _blocks = new JHashMap[String, BlockStatus] // buffer每个block的BlockStatus // 这里的memSize, 默认为0, 意思是droppedMemorySize def updateBlockInfo(blockId: String, storageLevel: StorageLevel, memSize: Long, diskSize: Long) { if (_blocks.containsKey(blockId)) { // The block exists on the slave already. val originalLevel: StorageLevel = _blocks.get(blockId).storageLevel if (originalLevel.useMemory) { _remainingMem += memSize } } if (storageLevel.isValid) {// isValid means it is either stored in-memory or on-disk. _blocks.put(blockId, BlockStatus(storageLevel, memSize, diskSize)) if (storageLevel.useMemory) { _remainingMem -= memSize } } else if (_blocks.containsKey(blockId)) { // If isValid is not true, drop the block. val blockStatus: BlockStatus = _blocks.get(blockId) _blocks.remove(blockId) if (blockStatus.storageLevel.useMemory) { _remainingMem += blockStatus.memSize } } } def removeBlock(blockId: String) { if (_blocks.containsKey(blockId)) { _remainingMem += _blocks.get(blockId).memSize _blocks.remove(blockId) } } } } BlockManagerMasterActor 维护各个slave的BlockManagerInfo信息, 以及各个block的locations信息(所属哪个BlockManager) 核心功能就是管理和更新这些元数据, RegisterBlockManager updateBlockInfo heartBeat RemoveRDD, Executor(BlockManager), Block /** * BlockManagerMasterActor is an actor on the master node to track statuses of * all slaves' block managers. */ private[spark] class BlockManagerMasterActor(val isLocal: Boolean) extends Actor with Logging { // Mapping from block manager id to the block manager's information. // Buffer所有的BlockManager的Info private val blockManagerInfo = new mutable.HashMap[BlockManagerId, BlockManagerMasterActor.BlockManagerInfo] // Mapping from executor ID to block manager ID. private val blockManagerIdByExecutor = new mutable.HashMap[String, BlockManagerId] // Mapping from block id to the set of block managers that have the block. // Buffer blockLocation,这里用BlockManagerId来表示location,因为从BlockManagerId可以知道对应的executor private val blockLocations = new JHashMap[String, mutable.HashSet[BlockManagerId]] def receive = { case RegisterBlockManager(blockManagerId, maxMemSize, slaveActor) => register(blockManagerId, maxMemSize, slaveActor) sender ! true // BlockManagerMaster.tell要求返回true // ……这里接收的和BlockManagerMaster中的接口一致, 省略 } // 处理RegisterBlockManager event, 用于slave向master注册自己的blockmanager// 主要就是将slave的BlockManagerInfo注册到master中 private def register(id: BlockManagerId, maxMemSize: Long, slaveActor: ActorRef) { if (id.executorId == "<driver>" && !isLocal) { // 如果本身就是driver,就不需要注册 // Got a register message from the master node; don't register it } else if (!blockManagerInfo.contains(id)) { // 如果包含,说明已经注册过 blockManagerIdByExecutor.get(id.executorId) match { case Some(manager) => // 一个executor应该只有一个bm, 所以如果该executor已经注册过bm …… // A block manager of the same executor already exists. // This should never happen. Let's just quit. logError("Got two different block manager registrations on " + id.executorId) System.exit(1) case None => blockManagerIdByExecutor(id.executorId) = id } blockManagerInfo(id) = new BlockManagerMasterActor.BlockManagerInfo( // 创建新的BlockManagerInfo, 并buffer在blockManagerInfo中 id, System.currentTimeMillis(), maxMemSize, slaveActor) } } // 处理updateBlockInfo private def updateBlockInfo( blockManagerId: BlockManagerId, blockId: String, storageLevel: StorageLevel, memSize: Long, diskSize: Long) { if (!blockManagerInfo.contains(blockManagerId)) { //blockManagerInfo中不包含这个blockManagerId if (blockManagerId.executorId == "<driver>" && !isLocal) { // We intentionally do not register the master (except in local mode), // so we should not indicate failure. sender ! true } else { sender ! false } return } //调用BlockManagerInfo.updateBlockInfo blockManagerInfo(blockManagerId).updateBlockInfo(blockId, storageLevel, memSize, diskSize) var locations: mutable.HashSet[BlockManagerId] = null if (blockLocations.containsKey(blockId)) { locations = blockLocations.get(blockId) } else { locations = new mutable.HashSet[BlockManagerId] blockLocations.put(blockId, locations) //缓存该block的location信息 } if (storageLevel.isValid) { locations.add(blockManagerId) } else { locations.remove(blockManagerId) } // Remove the block from master tracking if it has been removed on all slaves. if (locations.size == 0) { blockLocations.remove(blockId) } sender ! true } // 处理removeRdd, 删除RDD private def removeRdd(rddId: Int): Future[Seq[Int]] = { // First remove the metadata for the given RDD, and then asynchronously remove the blocks from the slaves. val prefix = "rdd_" + rddId + "_" // Find all blocks for the given RDD, remove the block from both blockLocations and // the blockManagerInfo that is tracking the blocks. val blocks = blockLocations.keySet().filter(_.startsWith(prefix)) // 从blockLocations中找出所有该RDD对应的blocks blocks.foreach { blockId => // 从blockManagerInfo和blockLocations中去除这些blocks信息 val bms: mutable.HashSet[BlockManagerId] = blockLocations.get(blockId) bms.foreach(bm => blockManagerInfo.get(bm).foreach(_.removeBlock(blockId))) blockLocations.remove(blockId) } // Ask the slaves to remove the RDD, and put the result in a sequence of Futures. // The dispatcher is used as an implicit argument into the Future sequence construction. import context.dispatcher val removeMsg = RemoveRdd(rddId) Future.sequence(blockManagerInfo.values.map { bm => // Future.sequence, Transforms a Traversable[Future[A]] into a Future[Traversable[A] bm.slaveActor.ask(removeMsg)(akkaTimeout).mapTo[Int] // 将RemoveRDD的msg发送给每个slave actors }.toSeq) } //处理removeExecutor //删除Executor上的BlockManager, 名字起的不好 private def removeExecutor(execId: String) { logInfo("Trying to remove executor " + execId + " from BlockManagerMaster.") blockManagerIdByExecutor.get(execId).foreach(removeBlockManager) } private def removeBlockManager(blockManagerId: BlockManagerId) { val info = blockManagerInfo(blockManagerId) // Remove the block manager from blockManagerIdByExecutor. blockManagerIdByExecutor -= blockManagerId.executorId // Remove it from blockManagerInfo and remove all the blocks. blockManagerInfo.remove(blockManagerId) val iterator = info.blocks.keySet.iterator while (iterator.hasNext) { val blockId = iterator.next val locations = blockLocations.get(blockId) locations -= blockManagerId if (locations.size == 0) { blockLocations.remove(locations) } } } // 处理sendHeartBeat// blockManager的hb通过blockManagerInfo的LastSeenMs来表示 private def heartBeat(blockManagerId: BlockManagerId): Boolean = { if (!blockManagerInfo.contains(blockManagerId)) { blockManagerId.executorId == "<driver>" && !isLocal } else { blockManagerInfo(blockManagerId).updateLastSeenMs() true } } // 处理removeBlock // Remove a block from the slaves that have it. This can only be used to remove // blocks that the master knows about. private def removeBlockFromWorkers(blockId: String) { val locations = blockLocations.get(blockId) if (locations != null) { locations.foreach { blockManagerId: BlockManagerId => val blockManager = blockManagerInfo.get(blockManagerId) if (blockManager.isDefined) { // Remove the block from the slave's BlockManager. // Doesn't actually wait for a confirmation and the message might get lost. // If message loss becomes frequent, we should add retry logic here. blockManager.get.slaveActor ! RemoveBlock(blockId) } } } } BlockManagerSlaveActor Master可用发给的slave的message就2种, 所以很简单...过于简单 因为他只处理master发送来的event, 而大部分对于数据的读写等, 在BlockManager中直接实现了 /** * An actor to take commands from the master to execute options. For example, * this is used to remove blocks from the slave's BlockManager. */ class BlockManagerSlaveActor(blockManager: BlockManager) extends Actor { override def receive = { case RemoveBlock(blockId) => blockManager.removeBlock(blockId) case RemoveRdd(rddId) => val numBlocksRemoved = blockManager.removeRdd(rddId) sender ! numBlocksRemoved } } 本文章摘自博客园,原文发布日期:2014-01-10

优秀的个人博客,低调大师

Storm-源码分析- Thrift的使用

1 IDL 首先是storm.thrift, 作为IDL里面定义了用到的数据结构和service 然后backtype.storm.generated, 存放从IDL通过Thrift自动转化成的Java代码 比如对于nimbus service 在IDL的定义为, service Nimbus { void submitTopology(1: string name, 2: string uploadedJarLocation, 3: string jsonConf, 4: StormTopology topology) throws (1: AlreadyAliveException e, 2: InvalidTopologyException ite); void submitTopologyWithOpts(1: string name, 2: string uploadedJarLocation, 3: string jsonConf, 4: StormTopology topology, 5: SubmitOptions options) throws (1: AlreadyAliveException e, 2: InvalidTopologyException ite); void killTopology(1: string name) throws (1: NotAliveException e); void killTopologyWithOpts(1: string name, 2: KillOptions options) throws (1: NotAliveException e); void activate(1: string name) throws (1: NotAliveException e); void deactivate(1: string name) throws (1: NotAliveException e); void rebalance(1: string name, 2: RebalanceOptions options) throws (1: NotAliveException e, 2: InvalidTopologyException ite); // need to add functions for asking about status of storms, what nodes they're running on, looking at task logs string beginFileUpload(); void uploadChunk(1: string location, 2: binary chunk); void finishFileUpload(1: string location); string beginFileDownload(1: string file); //can stop downloading chunks when receive 0-length byte array back binary downloadChunk(1: string id); // returns json string getNimbusConf(); // stats functions ClusterSummary getClusterInfo(); TopologyInfo getTopologyInfo(1: string id) throws (1: NotAliveException e); //returns json string getTopologyConf(1: string id) throws (1: NotAliveException e); StormTopology getTopology(1: string id) throws (1: NotAliveException e); StormTopology getUserTopology(1: string id) throws (1: NotAliveException e); } 而对应在Nimbus.java的Java代码如下, public class Nimbus { public interface Iface { public void submitTopology(String name, String uploadedJarLocation, String jsonConf, StormTopology topology) throws AlreadyAliveException, InvalidTopologyException, org.apache.thrift7.TException; public void submitTopologyWithOpts(String name, String uploadedJarLocation, String jsonConf, StormTopology topology, SubmitOptions options) throws AlreadyAliveException, InvalidTopologyException, org.apache.thrift7.TException; public void killTopology(String name) throws NotAliveException, org.apache.thrift7.TException; public void killTopologyWithOpts(String name, KillOptions options) throws NotAliveException, org.apache.thrift7.TException; public void activate(String name) throws NotAliveException, org.apache.thrift7.TException; public void deactivate(String name) throws NotAliveException, org.apache.thrift7.TException; public void rebalance(String name, RebalanceOptions options) throws NotAliveException, InvalidTopologyException, org.apache.thrift7.TException; public String beginFileUpload() throws org.apache.thrift7.TException; public void uploadChunk(String location, ByteBuffer chunk) throws org.apache.thrift7.TException; public void finishFileUpload(String location) throws org.apache.thrift7.TException; public String beginFileDownload(String file) throws org.apache.thrift7.TException; public ByteBuffer downloadChunk(String id) throws org.apache.thrift7.TException; public String getNimbusConf() throws org.apache.thrift7.TException; public ClusterSummary getClusterInfo() throws org.apache.thrift7.TException; public TopologyInfo getTopologyInfo(String id) throws NotAliveException, org.apache.thrift7.TException; public String getTopologyConf(String id) throws NotAliveException, org.apache.thrift7.TException; public StormTopology getTopology(String id) throws NotAliveException, org.apache.thrift7.TException; public StormTopology getUserTopology(String id) throws NotAliveException, org.apache.thrift7.TException; } 2 Client 1. 首先Get Client, NimbusClient client = NimbusClient.getConfiguredClient(conf); 看看backtype.storm.utils下面的client.getConfiguredClient的逻辑, 只是从配置中取出nimbus的host:port, 并new NimbusClient public static NimbusClient getConfiguredClient(Map conf) { try { String nimbusHost = (String) conf.get(Config.NIMBUS_HOST); int nimbusPort = Utils.getInt(conf.get(Config.NIMBUS_THRIFT_PORT)); return new NimbusClient(conf, nimbusHost, nimbusPort); } catch (TTransportException ex) { throw new RuntimeException(ex); } } NimbusClient 继承自ThriftClient, public class NimbusClient extends ThriftClient ThriftClient又做了什么? 关键是怎么进行数据序列化和怎么将数据传输到remote 这里看出Thrift对Transport和Protocol的封装 对于Transport, 其实就是对Socket的封装, 使用TSocket(host, port) 然后对于protocol, 默认使用TBinaryProtocol, 如果你不指定的话 public ThriftClient(Map storm_conf, String host, int port, Integer timeout) throws TTransportException { try { //locate login configuration Configuration login_conf = AuthUtils.GetConfiguration(storm_conf); //construct a transport plugin ITransportPlugin transportPlugin = AuthUtils.GetTransportPlugin(storm_conf, login_conf); //create a socket with server if(host==null) { throw new IllegalArgumentException("host is not set"); } if(port<=0) { throw new IllegalArgumentException("invalid port: "+port); } TSocket socket = new TSocket(host, port); if(timeout!=null) { socket.setTimeout(timeout); } final TTransport underlyingTransport = socket; //establish client-server transport via plugin _transport = transportPlugin.connect(underlyingTransport, host); } catch (IOException ex) { throw new RuntimeException(ex); } _protocol = null; if (_transport != null) _protocol = new TBinaryProtocol(_transport); } 2. 调用任意RPC 那么就看看submitTopologyWithOpts client.getClient().submitTopologyWithOpts(name, submittedJar, serConf, topology, opts); 可以看出上面的Nimbus的interface里面有这个方法的定义, 而且Thrift不仅仅自动产生java interface, 而且还提供整个RPC client端的实现 public void submitTopologyWithOpts(String name, String uploadedJarLocation, String jsonConf, StormTopology topology, SubmitOptions options) throws AlreadyAliveException, InvalidTopologyException, org.apache.thrift7.TException { send_submitTopologyWithOpts(name, uploadedJarLocation, jsonConf, topology, options); recv_submitTopologyWithOpts(); } 分两步, 首先send_submitTopologyWithOpts, 调用sendBase 接着, recv_submitTopologyWithOpts, 调用receiveBase protected void sendBase(String methodName, TBase args) throws TException { oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_)); args.write(oprot_); oprot_.writeMessageEnd(); oprot_.getTransport().flush(); } protected void receiveBase(TBase result, String methodName) throws TException { TMessage msg = iprot_.readMessageBegin(); if (msg.type == TMessageType.EXCEPTION) { TApplicationException x = TApplicationException.read(iprot_); iprot_.readMessageEnd(); throw x; } if (msg.seqid != seqid_) { throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, methodName + " failed: out of sequence response"); } result.read(iprot_); iprot_.readMessageEnd(); } 可以看出Thrift对protocol的封装, 不需要自己处理序列化, 调用protocol的接口搞定 3 Server Thrift强大的地方是, 实现了整个协议栈而不光只是IDL的转化, 对于server也给出多种实现 下面看看在nimbus server端, 是用clojure来写的 可见其中使用Thrift封装的NonblockingServerSocket, THsHaServer, TBinaryProtocol, Proccessor, 非常简单 其中processor会使用service-handle来处理recv到的数据, 所以作为使用者只需要在service-handle中实现Nimbus$Iface, 其他和server相关的, Thrift都已经帮你封装好了, 这里使用的IDL也在backtype.storm.generated, 因为clojure基于JVM所以IDL只需要转化成Java即可. (defn launch-server! [conf nimbus] (validate-distributed-mode! conf) (let [service-handler (service-handler conf nimbus) options (-> (TNonblockingServerSocket. (int (conf NIMBUS-THRIFT-PORT))) (THsHaServer$Args.) (.workerThreads 64) (.protocolFactory (TBinaryProtocol$Factory.)) (.processor (Nimbus$Processor. service-handler)) ) server (THsHaServer. options)] (.addShutdownHook (Runtime/getRuntime) (Thread. (fn [] (.shutdown service-handler) (.stop server)))) (log-message "Starting Nimbus server...") (.serve server))) 本文章摘自博客园,原文发布日期:2013-06-04

优秀的个人博客,低调大师

Storm-源码分析- Multimethods使用例子

1. storm通过multimethods来区分local和distributed模式 当调用launch-worker的时候, clojure会自动根据defmulti里面定义的fn来判断是调用哪个版本的launch-worker (defmulti launch-worker (fn [supervisor & _] (cluster-mode (:conf supervisor)))) (defmethod launch-worker :distributed [supervisor storm-id port worker-id] (let [conf (:conf supervisor) storm-home (System/getProperty "storm.home") stormroot (supervisor-stormdist-root conf storm-id) stormjar (supervisor-stormjar-path stormroot) storm-conf (read-supervisor-storm-conf conf storm-id) classpath (add-to-classpath (current-classpath) [stormjar]) childopts (.replaceAll (str (conf WORKER-CHILDOPTS) " " (storm-conf TOPOLOGY-WORKER-CHILDOPTS)) "%ID%" (str port)) logfilename (str "worker-" port ".log") command (str "java -server " childopts " -Djava.library.path=" (conf JAVA-LIBRARY-PATH) " -Dlogfile.name=" logfilename " -Dstorm.home=" storm-home " -Dlogback.configurationFile=" storm-home "/logback/cluster.xml" " -cp " classpath " backtype.storm.daemon.worker " (java.net.URLEncoder/encode storm-id) " " (:assignment-id supervisor) " " port " " worker-id)] (log-message "Launching worker with command: " command) (launch-process command :environment {"LD_LIBRARY_PATH" (conf JAVA-LIBRARY-PATH)}) )) (defmethod launch-worker :local [supervisor storm-id port worker-id] (let [conf (:conf supervisor) pid (uuid) worker (worker/mk-worker conf (:shared-context supervisor) storm-id (:assignment-id supervisor) port worker-id)] (psim/register-process pid worker) (swap! (:worker-thread-pids-atom supervisor) assoc worker-id pid) )) 2. storm通过multimethods来区分blot和spout 在executor.clj中通过multimethods来区分blot和spout不同的创建逻辑 (defmulti mk-threads executor-selector) (defmulti mk-executor-stats executor-selector) (defmulti close-component executor-selector) 本文章摘自博客园,原文发布日期:2013-06-28

优秀的个人博客,低调大师

spark源码分析之任务调度篇

DAG的生成 概述 DAG(Directed Acyclic Graph)叫做有向无环图,原始的RDD通过一系列的转换就就形成了DAG,根据RDD之间的依赖关系的不同将DAG划分成不同的Stage,对于窄依赖,partition的转换处理在Stage中完成计算。对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分Stage的依据。 窄依赖 指的是每一个父RDD的Partition最多被子RDD的一个Partition使用宽依赖 指的是多个子RDD的Partition会依赖同一个父RDD的Partition DAGScheduler调度队列 当我们看完Executor的创建与启动流程后,我们继续在SparkContext的构造方法中继续查看 class SparkContext(config: SparkConf) extends Logging with ExecutorAllocationClient { 。。。。。。 private[spark] def createSparkEnv( conf: SparkConf, isLocal: Boolean, listenerBus: LiveListenerBus): SparkEnv = { //通过SparkEnv来创建createDriverEnv SparkEnv.createDriverEnv(conf, isLocal, listenerBus) } //在这里调用了createSparkEnv,返回一个SparkEnv对象,这个对象里面有很多重要属性,最重要的ActorSystem private[spark] val env = createSparkEnv(conf, isLocal, listenerBus) SparkEnv.set(env) //创建taskScheduler // Create and start the scheduler private[spark] var (schedulerBackend, taskScheduler) = SparkContext.createTaskScheduler(this, master) //创建DAGScheduler dagScheduler = new DAGScheduler(this) //启动TaksScheduler taskScheduler.start() 。。。。。 } 在构造方法中还创建了一个DAGScheduler对象,这个类的任务就是用来划分Stage任务的,构造方法中初始化了 private[scheduler] val eventProcessLoop = new DAGSchedulerEventProcessLoop(this)DAGSchedulerEventProcessLoop是一个事件总线对象,用来负责任务的分发,在构造方法eventProcessLoop.start()被调用,该方法是父类EventLoop的start def start(): Unit = { if (stopped.get) { throw new IllegalStateException(name + " has already been stopped") } // Call onStart before starting the event thread to make sure it happens before onReceive onStart() eventThread.start() } 调用了eventThread的start方法,开启了一个线程 private val eventThread = new Thread(name) { setDaemon(true) override def run(): Unit = { try { while (!stopped.get) { val event = eventQueue.take() try { onReceive(event) } catch { case NonFatal(e) => { try { onError(e) } catch { case NonFatal(e) => logError("Unexpected error in " + name, e) } } } } } catch { case ie: InterruptedException => // exit even if eventQueue is not empty case NonFatal(e) => logError("Unexpected error in " + name, e) } } } run方法中不断的从LinkedBlockingDeque阻塞队列中取消息,然后调用onReceive(event)方法,该方法是由子类DAGSchedulerEventProcessLoop实现的 override def onReceive(event: DAGSchedulerEvent): Unit = event match { case JobSubmitted(jobId, rdd, func, partitions, allowLocal, callSite, listener, properties) => //调用dagScheduler来出来提交任务 dagScheduler.handleJobSubmitted(jobId, rdd, func, partitions, allowLocal, callSite, listener, properties) case StageCancelled(stageId) => dagScheduler.handleStageCancellation(stageId) case JobCancelled(jobId) => dagScheduler.handleJobCancellation(jobId) case JobGroupCancelled(groupId) => dagScheduler.handleJobGroupCancelled(groupId) case AllJobsCancelled => dagScheduler.doCancelAllJobs() case ExecutorAdded(execId, host) => dagScheduler.handleExecutorAdded(execId, host) case ExecutorLost(execId) => dagScheduler.handleExecutorLost(execId, fetchFailed = false) case BeginEvent(task, taskInfo) => dagScheduler.handleBeginEvent(task, taskInfo) case GettingResultEvent(taskInfo) => dagScheduler.handleGetTaskResult(taskInfo) case completion @ CompletionEvent(task, reason, _, _, taskInfo, taskMetrics) => dagScheduler.handleTaskCompletion(completion) case TaskSetFailed(taskSet, reason) => dagScheduler.handleTaskSetFailed(taskSet, reason) case ResubmitFailedStages => dagScheduler.resubmitFailedStages() } onReceive中会匹配到传入的任务类型,执行相应的逻辑。到此DAGScheduler的调度队列会一直挂起,不断轮询队列中的任务。 DAG提交Task任务流程 当RDD经过一系列的转换Transformation方法后,最终要执行Action动作方法,这里比如WordCount程序中最后调用collect()方法时会将数据提交到Master上运行,任务真正的被执行,这里的方法执行过程如下 /** * Return an array that contains all of the elements in this RDD. */ def collect(): Array[T] = { val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray) Array.concat(results: _*) } sc 是SparkContext对象,这里调用 一个runJob该方法调用多次重载的方法后,该方法最终会调用 dagScheduler.runJob def runJob[T, U: ClassTag]( rdd: RDD[T], func: (TaskContext, Iterator[T]) => U, partitions: Seq[Int], allowLocal: Boolean, resultHandler: (Int, U) => Unit) { if (stopped) { throw new IllegalStateException("SparkContext has been shutdown") } val callSite = getCallSite val cleanedFunc = clean(func) logInfo("Starting job: " + callSite.shortForm) if (conf.getBoolean("spark.logLineage", false)) { logInfo("RDD's recursive dependencies:\n" + rdd.toDebugString) } //dagScheduler出现了,可以切分stage dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, allowLocal, resultHandler, localProperties.get) progressBar.foreach(_.finishAll()) rdd.doCheckpoint() } dagScheduler的runJob是我们比较关心的 def runJob[T, U: ClassTag]( 。。。。。 val waiter = submitJob(rdd, func, partitions, callSite, allowLocal, resultHandler, properties) waiter.awaitResult() match { case JobSucceeded => { logInfo("Job %d finished: %s, took %f s".format (waiter.jobId, callSite.shortForm, (System.nanoTime - start) / 1e9)) } case JobFailed(exception: Exception) => logInfo("Job %d failed: %s, took %f s".format (waiter.jobId, callSite.shortForm, (System.nanoTime - start) / 1e9)) throw exception } } 这里面的我们主要看的是submitJob(rdd, func, partitions, callSite, allowLocal, resultHandler, properties)提交任务 def submitJob[T, U]( rdd: RDD[T], func: (TaskContext, Iterator[T]) => U, partitions: Seq[Int], callSite: CallSite, allowLocal: Boolean, resultHandler: (Int, U) => Unit, properties: Properties): JobWaiter[U] = { 。。。。。。 //把job加入到任务队列里面 eventProcessLoop.post(JobSubmitted( jobId, rdd, func2, partitions.toArray, allowLocal, callSite, waiter, properties)) waiter } 这里比较关键的地方是 eventProcessLoop.post往任务队列中加入一个JobSubmitted类型的任务,eventProcessLoop是在构造方法中就初始化好的事件总线对象,内部有一个线程不断的轮询队列里的任务 轮询到任务后调用onReceive方法匹配任务类型,在这里我们提交的任务是JobSubmitted类型 case JobSubmitted(jobId, rdd, func, partitions, allowLocal, callSite, listener, properties) => //调用dagScheduler来出来提交任务 dagScheduler.handleJobSubmitted(jobId, rdd, func, partitions, allowLocal, callSite, listener, properties) 调用了handleJobSubmitted方法,接下来查看该方法 private[scheduler] def handleJobSubmitted(jobId: Int, finalRDD: RDD[_], func: (TaskContext, Iterator[_]) => _, partitions: Array[Int], allowLocal: Boolean, callSite: CallSite, listener: JobListener, properties: Properties) { var finalStage: Stage = null try { // New stage creation may throw an exception if, for example, jobs are run on a // HadoopRDD whose underlying HDFS files have been deleted. //最终的stage finalStage = newStage(finalRDD, partitions.size, None, jobId, callSite) } catch { case e: Exception => logWarning("Creating new stage failed due to exception - job: " + jobId, e) listener.jobFailed(e) return } 。。。。 submitStage(finalStage) } 上面的代码中,调用了newStage进行任务的划分,该方法是划分任务的核心方法,划分任务的根据最后一个依赖关系作为开始,通过递归,将每个宽依赖做为切分Stage的依据,切分Stage的过程是流程中的一环,但在这里不详细阐述,当任务切分完毕后,代码继续执行来到submitStage(finalStage)这里开始进行任务提交 这里以递归的方式进行任务的提交 //递归的方式提交stage private def submitStage(stage: Stage) { val jobId = activeJobForStage(stage) if (jobId.isDefined) { logDebug("submitStage(" + stage + ")") if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) { val missing = getMissingParentStages(stage).sortBy(_.id) logDebug("missing: " + missing) if (missing == Nil) { logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents") //提交任务 submitMissingTasks(stage, jobId.get) } else { for (parent <- missing) { submitStage(parent) } waitingStages += stage } } } else { abortStage(stage, "No active job for stage " + stage.id) } } 调用submitMissingTasks(stage, jobId.get)提交任务,将每一个Stage和jobId传入 private def submitMissingTasks(stage: Stage, jobId: Int) { 。。。。。 if (tasks.size > 0) { logInfo("Submitting " + tasks.size + " missing tasks from " + stage + " (" + stage.rdd + ")") stage.pendingTasks ++= tasks logDebug("New pending tasks: " + stage.pendingTasks) //taskScheduler提交task taskScheduler.submitTasks( new TaskSet(tasks.toArray, stage.id, stage.newAttemptId(), stage.jobId, properties)) stage.latestInfo.submissionTime = Some(clock.getTimeMillis()) } else { // Because we posted SparkListenerStageSubmitted earlier, we should mark // the stage as completed here in case there are no tasks to run markStageAsFinished(stage, None) logDebug("Stage " + stage + " is actually done; %b %d %d".format( stage.isAvailable, stage.numAvailableOutputs, stage.numPartitions)) } } 这里的代码我们需要关注的是`taskScheduler.submitTasks( new TaskSet(tasks.toArray, stage.id, stage.newAttemptId(), stage.jobId, properties))` 创建了一个TaskSet对象,将所有任务的信息封装,包括task任务列表,stageId,任务id,分区数参数等 Task任务调度 override def submitTasks(taskSet: TaskSet) { val tasks = taskSet.tasks logInfo("Adding task set " + taskSet.id + " with " + tasks.length + " tasks") this.synchronized { //创建TaskSetManager保存了taskSet任务列表 val manager = createTaskSetManager(taskSet, maxTaskFailures) activeTaskSets(taskSet.id) = manager //将任务加入调度池 schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties) if (!isLocal && !hasReceivedTask) { starvationTimer.scheduleAtFixedRate(new TimerTask() { override def run() { if (!hasLaunchedTask) { logWarning("Initial job has not accepted any resources; " + "check your cluster UI to ensure that workers are registered " + "and have sufficient resources") } else { this.cancel() } } }, STARVATION_TIMEOUT, STARVATION_TIMEOUT) } hasReceivedTask = true } //接受任务 backend.reviveOffers() } 该方法比较重要,主要将任务加入调度池,最后调用了backend.reviveOffers()这里的backend是CoarseGrainedSchedulerBackend一个Executor任务调度对象 override def reviveOffers() { //自己给自己发消息 driverActor ! ReviveOffers } 这里用了内部的DriverActor对象发送了一个内部消息给自己,接下来查看receiver方法接受的消息 case ReviveOffers => makeOffers() 收到消息后调用了 makeOffers()方法 def makeOffers() { launchTasks(scheduler.resourceOffers(executorDataMap.map { case (id, executorData) => new WorkerOffer(id, executorData.executorHost, executorData.freeCores) }.toSeq)) } makeOffers方法中,将Executor的信息集合与调度池中的Tasks封装成WokerOffers列表传给了launchTasks def launchTasks(tasks: Seq[Seq[TaskDescription]]) { for (task <- tasks.flatten) { 。。。。。。 //把task序列化 val serializedTask = ser.serialize(task) 。。。。。 val executorData = executorDataMap(task.executorId) executorData.freeCores -= scheduler.CPUS_PER_TASK //把序列化好的task发送给Executor executorData.executorActor ! LaunchTask(new SerializableBuffer(serializedTask)) } } } launchTasks方法将遍历Tasks集合,每个Task任务序列化,发送启动Task执行消息的给Executor Executor的onReceive方法 //DriverActor发送给Executor的启动Task的消息 case LaunchTask(data) => if (executor == null) { logError("Received LaunchTask command but executor was null") System.exit(1) } else { val ser = env.closureSerializer.newInstance() //把Task反序列化 val taskDesc = ser.deserialize[TaskDescription](data.value) logInfo("Got assigned task " + taskDesc.taskId) //启动task executor.launchTask(this, taskId = taskDesc.taskId, attemptNumber = taskDesc.attemptNumber, taskDesc.name, taskDesc.serializedTask) } Executor收到DriverActor发送的启动Task的消息,这里才开始真正执行任务了,将收到的Task序列化信息反序列化,调用Executor的launchTask方法执行任务 def launchTask( context: ExecutorBackend, taskId: Long, attemptNumber: Int, taskName: String, serializedTask: ByteBuffer) { //把task的描述信息放到了一份TaskRunner val tr = new TaskRunner(context, taskId = taskId, attemptNumber = attemptNumber, taskName, serializedTask) runningTasks.put(taskId, tr) //然后把TaskRunner丢到线程池里面 threadPool.execute(tr) } launchTask内将Task提交到线程池去运行,TaskRunner是Runnable对象,里面的run方法执行了我们app生成的每一个RDD的链上的逻辑。

优秀的个人博客,低调大师

spark源码分析之Checkpoint的过程

概述 checkpoint 的机制保证了需要访问重复数据的应用 Spark 的DAG执行行图可能很庞大,task 中计算链可能会很长,这时如果 task 中途运行出错,那么 task 的整个需要重算非常耗时,因此,有必要将计算代价较大的 RDD checkpoint 一下,当下游 RDD 计算出错时,可以直接从 checkpoint 过的 RDD 那里读取数据继续算。 我们先来看一个例子,checkpoint的使用 import org.apache.spark.SparkContext import org.apache.spark.SparkContext._ import org.apache.spark.SparkConf object CheckPointTest { def main(args: Array[String]) { val sc: SparkContext = SparkContext.getOrCreate(new SparkConf().setAppName("ck").setMaster("local[2]")) sc.setCheckpointDir("/Users/kinge/ck") val rdd: RDD[(String, Int)] = sc.textFile("").map{x=>(x,1) }.reduceByKey(_+_) rdd.checkpoint() rdd.count() rdd.groupBy(x=>x._2).collect().foreach(println) } } checkpoint流程分析 checkpoint初始化 我们可以看到最先调用了SparkContext的setCheckpointDir 设置了一个checkpoint 目录我们跟进这个方法看一下 /** * Set the directory under which RDDs are going to be checkpointed. The directory must * be a HDFS path if running on a cluster. */ def setCheckpointDir(directory: String) { // If we are running on a cluster, log a warning if the directory is local. // Otherwise, the driver may attempt to reconstruct the checkpointed RDD from // its own local file system, which is incorrect because the checkpoint files // are actually on the executor machines. if (!isLocal && Utils.nonLocalPaths(directory).isEmpty) { logWarning("Checkpoint directory must be non-local " + "if Spark is running on a cluster: " + directory) } //利用hadoop的api创建了一个hdfs目录 checkpointDir = Option(directory).map { dir => val path = new Path(dir, UUID.randomUUID().toString) val fs = path.getFileSystem(hadoopConfiguration) fs.mkdirs(path) fs.getFileStatus(path).getPath.toString } } 这个方法挺简单的,就创建了一个目录,接下来我们看RDD核心的checkpoint 方法,跟进去 def checkpoint(): Unit = RDDCheckpointData.synchronized { if (context.checkpointDir.isEmpty) { throw new SparkException("Checkpoint directory has not been set in the SparkContext") } else if (checkpointData.isEmpty) { checkpointData = Some(new ReliableRDDCheckpointData(this)) } } 这个方法没有返回值,逻辑只有一个判断,checkpointDir刚才设置过了,不为空,然后创建了一个ReliableRDDCheckpointData ,我们来看ReliableRDDCheckpointData /** * An implementation of checkpointing that writes the RDD data to reliable storage. * This allows drivers to be restarted on failure with previously computed state. */ private[spark] class ReliableRDDCheckpointData[T: ClassTag](@transient rdd: RDD[T]) extends RDDCheckpointData[T](rdd) with Logging { 。。。。。 } 这个ReliableRDDCheckpointData 的父类RDDCheckpointData 我们再继续看它的父类 /** * RDD 需要经过 * [ Initialized --> CheckpointingInProgress--> Checkpointed ] * 这几个阶段才能被 checkpoint。 */ private[spark] object CheckpointState extends Enumeration { type CheckpointState = Value val Initialized, CheckpointingInProgress, Checkpointed = Value } private[spark] abstract class RDDCheckpointData[T: ClassTag](@transient rdd: RDD[T]) extends Serializable { import CheckpointState._ // The checkpoint state of the associated RDD. protected var cpState = Initialized 。。。。。。 } RDD 需要经过 [ Initialized --> CheckpointingInProgress--> Checkpointed ] 这几个阶段才能被 checkpoint。这类里面有一个枚举来标识CheckPoint的状态,第一次初始化时是Initialized。checkpoint这个一步已经完成了,回到我们的RDD成员变量里checkpointData这个变量指向的RDDCheckpointData 的实例。 Checkpoint初始化时序图 checkpoint什么时候写入数据 我们知道一个spark job运行最终会调用SparkContext的runJob方法将任务提交给Executor去执行,我们来看runJob def runJob[T, U: ClassTag]( rdd: RDD[T], func: (TaskContext, Iterator[T]) => U, partitions: Seq[Int], resultHandler: (Int, U) => Unit): Unit = { if (stopped.get()) { throw new IllegalStateException("SparkContext has been shutdown") } val callSite = getCallSite val cleanedFunc = clean(func) logInfo("Starting job: " + callSite.shortForm) if (conf.getBoolean("spark.logLineage", false)) { logInfo("RDD's recursive dependencies:\n" + rdd.toDebugString) } dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, resultHandler, localProperties.get) progressBar.foreach(_.finishAll()) rdd.doCheckpoint() } 最后一行代码调用了doCheckpoint,在dagScheduler将任务提交给集群运行之后,我来看这个doCheckpoint 方法 private[spark] def doCheckpoint(): Unit = { RDDOperationScope.withScope(sc, "checkpoint", allowNesting = false, ignoreParent = true) { if (!doCheckpointCalled) { doCheckpointCalled = true if (checkpointData.isDefined) { checkpointData.get.checkpoint() } else { //遍历依赖的rdd,调用每个rdd的doCheckpoint方法 dependencies.foreach(_.rdd.doCheckpoint()) } } } } 这个是一个递归,遍历RDD依赖链条,当rdd是checkpointData不为空时,调用 checkpointData的checkpoint()方法。还记得checkpointData 类型是什么吗?就是RDDCheckpointData ,我们来看它的checkpoint方法,以下 final def checkpoint(): Unit = { // Guard against multiple threads checkpointing the same RDD by // atomically flipping the state of this RDDCheckpointData RDDCheckpointData.synchronized { if (cpState == Initialized) { //1、标记当前状态为正在checkpoint中 cpState = CheckpointingInProgress } else { return } } //2 这里调用的是子类的doCheckpoint() val newRDD = doCheckpoint() // 3 标记checkpoint已完成,清空RDD依赖 RDDCheckpointData.synchronized { cpRDD = Some(newRDD) cpState = Checkpointed rdd.markCheckpointed() } } 这个方法开始做checkpoint操作了,将doCheckpoint 交给子类去实现checkponit的逻辑,我们去看子类怎么实现doCheckpoint protected override def doCheckpoint(): CheckpointRDD[T] = { // Create the output path for the checkpoint val path = new Path(cpDir) val fs = path.getFileSystem(rdd.context.hadoopConfiguration) if (!fs.mkdirs(path)) { throw new SparkException(s"Failed to create checkpoint path $cpDir") } //需要的配置文件(如 core-site.xml 等)broadcast 到其他 worker 节点的 blockManager。 val broadcastedConf = rdd.context.broadcast( new SerializableConfiguration(rdd.context.hadoopConfiguration)) //向集群提交一个Job去执行checkpoint操作,将RDD序列化到HDFS目录上 rdd.context.runJob(rdd, ReliableCheckpointRDD.writeCheckpointFile[T](cpDir, broadcastedConf) _) // 为该 rdd 生成一个新的依赖,设置该 rdd 的 parent rdd 为 //CheckpointRDD,该 CheckpointRDD 负责以后读取在文件系统上的 //checkpoint 文件,生成该 rdd 的 partition。 val newRDD = new ReliableCheckpointRDD[T](rdd.context, cpDir) if (newRDD.partitions.length != rdd.partitions.length) { throw new SparkException( s"Checkpoint RDD $newRDD(${newRDD.partitions.length}) has different " + s"number of partitions from original RDD $rdd(${rdd.partitions.length})") } // 是否清除checkpoint文件如果超出引用的资源范围 if (rdd.conf.getBoolean("spark.cleaner.referenceTracking.cleanCheckpoints", false)) { rdd.context.cleaner.foreach { cleaner => cleaner.registerRDDCheckpointDataForCleanup(newRDD, rdd.id) } } logInfo(s"Done checkpointing RDD ${rdd.id} to $cpDir, new parent is RDD ${newRDD.id}") // 将新产生的RDD返回给父类 newRDD } 上面的代码最终会返回新的CheckpointRDD ,父类将它复值给成员变量cpRDD,最终标记当前状态为Checkpointed并清空当RDD的依赖链。到此Checkpoint的数据就被序列化到HDFS上了。 Checkpoint 写数据时序图 checkpoint什么时候读取数据 我们知道Task是saprk运行任务的最小单元,当Task执行失败的时候spark会重新计算,这里Task进行计算的地方就是读取checkpoint的入口。我们可以看一下ShuffleMapTask 里的计算方法runTask,如下 override def runTask(context: TaskContext): MapStatus = { 。。。。。。。 try { val manager = SparkEnv.get.shuffleManager writer = manager.getWriter[Any, Any](dep.shuffleHandle, partitionId, context) //调用rdd.iterator,迭代每个partition里的数据,计算并写入磁盘 writer.write(rdd.iterator(partition, context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]]) writer.stop(success = true).get } catch { case e: Exception => try { if (writer != null) { writer.stop(success = false) } } catch { case e: Exception => log.debug("Could not stop writer", e) } throw e } } 这是spark真正调用计算方法的逻辑runTask调用 rdd.iterator() 去计算该 rdd 的 partition 的,我们来看RDD的iterator() final def iterator(split: Partition, context: TaskContext): Iterator[T] = { if (storageLevel != StorageLevel.NONE) { SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel) } else { computeOrReadCheckpoint(split, context) } } 这里会继续调用computeOrReadCheckpoint ,我们看该方法 ** * Compute an RDD partition or read it from a checkpoint if the RDD is checkpointing. */ private[spark] def computeOrReadCheckpoint(split: Partition, context: TaskContext): Iterator[T] = { if (isCheckpointedAndMaterialized) { firstParent[T].iterator(split, context) } else { compute(split, context) } } 当调用 rdd.iterator() 去计算该 rdd 的 partition 的时候,会调用 computeOrReadCheckpoint(split: Partition) 去查看该 rdd 是否被 checkpoint 过了,如果是,就调用该 rdd 的 parent rdd 的 iterator() 也就是 CheckpointRDD.iterator(),否则直接调用该RDD的compute, 那么我们就跟进CheckpointRDD的compute /** * Read the content of the checkpoint file associated with the given partition. */ override def compute(split: Partition, context: TaskContext): Iterator[T] = { val file = new Path(checkpointPath, ReliableCheckpointRDD.checkpointFileName(split.index)) ReliableCheckpointRDD.readCheckpointFile(file, broadcastedConf, context) } 这里就两行代码,意思是从Path上读取我们的CheckPoint数据,看一下readCheckpointFile /** * Read the content of the specified checkpoint file. */ def readCheckpointFile[T]( path: Path, broadcastedConf: Broadcast[SerializableConfiguration], context: TaskContext): Iterator[T] = { val env = SparkEnv.get // 用hadoop API 读取HDFS上的数据 val fs = path.getFileSystem(broadcastedConf.value.value) val bufferSize = env.conf.getInt("spark.buffer.size", 65536) val fileInputStream = fs.open(path, bufferSize) val serializer = env.serializer.newInstance() val deserializeStream = serializer.deserializeStream(fileInputStream) // Register an on-task-completion callback to close the input stream. context.addTaskCompletionListener(context => deserializeStream.close()) //反序列化数据后转换为一个Iterator deserializeStream.asIterator.asInstanceOf[Iterator[T]] } CheckpointRDD 负责读取文件系统上的文件,生成该 rdd 的 partition。这就解释了为什么要为调用了 checkpoint的RDD 添加一个 parent CheckpointRDD的原因。到此,整个checkpoint的流程就结束了。 Checkpoint 读取数据时序图

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册