Hadoop RPC

来源:互联网 发布:算法设计克林伯格pdf 编辑:程序博客网 时间:2024/05/22 17:40

终于进入RPC模块了,网上有很多基于早期hadoopRPC机制分析,分析都很到位,今天我就带着大家走走源码(使用版本2.4.0)。本文采用深度遍历的方法分析源码,不知道你们习不习惯

前奏:

RPC是开发中抽出来的组件,如果不使用RPC,那么在分布式调用中流程是不是介个样子:服务器端是不是先创建ServerSocket在指定的ip地址和端口上监听,客户端创建到远程连接的Socket;好了,socket套接字完毕。然后我们可以从套接字拿到输入输出流InputStreamOutputStream吧。接着客户端就可以往输出流写序列化的参数,服务器端将读到的输入流中参数流反序列化成程序支持的类型,然后将处理的结果再序列化到输出流,发送给客户端。每次都这样写是不是很麻烦?

分析步骤:

从客户端发送请求开始,到服务器端返回,看当初google工程师是如何设计的

主要代码:

RPC的类都放在ipc包下面,ipc是进程间通信的意思。前面我们知道了动态代理,分析RPC先从InvocationHandler入手。在程序中我们发现了RpcInvocationHandler继承了InvocationHandler,而RpcInvocationHandler接口有多个实现类,其中两个重要实现类是ProtobufRPCEngine和WritableRPCEngine,很明显这跟两种序列化框架有关,Writable是hadoop当初自己实现的序列化,Protobuf是谷歌的序列化(单独抽一篇文章讲解)。为了不走偏,咱们从WritableRPCEngine开始。在WritableRpcEngine类中,我们看到了静态Invoker类实现了InvocationHandler。

下面是Invoker类的invoke方法:

代码1

public Object invoke(Object proxy, Method method, Object[] args) // 终于找到你了 invoke      throws Throwable {      long startTime = 0;      if (LOG.isDebugEnabled()) { // 是否启用了debug        startTime = Time.now();      }      // ObjectWritable是Writable中最复杂的一类序列化      // 在invoke方法中,他调用了client的call方法,先看下参数第一个是使用的序列化框架类型      // 第二个参数是Invocation对象,叫VO(Value Object数据传输层)      // 第三个参数是remoteId,类型是ConnectionId 表示唯一确定一个连接(那么可复用)      ObjectWritable value = (ObjectWritable)        client.call(RPC.RpcKind.RPC_WRITABLE, new Invocation(method, args), remoteId);      if (LOG.isDebugEnabled()) {        long callTime = Time.now() - startTime;        LOG.debug("Call: " + method.getName() + " " + callTime);      }      return value.get();    }
Client也是在ipc包下,跟RPC类平级,进入它的call方法看看呗

进去之后它又调用了重载方法,进入它的重载方法

代码2

 public Writable call(RPC.RpcKind rpcKind, Writable rpcRequest,      ConnectionId remoteId, int serviceClass) throws IOException {    final Call call = createCall(rpcKind, rpcRequest); // 创建call对象,代表一次rpc调用,就不进去详细看了    Connection connection = getConnection(remoteId, call, serviceClass); // 获取连接
它是怎么获取连接的呢,进如getConnection方法看看吧

方法里边前面主要是判断连接池里有没有符合自己的连接,有的话就复用(是不是跟前面的ConnectId呼应了),没有就新建,然后放到队列里

connections.put(remoteId, connection);

然后你会发现在后面有行代码

connection.setupIOstreams(); // 进去看看吧,好像是建立IO

代码3

    private synchronized void setupIOstreams() {      if (socket != null || shouldCloseConnection.get()) { // 是的,你没看错,socket,多么激动人心啊        return;      }       try {        if (LOG.isDebugEnabled()) {          LOG.debug("Connecting to "+server);        }        short numRetries = 0;        final short MAX_RETRIES = 5;        Random rand = null;        while (true) {          setupConnection(); // 貌似这一行比较关键,进去吧          InputStream inStream = NetUtils.getInputStream(socket); // 看到了吧,获取输入流哦          OutputStream outStream = NetUtils.getOutputStream(socket); // 获取输出流          writeConnectionHeader(outStream);
下面就进入setupConnection瞧一瞧

代码4

    private synchronized void setupConnection() throws IOException {      short ioFailures = 0;      short timeoutFailures = 0;      while (true) {        try {          // 终于等到你,我亲爱的套接字啊          // 使用了工厂方法模式,          // createSocket()代码我拷其中一个实现类StandardSocketFactory里面的          // StandardSocketFactory//            public Socket createSocket(InetAddress addr, int port) throws IOException {////                Socket socket = createSocket();//                socket.connect(new InetSocketAddress(addr, port));//                return socket;//            }          this.socket = socketFactory.createSocket();          this.socket.setTcpNoDelay(tcpNoDelay);          this.socket.setKeepAlive(true);
上面是不是有了连接的建立与IO的建立

下面再看看客户端请求时发送的数据情况,再回到Client中的call方法

代码5

public Writable call(RPC.RpcKind rpcKind, Writable rpcRequest,      ConnectionId remoteId, int serviceClass) throws IOException {    final Call call = createCall(rpcKind, rpcRequest); // 创建call对象,代表一次rpc调用,就不进去详细看了    Connection connection = getConnection(remoteId, call, serviceClass); // 获取连接    try {      connection.sendRpcRequest(call);                 // 发送rpc请求。注意:hadoop1中是connection.sendParam(call);

呵呵,继续跟进sendRpcRequest方法,核心代码如下

 try {              synchronized (Connection.this.out) { // 二话不说,先上锁                if (shouldCloseConnection.get()) { // 判断是否关闭                  return;                }                                if (LOG.isDebugEnabled()) // ...                  LOG.debug(getName() + " sending #" + call.id);                         byte[] data = d.getData(); // d是DataOutputBuffer,缓冲输出流                int totalLength = d.getLength(); // 得到流的长度                out.writeInt(totalLength); // 先写入int型数值,即下面待发送数据长度                out.write(data, 0, totalLength);// 发送数据                out.flush(); // 冲厕所,哈哈              }            }
为了把client的讲完,接着我们把client接收服务器端的response讲一下,接收过程在run方法中

try {        while (waitForWork()) {//wait here for work - read or close connection          receiveRpcResponse();        }      }
receiveRpcResponse方法走起
try {        int totalLen = in.readInt(); // 读取数据长度的一个整型数值        RpcResponseHeaderProto header =             RpcResponseHeaderProto.parseDelimitedFrom(in); // 也许你迷茫了,谷歌的序列化也在这里出现了        checkResponse(header);        int headerLen = header.getSerializedSize();        headerLen += CodedOutputStream.computeRawVarint32Size(headerLen);        int callId = header.getCallId();        if (LOG.isDebugEnabled())          LOG.debug(getName() + " got value #" + callId);        Call call = calls.get(callId);        RpcStatusProto status = header.getStatus();        if (status == RpcStatusProto.SUCCESS) { // 判断是不是调用成功          Writable value = ReflectionUtils.newInstance(valueClass, conf); // 哈哈,前面java的反射我们分析过了,                                                                          // 是不是很熟悉的感觉                                                                          // 根据valueClass和配置信息动态创建实例          value.readFields(in);                 // 读取输入流并赋值          calls.remove(callId);                 // 成功了就要把调用从队列中移除          call.setRpcResponse(value);           // 把对象放到call中,到这里是不是发现就像本地调用,一直操作本地的call          
好了,server端留下篇再讲吧
















0 0
原创粉丝点击