考察Hadoop的底层rpc通信(二)

本篇文章将延续前篇文章的内容考察Hadoop的底层ipc通信(一),继续剖析Hadoop的底层ipc通信。通过debug的方式,对自己所写的ipc demo进行通信数据流的分析。
##Client端发送数据过程源码跟踪及分析
org.apache.hadoop.ipc.WritableRpcEngine类

// 调用了代理对象的invoke方法
   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
     throws Throwable {
     long startTime = 0;
     if (LOG.isDebugEnabled()) {
       startTime = Time.now();
     }
     TraceScope traceScope = null;
     if (Trace.isTracing()) {
       traceScope = Trace.startSpan(RpcClientUtil.methodToTraceString(method));
     }
  // 考察ObjectWritable
     ObjectWritable value;
     try {
       value = (ObjectWritable)
	  // 也调用了客户端的call方法  走了4个参数
	  // a.类别  b.Invocation  c.remoteId  d.认证
         client.call(RPC.RpcKind.RPC_WRITABLE, new Invocation(method, args),
           remoteId, fallbackToSimpleAuth);
     } finally {
       if (traceScope != null) traceScope.close();
     }
     if (LOG.isDebugEnabled()) {
       long callTime = Time.now() - startTime;
       LOG.debug("Call: " + method.getName() + " " + callTime);
     }
     return value.get();
   }
...
// 这是一个方法的调用(因为远程过程调用,实际上就是对方法的调用)包含了方法名称和他的参数
// 方法的实现在远程服务端,因此需要将方法名称和相关参数打包之后传给远程服务端
private static class Invocation implements Writable, Configurable {
	// 服务器通过以下信息,可知需要调用哪个类的哪个方法调用哪些参数
	// methodName 					:sayHello
	// parameterClasses				:[class java.lang.String]
	// parameters					:[world]
	// conf							:配置信息
	// clientVersion				:1
	// clientMethodsHash			:998346840
	// declaringClassProtocolName	:com.zhaotao.hadoop.ipc.HelloWorldService
       private String methodName;
       private Class<?>[] parameterClasses;
       private Object[] parameters;
       private Configuration conf;
       private long clientVersion;
       private int clientMethodsHash;
       private String declaringClassProtocolName;
	...
    // Object[] parameters  为所传进来的参数数组,在该调用示例中parameters的值为:world
	public Invocation(Method method, Object[] parameters) {
	// 方法名称  取得方法名称:sayHello
    this.methodName = method.getName();
    // 参数类型  取得参数类型集:[class java.lang.String]    parameterClasses为数组形式
    this.parameterClasses = method.getParameterTypes();
    // 把参数的赋值给局部变量   parameters的值为[world](parameters为一个数组)
    this.parameters = parameters;
    // rpc版本   writableRpcVersion为一个固定值:2
    rpcVersion = writableRpcVersion;
    // 得到method方法所声明的类
    // method的值为:public abstract java.lang.String com.zhaotao.hadoop.ipc.HelloWorldService.sayHello(java.lang.String)
    // 声明的类与VersionedProtocol进行比较  VersionedProtocol为HelloWorldService的超类
    if (method.getDeclaringClass().equals(VersionedProtocol.class)) {
 	  //VersionedProtocol is exempted from version check.
	  clientVersion = 0;
	  clientMethodsHash = 0;
    } else {
	  // getDeclaringClass()方法得到方法所在的类
	  // getProtocolVersion()方法并取得它对应的版本号
	  // clientVersion的值为1
	  this.clientVersion = RPC.getProtocolVersion(method.getDeclaringClass());
	  // 取得哈希值,clientMethodsHash的值为998346840
	  this.clientMethodsHash = ProtocolSignature.getFingerprint(method
		  .getDeclaringClass().getMethods());
    }
    // 获得所声明的类的协议名称
    // declaringClassProtocolName的值为com.zhaotao.hadoop.ipc.HelloWorldService
    this.declaringClassProtocolName = 
	    RPC.getProtocolName(method.getDeclaringClass());
  }
  ...
  // 该过程为一个串行化过程
     public void write(DataOutput out) throws IOException {
       out.writeLong(rpcVersion);
       UTF8.writeString(out, declaringClassProtocolName);
       UTF8.writeString(out, methodName);
       out.writeLong(clientVersion);
       out.writeInt(clientMethodsHash);
	// 写入参数类的长度
       out.writeInt(parameterClasses.length);
	// 循环写入参数类、参数值、配置信息
       for (int i = 0; i < parameterClasses.length; i++) {
         ObjectWritable.writeObject(out, parameters[i], parameterClasses[i],
                                    conf, true);
       }
     }
}
...
private static class Invoker implements RpcInvocationHandler {
	...
	// 实质上在构造的时候,走的是这个Invoker()方法
	public Invoker(Class<?> protocol,
				   InetSocketAddress address, UserGroupInformation ticket,
				   Configuration conf, SocketFactory factory,
				   int rpcTimeout, AtomicBoolean fallbackToSimpleAuth)
		throws IOException {
	  this.remoteId = Client.ConnectionId.getConnectionId(address, protocol,
		  ticket, rpcTimeout, conf);
	  this.client = CLIENTS.getClient(conf, factory);
	  this.fallbackToSimpleAuth = fallbackToSimpleAuth;
	}
	...
}

