Código fuente gráfico de Spark (1) --- Arquitectura de comunicación de Spark

Lo que la tortuguita está aprendiendo hoy es el marco de comunicación de Spark. Debido a que Spark se distribuye después de todo, se requiere comunicación entre módulos, por lo que se debe usar un marco de comunicación.

Descripción general de la arquitectura de comunicación de Spark

Antes de Spark 1.6, Akka se usaba como componente de comunicación interna. Después de Spark 1.6, Akka fue reemplazado por Netty. Pero toma prestado un diseño de Akka, el modelo Actor.
Spark es un sistema informático distribuido, por lo que hay mucha comunicación entre los nodos, entonces Spark usará estos marcos de comunicación para la comunicación RPC.
Hay comunicación entre muchos nodos Spark, por ejemplo:

  • Comunicación entre el controlador y el maestro, por ejemplo, el controlador solicita recursos informáticos de vuelta al maestro
  • Comunicación entre masetr y el trabajador, por ejemplo, el trabajador informará la información del Ejecutor que se ejecuta en el trabajador al maestro
  • El ejecutor se comunica con el controlador y el ejecutor informa los resultados de la ejecución de la tarea al controlador.
  • Para la comunicación entre trabajadores, las tareas deben obtener datos entre sí

Introducción a Akka

Modelo actor

Akka se basa en el modelo Actor, y el modelo Actor se muestra en la siguiente figura.

inserte la descripción de la imagen aquí

caso sencillo

Simule ResourceManager y NodeManager de yarn para comunicarse entre sí.

  • MyResourceManager.scala
class MyResourceManager(var hostname: String, var port: Int) extends Actor {
    
    

  // 用来存储每个注册的NodeManager节点的信息
  private var id2nodemanagerinfo = new mutable.HashMap[String, NodeManagerInfo]()
  // 对所有注册的NodeManager进行去重,其实就是一个HashSet
  private var nodemanagerInfoes = new mutable.HashSet[NodeManagerInfo]()

  // actor在最开始的时候,会执行一次
  override def preStart(): Unit = {
    
    
      import scala.concurrent.duration._
      import context.dispatcher

      // 调度一个任务, 每隔五秒钟执行一次
      context.system.scheduler.schedule(0 millis, 5000 millis, self, CheckTimeOut)
    }

  override def receive: Receive = {
    
    

      case RegisterNodeManager(nodemanagerid, memory, cpu) => {
    
    
          val nodeManagerInfo = new NodeManagerInfo(nodemanagerid, memory, cpu)
          println(s"节点 ${
      
      nodemanagerid} 上线")

          // 对注册的NodeManager节点进行存储管理
          id2nodemanagerinfo.put(nodemanagerid, nodeManagerInfo)
          nodemanagerInfoes += nodeManagerInfo

          //把信息存到zookeeper
          sender() ! RegisteredNodeManager(hostname + ":" + port)
        }

      case Heartbeat(nodemanagerid) => {
    
    
          val currentTime = System.currentTimeMillis()
          val nodeManagerInfo = id2nodemanagerinfo(nodemanagerid)
          nodeManagerInfo.lastHeartBeatTime = currentTime

          id2nodemanagerinfo(nodemanagerid) = nodeManagerInfo
          nodemanagerInfoes += nodeManagerInfo
        }

      // 检查过期失效的 NodeManager
      case CheckTimeOut => {
    
    
          val currentTime = System.currentTimeMillis()

          // 15 秒钟失效
          nodemanagerInfoes.filter(nm => {
    
    
              val bool = currentTime - nm.lastHeartBeatTime > 15000
              if (bool) {
    
    
                  println(s"节点 ${
      
      nm.nodemanagerid} 下线")
                }
              bool
            }).foreach(deadnm => {
    
    
              nodemanagerInfoes -= deadnm
              id2nodemanagerinfo.remove(deadnm.nodemanagerid)
            })
          println("当前注册成功的节点数" + nodemanagerInfoes.size + "\t分别是:" + nodemanagerInfoes.map(x => x.toString)
                    .mkString(","));
        }
    }
}

