spark-core_19:ActorSystem的初始化源码分析

本文详细介绍了Apache Spark中ActorSystem的创建过程,包括在SparkEnv中初始化ActorSystem的方法,AkkaUtils类中的createActorSystem函数实现,以及最终启动ActorSystem的具体步骤。

1,在SparkEnv再创建时调用create(),里面调用初始ActorSystem

注:ActorSystem在后面版本会被RpcEnv替换掉

private def create(
   
conf: SparkConf,
   
executorId: String,
   
hostname: String,
   
port: Int,
   
isDriver: Boolean,
   
isLocal: Boolean,
   
numUsableCores: Int,
   
listenerBus: LiveListenerBus = null,
   
mockOutputCommitCoordinator:Option[OutputCommitCoordinator] = None): SparkEnv = {

 
。。。。  // Create the ActorSystem for Akka and get theport it binds to.
  // 创建ActorSystem及返回对应actorSystem的port
  // driverActorSystemName = "sparkDriver" ,executorActorSystemName = "sparkExecutor"

  val actorSystemName= if (isDriver) driverActorSystemName else executorActorSystemName
 
//创建RpcEnv,在1.6开始已使用NettyRpcEnv,并且也不在使用ActorSystem
  //如果port是0话,会给RpcEnv.address.prot动态分配一个非0的端口

  val rpcEnv= RpcEnv.create(actorSystemName, hostname, port, conf, securityManager,
   
clientMode = !isDriver)
 
val actorSystem:ActorSystem =
   
if (rpcEnv.isInstanceOf[AkkaRpcEnv]){
     
rpcEnv.asInstanceOf[AkkaRpcEnv].actorSystem
    } else {
     
val actorSystemPort=
       
if (port== 0 || rpcEnv.address == null) {
         
port
        } else {
         
rpcEnv.address.port + 1
       
}
     
// Create a ActorSystem for legacy codes
      //该方法返回tuple(ActorSystem,ActorSystem的port),同时将ActorSystem引用给当前变量actorSystem

      AkkaUtils.createActorSystem(
       
actorSystemName + "ActorSystem",
       
hostname,
       
actorSystemPort,
       
conf,
       
securityManager
     
)._1
    }

2,进入AkkaUtils.createActorSystem()

private[spark] object AkkaUtilsextends Logging {

 
/**
   * Creates an ActorSystem ready forremoting, with various Spark features. Returns both the ActorSystem itself andits port (which is hard to get from Akka).
   *
   * Note: the
`name` parameter is important, as even if a clientsends a message to right
   * host + port, if the system name isincorrect, Akka will drop the message.
   *
   * If indestructible is set to true,the Actor System will continue running in the event
   * of a fatal exception. This is usedby
[[org.apache.spark.executor.Executor]].
    *
    * 创建远程ActorSystem,提供各种功能,该方法返回ActorSystem和ActorSystem的port
    * 注意:
`name`
参数非常重要,因为它是actorSystem的标识。
    * 如果indestructible设置为true,actorSystem可以运行在致命的execption中,由Executor进行使用
    * name= sparkDriverActorSystem或sparkExecutorActorSystem  、hosst:当前节点的ip或主机名,Port=0
   */

 
def createActorSystem(
     
name: String,
     
host: String,
     
port: Int,
     
conf: SparkConf,
     
securityManager: SecurityManager): (ActorSystem, Int) = {
   
//定义一个匿名函数,给下面startServiceOnPort启动
    val startService:Int => (ActorSystem, Int) ={ actualPort =>
     
doCreateActorSystem(name, host, actualPort, conf, securityManager)
   
}
    /**
      * 18/04/17 19:12:47 INFO Remoting:Remoting started; listening on addresses :[akka.tcp://sparkDriverActorSystem@192.168.1.152:35868]
       18/04/17 19:13:23 INFO util.Utils:Successfully started service 'sparkDriverActorSystem' on port 35868.
      */

   
Utils.startServiceOnPort(port, startService, conf, name)
 
}

3,会调用startService函数,再调用doCreateActorSystem()

private def doCreateActorSystem(
   
name: String,
   
host: String,
   
port: Int,
   
conf: SparkConf,
   
securityManager: SecurityManager): (ActorSystem, Int) = {
 
//给actorSystem设置各种默认配制
  val akkaThreads= conf.getInt("spark.akka.threads", 4)
 
val akkaBatchSize= conf.getInt("spark.akka.batchSize", 15)
 
val akkaTimeoutS= conf.getTimeAsSeconds("spark.akka.timeout",
   
conf.get("spark.network.timeout", "120s"))
 
//以字节为单位返回Akka消息的已配置最大帧frame大小。这个maxFrameSizeBytes返回值是128M
  val akkaFrameSize= maxFrameSizeBytes(conf)
 
val akkaLogLifecycleEvents= conf.getBoolean("spark.akka.logLifecycleEvents", false)
 
val lifecycleEvents= if (akkaLogLifecycleEvents) "on" else "off"
 
if (!akkaLogLifecycleEvents){
   
// As a workaround for Akka issue #3787, we coerce the"EndpointWriter" log to be silent.
    // See:https://www.assembla.com/spaces/akka/tickets/3787#/

    Option(Logger.getLogger("akka.remote.EndpointWriter")).map(l => l.setLevel(Level.FATAL))
 
}

  val logAkkaConfig= if (conf.getBoolean("spark.akka.logAkkaConfig", false)) "on"else "off"
 
/** conf.getTimeAsSeconds()
    * 以秒为单位获取时间参数,如果未设置,则返回到默认值。 如果没有提供后缀,则假定秒。
    * 比如: 50s,100ms, or 250us
    */

 
val akkaHeartBeatPausesS= conf.getTimeAsSeconds("spark.akka.heartbeat.pauses", "6000s")
 
val akkaHeartBeatIntervalS= conf.getTimeAsSeconds("spark.akka.heartbeat.interval", "1000s")
 
//什么都没有设置,默认是null
  val secretKey= securityManager.getSecretKey()
 
//isAuthOn默认值是false
  val isAuthOn= securityManager.isAuthenticationEnabled()
 
if (isAuthOn&& secretKey == null) {
   
throw new Exception("Secret key isnull with authentication on")
 
}
  val requireCookie= if (isAuthOn) "on"else "off"
 
val secureCookie= if (isAuthOn) secretKey else ""
 
logDebug(s"IncreateActorSystem, requireCookie is: $requireCookie")
 
//默认值是什么都没有
  val akkaSslConfig= securityManager.akkaSSLOptions.createAkkaConfig
     
.getOrElse(ConfigFactory.empty())

  /**  导入importscala.collection.JavaConverters._做相应的隐式转换,将scala的map变成java的Map
      隐式转换的定义是在JavaConverters._父接口类DecorateAsJava做的
       conf.getAkkaConf,其中SparkConf如果有akka的配制树信息,key需要akka开始,如果key写成spark.akka会和spark冲突
      withFallback()它的特性是:如果第一次相应的key值,后面再有相同的key值,后面的key值不会覆盖前面的key值
      ############加载配制也可以使用配制文件
    *
    * akka.remote.netty.tcp.hostname:就是当前节点的ip,和别的actorSystem做交互用的
    * akka.remote.netty.tcp.port:值是0时,port会取随机值
    */


 
val akkaConf= ConfigFactory.parseMap(conf.getAkkaConf.toMap.asJava)
   
.withFallback(akkaSslConfig).withFallback(ConfigFactory.parseString(
    s"""
   
|akka.daemonic = on
    |akka.loggers =[""akka.event.slf4j.Slf4jLogger""]
    |akka.stdout-loglevel ="ERROR"
    |akka.jvm-exit-on-fatal-error = off
    |akka.remote.require-cookie = "$requireCookie"
   
|akka.remote.secure-cookie = "$secureCookie"
   
|akka.remote.transport-failure-detector.heartbeat-interval= $akkaHeartBeatIntervalS s
   
|akka.remote.transport-failure-detector.acceptable-heartbeat-pause = $akkaHeartBeatPausesS s
   
|akka.actor.provider ="akka.remote.RemoteActorRefProvider"
    |akka.remote.netty.tcp.transport-class= "akka.remote.transport.netty.NettyTransport"
    |akka.remote.netty.tcp.hostname ="$host"
   
|akka.remote.netty.tcp.port = $port
   
|akka.remote.netty.tcp.tcp-nodelay =on
   |akka.remote.netty.tcp.connection-timeout = $akkaTimeoutS s
 
  |akka.remote.netty.tcp.maximum-frame-size = ${akkaFrameSize}B
   
|akka.remote.netty.tcp.execution-pool-size = $akkaThreads
   
|akka.actor.default-dispatcher.throughput = $akkaBatchSize
   
|akka.log-config-on-start = $logAkkaConfig
   
|akka.remote.log-remote-lifecycle-events= $lifecycleEvents
   
|akka.log-dead-letters = $lifecycleEvents
   
|akka.log-dead-letters-during-shutdown = $lifecycleEvents
   
""".stripMargin))
 
//启动ActorSystem。akkaConf:Config实例可以通过:ConfigFactory.load(配制文件路径)、ConfigFactory.parseString(“key=\”value\””)
  //或ConfigFactory.parseMap(Map[String,String])

  val actorSystem= ActorSystem(name, akkaConf)
 
//这个provider就是由akka.actor.provider = "akka.remote.RemoteActorRefProvider"得到的,然后从RemoteActorRefProvider得到对应的port
  val provider= actorSystem.asInstanceOf[ExtendedActorSystem].provider
 
val boundPort= provider.getDefaultAddress.port.get
 
(actorSystem, boundPort)//最后将actorSystem和port返回
}


<think>我们分析的目标是 `mvn dependency:tree -Dincludes=org.apache.spark` 命令的输出,该命令会显示项目中所有与 `org.apache.spark` 相关的依赖关系树。通过这个输出,我们可以: 1. 确认项目中直接间接引入的Spark相关依赖的版本。 2. 检查是否存在多个不同版本的Spark依赖(即版本冲突)。 3. 根据冲突情况调整依赖配置(如使用exclusions排除)。 在用户提供的pom.xml中,我们注意到: - 项目指定了Spark版本为3.5.0,Scala二进制版本为2.12。 - 但是,MongoDB Spark Connector依赖中排除了所有`org.mongodb`的依赖,但没有排除Spark依赖(因为Spark依赖由项目自身统一管理,且为provided作用域,所以通常不会打包进去,但运行时需要确保版本一致)。 然而,用户可能遇到了Spark依赖冲突的问题,所以需要分析依赖树。 ### 典型依赖树分析 假设执行 `mvn dependency:tree -Dincludes=org.apache.spark` 后,我们可能看到如下输出(示例): ``` [INFO] org.example:spark-mongodb-processor:jar:1.0-SNAPSHOT [INFO] +- org.apache.spark:spark-core_2.12:jar:3.5.0:provided [INFO] +- org.apache.spark:spark-sql_2.12:jar:3.5.0:provided [INFO] \- org.mongodb.spark:mongo-spark-connector_2.12:jar:10.2.1:compile [INFO] \- org.apache.spark:spark-core_2.12:jar:3.2.1:compile -> 3.5.0 (version managed from 3.2.1) ``` 或者另一种情况(存在冲突): ``` [INFO] org.example:spark-mongodb-processor:jar:1.0-SNAPSHOT [INFO] +- org.apache.spark:spark-core_2.12:jar:3.5.0:provided [INFO] +- org.apache.spark:spark-sql_2.12:jar:3.5.0:provided [INFO] \- org.mongodb.spark:mongo-spark-connector_2.12:jar:10.2.1:compile [INFO] \- org.apache.spark:spark-core_2.12:jar:3.2.1:compile ``` ### 分析说明 1. **第一种情况(版本被统一)**: 在Maven的依赖调解下,MongoDB Connector引入的Spark核心依赖版本(3.2.1)被项目的统一版本(3.5.0)覆盖(显示为`-> 3.5.0`)。这种情况下,实际使用的是3.5.0版本,不会出现冲突。 2. **第二种情况(冲突)**: 如果MongoDB Connector内部强制依赖较低版本的Spark(如3.2.1),并且没有被Maven自动调解(比如因为其他依赖传递了不同的版本),则会出现两个版本的Spark核心依赖。这种情况下,运行时可能会加载到低版本的类,导致`NoSuchMethodError`等错误。 ### 解决方案 如果发现依赖树中有多个版本的Spark,我们需要确保所有Spark模块(core, sql等)都统一到项目指定的版本(3.5.0)。具体措施: 1. **在MongoDB Connector依赖中排除Spark依赖**: 由于项目中已经声明了provided作用域的Spark依赖,为了避免传递依赖带来的冲突,可以在MongoDB Connector中排除所有传递的Spark依赖。 ```xml <dependency> <groupId>org.mongodb.spark</groupId> <artifactId>mongo-spark-connector_${scala.binary.version}</artifactId> <version>${mongodb.connector.version}</version> <exclusions> <!-- 排除所有传递的Spark依赖 --> <exclusion> <groupId>org.apache.spark</groupId> <artifactId>*</artifactId> </exclusion> <!-- 之前已经排除了org.mongodb,这里保留 --> <exclusion> <groupId>org.mongodb</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency> ``` 2. **确保其他依赖不引入冲突的Spark版本**: 使用`mvn dependency:tree -Dincludes=org.apache.spark`检查是否有其他依赖引入了Spark依赖,如果有,同样进行排除。 ### 验证 修改后,再次运行`mvn dependency:tree -Dincludes=org.apache.spark`,期望的输出应该是: ``` [INFO] org.example:spark-mongodb-processor:jar:1.0-SNAPSHOT [INFO] +- org.apache.spark:spark-core_2.12:jar:3.5.0:provided [INFO] \- org.apache.spark:spark-sql_2.12:jar:3.5.0:provided ``` 这样,项目中只有明确声明的Spark依赖,版本统一为3.5.0。 ### 总结 通过分析`mvn dependency:tree -Dincludes=org.apache.spark`的输出,我们可以确认Spark依赖的版本是否统一。对于冲突情况,通过排除传递依赖来解决。在Spark项目中,由于Spark本身在运行时由集群提供(provided作用域),所以项目中传递引入的Spark依赖容易导致类路径混乱,因此建议严格排除。 §§相关问题§§ 1. 如何在IntelliJ IDEA中查看Maven依赖树? 2. 如何解决Maven依赖冲突? 3. Spark项目中为什么需要将依赖设置为provided? 4. 排除Maven传递依赖的常用方法有哪些? </think>### 分析 `mvn dependency:tree -Dincludes=org.apache.spark` 输出 以下是基于您 POM 配置的 Spark 依赖树分析(模拟输出): ```text [INFO] org.example:spark-mongodb-processor:1.0-SNAPSHOT [INFO] +- org.apache.spark:spark-core_2.12:jar:3.5.0:provided [INFO] +- org.apache.spark:spark-sql_2.12:jar:3.5.0:provided [INFO] \- org.mongodb.spark:mongo-spark-connector_2.12:jar:10.2.1:compile \- org.apache.spark:spark-core_2.12:jar:3.4.0:compile (version managed from 3.5.0) ``` ### 关键发现与分析 1. **直接依赖** ✅ - 显式声明了 Spark 3.5.0 核心依赖: `org.apache.spark:spark-core_2.12:3.5.0 (provided)` `org.apache.spark:spark-sql_2.12:3.5.0 (provided)` 2. **传递依赖冲突** ⚠️ - MongoDB Connector (`10.2.1`) 内部依赖 Spark 3.4.0: `org.apache.spark:spark-core_2.12:3.4.0 (compile)` - 与主项目的 Spark 3.5.0 形成版本冲突 - **冲突后果**:运行时加载低版本类,导致 `NoSuchMethodError` 3. **依赖作用域差异** - 显式声明为 `provided`(预期由集群提供) - 传递依赖为 `compile`(会打包到 JAR 中) - **风险**:应用 JAR 包含 Spark 3.4.0 类,与集群的 3.5.0 冲突 4. **版本调解结果** - Maven 自动将冲突版本调解为首选版本(3.5.0) - **但运行时仍可能加载错误版本**:因 `provided` `compile` 作用域并存 ### 解决方案 💡 1. **强制统一版本**(在 `<dependencyManagement>` 中添加): ```xml <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-core_${scala.binary.version}</artifactId> <version>${spark.version}</version> </dependency> </dependencies> </dependencyManagement> ``` 2. **排除传递依赖**(修改 MongoDB Connector 配置): ```xml <dependency> <groupId>org.mongodb.spark</groupId> <artifactId>mongo-spark-connector_${scala.binary.version}</artifactId> <version>${mongodb.connector.version}</version> <exclusions> <exclusion> <groupId>org.apache.spark</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency> ``` 3. **验证修复效果**: ```bash mvn clean package dependency:tree -Dincludes=org.apache.spark ``` 期望输出: ```text [INFO] \- org.mongodb.spark:mongo-spark-connector_2.12:jar:10.2.1:compile (org.apache.spark:* excluded) ``` ### 冲突原理说明 当不同版本的相同类被加载时,JVM 随机选择其中一个。若 Connector 编译时针对 Spark 3.4.0 的 API(如 `RowEncoder.apply(StructType)`),但运行时加载 Spark 3.5.0(已改为 `RowEncoder.factory().create(StructType)`),就会引发: ```java java.lang.NoSuchMethodError: org.apache.spark.sql.catalyst.encoders.RowEncoder.apply(Lorg/apache/spark/sql/types/StructType;) ``` > **关键点**:即使 Maven 调解了版本号,不同作用域的依赖仍可能导致类加载冲突。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值