org.apache.hadoop.io.ObjectWritable类
这是多态 对象可写入 写入一个实例(是一个类名),处理数组、字符串、基本类型(不需要Writable wrapper的包装)

  // 声明的类
  private Class declaredClass;
  // 实例
  private Object instance;
  // 配置
  private Configuration conf;
  ...
  // 静态代码块,实现了java中的8种基本类型(一一对应)
  private static final Map<String, Class<?>> PRIMITIVE_NAMES = new HashMap<String, Class<?>>();
  static {
    PRIMITIVE_NAMES.put("boolean", Boolean.TYPE);
    PRIMITIVE_NAMES.put("byte", Byte.TYPE);
    PRIMITIVE_NAMES.put("char", Character.TYPE);
    PRIMITIVE_NAMES.put("short", Short.TYPE);
    PRIMITIVE_NAMES.put("int", Integer.TYPE);
    PRIMITIVE_NAMES.put("long", Long.TYPE);
    PRIMITIVE_NAMES.put("float", Float.TYPE);
    PRIMITIVE_NAMES.put("double", Double.TYPE);
    PRIMITIVE_NAMES.put("void", Void.TYPE);
  }
  ...

org.apache.hadoop.ipc.Client类

  // 进行参数的考察
  // rpcKind	:RPC_WRITABLE  使用了WRITABLE的RPC引擎
  // rpcRequest	:WritableRpcEngine$Invocation
  // 具体值		:sayHello(world), rpc version=2, client version=1, methodsFingerPrint=998346840(方法的指纹,类似于方法的版本)
  public Writable call(RPC.RpcKind rpcKind, Writable rpcRequest,
      ConnectionId remoteId, AtomicBoolean fallbackToSimpleAuth)
      throws IOException {
    return call(rpcKind, rpcRequest, remoteId, RPC.RPC_SERVICE_CLASS_DEFAULT,
      fallbackToSimpleAuth);
  }
  ...
  public Writable call(...){
	...
	// 用rpcKind和rpcRequest创建一个Call对象
	final Call call = createCall(rpcKind, rpcRequest);
	// 得到连接之后,会跳转到setupConnection()去
	Connection connection = getConnection(remoteId, call, serviceClass,
      fallbackToSimpleAuth);
	// 将Call对象发给远端
	connection.sendRpcRequest(call);  
	...
  }
  ...
  // Call对象里面包含了RpcKind和Writable类型的rpcRequest(实质上是在客户端封装完成之后发给服务端的消息)
  Call createCall(RPC.RpcKind rpcKind, Writable rpcRequest) {
    return new Call(rpcKind, rpcRequest);
  }
  ...
  // 在得到连接之后,进行安装连接
  private synchronized void setupConnection() throws IOException {
    short ioFailures = 0;
    short timeoutFailures = 0;
    while (true) {
 	  try {
	    // 使用套接字工厂去创建套接字
	    this.socket = socketFactory.createSocket();
	    this.socket.setTcpNoDelay(tcpNoDelay);
	    this.socket.setKeepAlive(true);
	  
	    /*
	     * Bind the socket to the host specified in the principal name of the
	     * client, to ensure Server matching address of the client connection
	     * to host name in principal passed.
	     */
	    UserGroupInformation ticket = remoteId.getTicket();
	    if (ticket != null && ticket.hasKerberosCredentials()) {
		  KerberosInfo krbInfo = 
		    remoteId.getProtocol().getAnnotation(KerberosInfo.class);
		  if (krbInfo != null && krbInfo.clientPrincipal() != null) {
		    String host = 
			  SecurityUtil.getHostFromPrincipal(remoteId.getTicket().getUserName());
		  
		    // If host name is a valid local address then bind socket to it
		    InetAddress localAddr = NetUtils.getLocalInetAddress(host);
		    if (localAddr != null) {
			  this.socket.bind(new InetSocketAddress(localAddr, 0));
		    }
		  }
	    }
	    
		// 在NetUtils工具类中,去连接到服务器
		// socket套接字  连接到server服务器  使用了connectionTimeout超时进行判断
	    NetUtils.connect(this.socket, server, connectionTimeout);
	    if (rpcTimeout > 0) {
		  pingInterval = rpcTimeout;  // rpcTimeout overwrites pingInterval
	    }
		// 设置一个套接字通信的timeout超时,继续运行进入到
	    this.socket.setSoTimeout(pingInterval);
	    return;
	  } catch (ConnectTimeoutException toe) {
	    /* Check for an address change and update the local reference.
	     * Reset the failure counter if the address was changed
	     */
	    if (updateAddress()) {
	  	timeoutFailures = ioFailures = 0;
	    }
	    handleConnectionTimeout(timeoutFailures++,
	  	  maxRetriesOnSocketTimeouts, toe);
	  } catch (IOException ie) {
	    if (updateAddress()) {
	    	timeoutFailures = ioFailures = 0;
	    }
	    handleConnectionFailure(ioFailures++, ie);
	  }
    }
  }
  ...
  private synchronized void setupIOstreams(...){
	...
	while (true) {
		...
		setupConnection();
		// 先从套接字中得到输入流
		// inStream的值为:org.apache.hadoop.net.SocketInputWrapper@660acfb
        InputStream inStream = NetUtils.getInputStream(socket);
		// 再从套接字中得到输出流
		// outStream的值为:org.apache.hadoop.net.SocketOutputStream@5d908d47
        OutputStream outStream = NetUtils.getOutputStream(socket);
		// 将连接头  写入到outStream流里去
        writeConnectionHeader(outStream);
		...
		// 向服务器发送一个测试信号
		if (doPing) {
          inStream = new PingInputStream(inStream);
        }
		// 将ping的流封装成数据的输入流
		this.in = new DataInputStream(new BufferedInputStream(inStream));
		...
	}
	...
  }
  ...
  /**
   * 当连接被建立的时候,发送给服务器
   * hrpc(4个字节) + 版本(1个字节) + 服务类(1个字节) + 认证协议(1个字节)   实质上为一个报文
   * Write the connection header - this is sent when connection is established
   * +----------------------------------+
   * |  "hrpc" 4 bytes                  |      
   * +----------------------------------+
   * |  Version (1 byte)                |
   * +----------------------------------+
   * |  Service Class (1 byte)          |
   * +----------------------------------+
   * |  AuthProtocol (1 byte)           |      
   * +----------------------------------+
   */
  private void writeConnectionHeader(OutputStream outStream)
       throws IOException {
	...
	// 将传进来的outStream输出流封装成了DataOutputStream类型的数据输出流
	DataOutputStream out = new DataOutputStream(new BufferedOutputStream(outStream));
    // Write out the header, version and authentication method
	// 依次将报文中的四个内容写入到流中去
    out.write(RpcConstants.HEADER.array());		//RpcConstants.HEADER.array()的值为[104, 114, 112, 99],其实就是写入hrpc四个字母
    out.write(RpcConstants.CURRENT_VERSION);	//版本号为9(为固定值)
    out.write(serviceClass);					//服务类的值为0
    out.write(authProtocol.callId);				//认证ID的值为0
	// 清理之后,将内容发给服务器
    out.flush();
	...
  }
  ...
  public void sendRpcRequest(final Call call)
        throws InterruptedException, IOException {
	...
	// 数据输出流缓冲区(将数据写入到这里,它在本地)
	final DataOutputBuffer d = new DataOutputBuffer();
	// 通过ProtoUtil制作类,去制作一个RpcRequestHeader(Rpc的请求头)
	// RpcRequestHeaderProto这个类继承了com.google.protobuf.GeneratedMessage(谷歌的)
	// 注意:在RpcRequestHeaderProto这个类中,将传入的消息、对象转换成谷歌protobuf在内部识别的一些消息格式
	// protobuf在网络间传输的消耗小
    RpcRequestHeaderProto header = ProtoUtil.makeRpcRequestHeader(
        call.rpcKind, OperationProto.RPC_FINAL_PACKET, call.id, call.retry,
        clientId);
    // header的值为:
	// rpcKind: RPC_WRITABLE
	// rpcOp: RPC_FINAL_PACKET(rpcOp为rpc的操作)
	// callId: 0
	// clientId: "\036]\207\256#GG\223\224\004\335\204\023\205\357\240"
	// etryCount: 0
	
	// 将header写入到输出流缓冲区
    header.writeDelimitedTo(d);
    call.rpcRequest.write(d);
	...
	// 使用线程池将消息发送到服务端
	synchronized (sendRpcRequestLock) {
		...
		@Override
        public void run() {
			...
			// d为DataOutputBuffer数据输出缓冲区;数据先写入到DataOutputBuffer中(DataOutputBuffer在本地)
			// 写入完成之后,将里面的内容写入到out中去,使用线程池技术,将其里面的内容一并发送到服务端
			// out的值为java.io.DataOutputStream@14008db3
			// out <--- BufferedOutputStream <--- SocketOutputStream
			byte[] data = d.getData();
            int totalLength = d.getLength();
            out.writeInt(totalLength); // Total Length
            out.write(data, 0, totalLength);// RpcRequestHeader + RpcRequest
			// 清除操作,发送完毕
            out.flush();
			...
		}
		...
	}
	...
  }