object MyResourceManager {
    
    
  def main(args: Array[String]): Unit = {
    
    
      //    val RESOURCEMANAGER_HOSTNAME="localhost" //解析的配置的日志
      //    val RESOURCEMANAGER_PORT=6789
      val str =
      s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = localhost
|akka.remote.netty.tcp.port = 6789
""".stripMargin

      val conf = ConfigFactory.parseString(str)

      // TODO_MA 注释:ActorSystem
      val actorSystem = ActorSystem(Constant.RMAS, conf)

      // TODO_MA 注释:启动了一个actor : MyResourceManager
      actorSystem.actorOf(Props(new MyResourceManager("localhost", 6789)), Constant.RMA)
    }
}
  • MyNodeManager
class MyNodeManager(val nmhostname: String, val resourcemanagerhostname: String, val resourcemanagerport: Int,
                    val memory: Int, val cpu: Int) extends Actor {
    
    
    
    var nodemanagerid: String = nmhostname
    var rmRef: ActorSelection = _
    
    override def preStart(): Unit = {
    
    
        // 远程path                    akka.tcp://(ActorSystem的名称)@(远程地址的IP)   :         (远程地址的端口)/user/(Actor的名称)
        rmRef = context.actorSelection(s"akka.tcp://${
      
      Constant
          .RMAS}@${
      
      resourcemanagerhostname}:${
      
      resourcemanagerport}/user/${
      
      Constant.RMA}")
        
        // val nodemanagerid:String
        // val memory:Int
        // val cpu:Int
        //    nodemanagerid = UUID.randomUUID().toString
        //    nodemanagerid = "hadoop05"
        
        println(nodemanagerid + " 正在注册")
        rmRef ! RegisterNodeManager(nodemanagerid, memory, cpu)
    }
    
    override def receive: Receive = {
    
    
        case RegisteredNodeManager(masterURL) => {
    
    
            println(masterURL);
            
            /**
             * initialDelay: FiniteDuration, 多久以后开始执行
             * interval:     FiniteDuration, 每隔多长时间执行一次
             * receiver:     ActorRef, 给谁发送这个消息
             * message:      Any  发送的消息是啥
             */
            import scala.concurrent.duration._
            import context.dispatcher
            context.system.scheduler.schedule(0 millis, 4000 millis, self, SendMessage)
        }
        
        case SendMessage => {
    
    
            
            //向主节点发送心跳信息
            rmRef ! Heartbeat(nodemanagerid)
            
            println(Thread.currentThread().getId)
        }
    }
}

object MyNodeManager {
    
    
    def main(args: Array[String]): Unit = {
    
    
        val HOSTNAME = args(0)
        val RM_HOSTNAME = args(1)
        val RM_PORT = args(2).toInt
        val NODEMANAGER_MEMORY = args(3).toInt
        val NODEMANAGER_CORE = args(4).toInt
        var NODEMANAGER_PORT = args(5).toInt
        var NMHOSTNAME = args(6)
        val str =
            s"""
               |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
               |akka.remote.netty.tcp.hostname =${
      
      HOSTNAME}
               |akka.remote.netty.tcp.port=${
      
      NODEMANAGER_PORT}
      """.stripMargin
        val conf = ConfigFactory.parseString(str)
        val actorSystem = ActorSystem(Constant.NMAS, conf)
        actorSystem.actorOf(Props(new MyNodeManager(NMHOSTNAME, RM_HOSTNAME, RM_PORT, NODEMANAGER_MEMORY, NODEMANAGER_CORE)), Constant.NMA)
    }
}

Análisis de la arquitectura de comunicación de Spark

relación entre componentes

Cada componente (Cliente/Maestro/Trabajador) en la arquitectura de comunicación de Spark se puede considerar de forma independiente como una entidad independiente, y cada entidad se comunica a través de mensajes. La relación entre los componentes específicos es la siguiente:
inserte la descripción de la imagen aquí
EndPoint tiene 1 InBox y N OutBoxes (N>=1, N depende de con cuántos otros EndPoints se comunica el EndPoint actual, y otro EndPoint que se comunica con él corresponde a un OutBox), y el EndPoint recibe Los mensajes entrantes se escriben en InBox, y los mensajes salientes se escriben en OutBox y se envían a InBoxes de otros EndPoints.

Arquitectura de comunicación

La arquitectura de comunicación de Spark se muestra en la siguiente figura:

inserte la descripción de la imagen aquí

  1. RpcEndPoint
Punto final de RPC, Spark se denomina punto final de Rpc para cada nodo (cliente/maestro/trabajador) y todos implementan la interfaz RpcEndPoint. Internamente, se diseñan diferentes mensajes y diferentes procesos comerciales de acuerdo con las necesidades de los diferentes puntos finales. Si necesita enviar (consulta) Luego llame al Despachador.

RpcEndPoint es similar a Actor en Akka. El proceso por el que pasa un RpcEndpoint es: Construir -> onStart -> recibir -> onStop. Entre ellos, onStart se llama antes de recibir el mensaje de la tarea, reveive y receiveAndReply se utilizan para recibir el mensaje de otro RpcEndpoint (o de sí mismo) enviar y preguntar respectivamente, y la respuesta se devuelve a través de RpcContext.
  1. RpcEndpointRef
Similar a ActorRef en Akka, es una referencia a RpcEndPoint, contiene el nombre de la dirección del RpcEndPoint remoto, etc., y proporciona un método de envío y un método de solicitud para enviar solicitudes.

RpcEndpointRef es una referencia al RpcEndpoint remoto. Cuando necesitamos enviar un mensaje a un RpcEndPoint específico, generalmente necesitamos obtener la referencia de este RpcEndpoint y luego enviar un mensaje a través de esta referencia.
  1. RpcEnv y NettyRpcEnv
Contexto RPC, RpcEnv es similar a ActorSystem, tanto el servidor como el cliente pueden usarlo para la comunicación.

Por el lado del servidor, RpcEnv es el entorno operativo de RpcEndpoint, responsable de la gestión del ciclo de vida de RpcEndPoint, analizando los paquetes de datos de la capa Tcp y deserializando los datos en RpcMessage, y luego enviándolos al Endpoint correspondiente según la ruta; para el lado del cliente

, puede obtener la referencia de RpcEnvpoint a través de RpcEnv, es decir, RpcEndpointRef, y luego comunicarse con el Endpoint correspondiente a través de RpcEndpointRef.

RpcEnv proporciona un entorno para que RpcEndpoint procese los mensajes.RpcEnv es responsable de la gestión de todo el ciclo de vida de RpcEndpoint, incluido: el registro de puntos finales. Enrutamiento de mensajes entre puntos finales y puntos finales de parada.
  1. Despachador, InBox y OutBox
NettyRpcEnv contiene Dispatcher, que está dirigido principalmente al servidor, ayudando al enrutador al RpcEndPoint especificado y llamando a la lógica comercial.

Dispathcer: distribuidor de mensajes, para los puntos de interrupción de RPC es necesario enviar mensajes o recibir mensajes de RPC remoto y distribuirlos a la bandeja de entrada/salida de comandos correspondiente. Si el teléfono móvil del receptor de la instrucción es él mismo, se almacenará en la bandeja de entrada, si el receptor de la instrucción no es él mismo, se colocará en la bandeja de salida; Bandeja de entrada: bandeja de entrada del mensaje de instrucción, un RpcEndPoint local corresponde a una bandeja de entrada

y Dispatcher envía Bandeja de entrada cada vez Al almacenar mensajes, los EndpointData correspondientes se agregarán a la ReceiverQueue interna; además, cuando se crea el Dispatcher, se iniciará un subproceso separado para sondear la Cola del receptor para el consumo de mensajes de la bandeja de entrada.

OutBox: Cuadro de envío de mensajes de instrucciones. Para el RpcEndpoint actual, un RpcEndPoint de destino corresponde a un Outbox. Si varios RpcEndpoints de destino envían mensajes, hay varios OutBoxes. Una vez que el mensaje se coloca en la Bandeja de salida, se envía a través de TransportClient. El mensaje se coloca en la bandeja de salida y se envía en el mismo hilo. La razón principal de esto es que el mensaje remoto se divide en RpcOutboxMessage y OneWayOutBoxMessage, y el mensaje que debe responderse se envía directamente y el resultado debe procesarse.
  1. RpcAddress、TransportClient、TransportServer
RpcAddress: Indica la dirección del RpcEndPointRef remoto, Host + Puerto.

TransportClient: cliente de comunicación Netty, un OutBox corresponde a un TransportClient, y TransportClient sondea el OutBox continuamente y solicita el TransportServer remoto correspondiente de acuerdo con la información del receptor del mensaje OutBox; TransportServer: servidor de comunicación Netty, un RpcEndpoint corresponde a un TransportServer,

que se llama después de recibir el mensaje remoto Dispatcher distribuye los mensajes a las bandejas de entrada correspondientes.

De acuerdo con el análisis anterior, la vista de alto nivel de la arquitectura de comunicación de Spark se muestra en la siguiente figura:

inserte la descripción de la imagen aquí

Relación del diagrama de clase Rpc de Spark

Marco de comunicación RPC de Spark-1.png
Los puntos centrales son los siguientes:

  1. El núcleo RpcEnv es un rasgo (trait), que proporciona principalmente definiciones de métodos como detener, registrar y obtener puntos finales, y NettyRpcEnv proporciona una implementación específica de este rasgo.
  2. Un RpcEnv se produce a través de la fábrica RpcEnvFactory y NettyRpcEnvFactory se usa para generar un objeto de NettyRpcEnv.
  3. Cuando llamamos a SetupEndpoint en RpcEnv para registrar un punto final en rpcEnv, dentro de NettyRpcEnv, la relación de mapeo entre el nombre del punto final y él mismo, la relación de mapeo entre rpcEndpoint y rpcEndpointRef se almacena en la variable miembro correspondiente del despachador.

Supongo que te gusta

Origin blog.csdn.net/qq_21744859/article/details/127480877
Recomendado
Clasificación