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

来源:互联网 发布:网络综艺节目受众人群 编辑:程序博客网 时间:2024/05/16 17:12

本篇文章将延续前篇文章的内容考察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 通信数据流以下图的形式进行展示:
这里写图片描述