com.google.protobuf.AbstractMessageLite类

  public void writeDelimitedTo(final OutputStream output) throws IOException {
    // 得到串行化大小  serialized的值为26(即为26个字节)
    final int serialized = getSerializedSize();
	// 使用CodedOutputStream编码输出流  去计算computePreferredBufferSize首选的缓冲区大小
	// 计算当前消息的大小  bufferSize的值为27(即为27个字节)
    final int bufferSize = CodedOutputStream.computePreferredBufferSize(
        CodedOutputStream.computeRawVarint32Size(serialized) + serialized);
    // 创建一个编码输出流实例
	final CodedOutputStream codedOutput =
        CodedOutputStream.newInstance(output, bufferSize);
    // 将serialized写入打变长的32位整数里面去
    codedOutput.writeRawVarint32(serialized);
	// 将codedOutput对象写入到流里去
    writeTo(codedOutput);
	// 清除codedOutput流
    codedOutput.flush();
  }

##Server端接收数据过程源码跟踪及分析

org.apache.hadoop.ipc.Server类

  // 启动服务,必须在任何操作被调用之前进行操作
  /** Starts the service.  Must be called before any calls will be handled. */
  public synchronized void start() {
    // 启动一个响应对象
    responder.start();
	// listener开始监听
    listener.start();
	// 处理器  有多少个就启动多少个
    handlers = new Handler[handlerCount];
    
    for (int i = 0; i < handlerCount; i++) {
      handlers[i] = new Handler(i);
      handlers[i].start();
    }
  }
  ...
  // 监听负责客户端发来的请求,进行连接的动作
  // 监听套接字,为处理器线程创建作业
  /** Listens on the socket. Creates jobs for the handler threads*/ 
  private class Listener extends Thread {
	...
    @Override
    public void run() {
      LOG.info(Thread.currentThread().getName() + ": starting"); 
      SERVER.set(Server.this);
	  // 连接管理器
      connectionManager.startIdleScan();
	  // running的值为true,因此为一个死循环
      while (running) {
        SelectionKey key = null;
        try {
		  // 得到挑选器,得到它的挑选方法
		  // 在这里会阻塞,等待客户端发来请求才会继续执行
          getSelector().select();
		  // 从挑选的集合中,迭代所有的key值
          Iterator<SelectionKey> iter = getSelector().selectedKeys().iterator();
		  // 对每一个元素进行迭代
          while (iter.hasNext()) {
		    // 取得下一个元素
            key = iter.next();
			// 迭代完一个,就删除
            iter.remove();
            try {
			  // 判断key是否有效
              if (key.isValid()) {
			    // 判断key是否可接受
                if (key.isAcceptable())
				  // 开始接受key
				  // key的值为:sun.nio.ch.SelectionKeyImpl@305b7c14
                  doAccept(key);
              }
            }
          }
        }
      }
    }
	...
    void doAccept(SelectionKey key) throws InterruptedException, IOException,  OutOfMemoryError {
      ServerSocketChannel server = (ServerSocketChannel) key.channel();
      // 通道:channel  里用channel进行接收
	  SocketChannel channel;
      while ((channel = server.accept()) != null) {
		// 设置阻塞(false为非阻塞)
        channel.configureBlocking(false);
        channel.socket().setTcpNoDelay(tcpNoDelay);
        channel.socket().setKeepAlive(true);
        
        Reader reader = getReader();
		// 进行连接注册
        Connection c = connectionManager.register(channel);
        // If the connectionManager can't take it, close the connection.
        if (c == null) {
          if (channel.isOpen()) {
            IOUtils.cleanup(null, channel);
          }
          continue;
        }
		// 关联
        key.attach(c);  // so closeCurrentConnection can get the object
		// 添加到连接中去
        reader.addConnection(c);
      }
    }
	...
  }
  ...
  private class Handler extends Thread {
    public Handler(int instanceNumber) {
	  // 将其设置为守护线程
      this.setDaemon(true);
      this.setName("IPC Server handler "+ instanceNumber + " on " + port);
    }
	...
	public void run() {
      LOG.debug(Thread.currentThread().getName() + ": starting");
      SERVER.set(Server.this);
      ByteArrayOutputStream buf = 
        new ByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
	  while(running){
		...
		TraceScope traceScope = null;
		// 从队列中去提取一个调用对象
		final Call call = callQueue.take(); // pop the queue; maybe blocked here
		...
	  }
	}
	...
  }
  ...
  public class Connection {
	...
	private void processOneRpc(byte[] buf){
		...
		// dis的值为:java.io.DataInputStream@480eaf50
        final DataInputStream dis =
            new DataInputStream(new ByteArrayInputStream(buf));
		// header的值为:
		// rpcKind: RPC_PROTOCOL_BUFFER
		// rpcOp: RPC_FINAL_PACKET
		// callId: -3
		// clientId: "\220x\223:\030\317D\025\235a?\206\266+\330Z"
		// retryCount: -1
		// 服务端接收到流之后,首先进行解码decodeProtobufFromStream(解码流)
        final RpcRequestHeaderProto header =
		    // newBuilder()对象和dis流(实质为从客户端接收到的数据输入流)进行解码
			// 从而得到请求头协议RpcRequestHeaderProto header
            decodeProtobufFromStream(RpcRequestHeaderProto.newBuilder(), dis);		
		// 拿到呼叫ID,callId的值为0
		callId = header.getCallId();
		// 拿到重试次数,retry的值为0
        retry = header.getRetryCount();
		...
		// 检查Rpc的头协议
		checkRpcHeaders(header);
		...
		// 开始处理Rpc请求
		processRpcRequest(header, dis);
		...
	}
	...
	private void processRpcRequest(RpcRequestHeaderProto header,
        DataInputStream dis){
		...
		// 得到类别
		// rpcRequestClass的值为:class org.apache.hadoop.ipc.WritableRpcEngine$Invocation
		Class<? extends Writable> rpcRequestClass = 
          getRpcRequestWrapper(header.getRpcKind());
		...
		// 通过反射去创建Rpc请求对象
		// rpcRequest = WritableRpcEngine$Invocation
		// rpcRequest的值为:com.sun.jdi.InvocationException occurred invoking method.
		// rpcRequest就是一个Invocation
		rpcRequest = ReflectionUtils.newInstance(rpcRequestClass, conf);
		// 读取内容(这是一个反串行化过程;这和客户端向服务端发送数据时串行化写入到流里是一一对应的)
        rpcRequest.readFields(dis);
		...
	}
	...
  }

##ipc通信数据流图解
通过debug对源码跟踪之后,可以将Hadoop的底层 ipc 通信数据流以下图的形式进行展示:
这里写图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值