Hadoop源碼之RPC機制

研讀Hadoop源碼,不得不說其中的RPC機制。

實現RPC的幾個基本步驟:

1)客戶端需要有一個負責與遠程服務端對象通信的對象,稱爲A;

2)服務端需要有一個負責與遠程客戶端對象通信的對象,稱爲B;

3)A負責將客戶端請求的Java類型方法、參數,序列化成字節流,通過網絡傳遞給B;

4)B負責將通過網絡收到的請求字節流,反序列化成Java類型方法、參數,傳遞給真正的服務端對象,並調用方法;

5)B收到服務端對象返回的結果,再序列化傳遞給A;

6)A反序列化後,將結果返回給客戶端調用者。

 

如果採用RMI實現上述RPC過程的話,A即爲存根對象,B即爲骨架對象;實際上CORBA架構也離不開上述的過程。

 

下面從RPC調用過程的先後出場順序,闡述hadoop的RPC方案:

1、客戶端使用Java 反射,獲取遠程服務端對象的代理對象,可以視爲A,同時解決了類型安全的問題;

  public static Object getProxy(Class protocol, InetSocketAddress addr, Configuration conf) {
    return Proxy.newProxyInstance(protocol.getClassLoader(),
                                  new Class[] { protocol },
                                  new Invoker(addr, conf));
  }

通過new 一個Invoker,來實現invocationHandler,實現調用過程,具體調用又是通過New一個Client對象的call方法實現;

    public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
      ObjectWritable value = (ObjectWritable)
        CLIENT.call(new Invocation(method, args), address);
      return value.get();
    }


2、上述的call調用與後續的調用,都是通過實現Writable接口的對象與ObjectWritable對象,來實現序列化與反序列化的,其中還包括所有Java基本類型在內都實現了Writable接口;

public interface Writable {
   void write(DataOutput out) throws IOException;      //序列化接口

   void readFields(DataInput in) throws IOException;   //反序列化接口
}


3、CLIENT在向服務端發送請求時,進行了同步,解決了線程安全的問題,並通過新建一個connection線程將調用序列化輸出;

  public Writable call(Writable param, InetSocketAddress address)
    throws IOException {
    Connection connection = getConnection(address); //新啓一個connection線程
    Call call = new Call(param);      //封裝成call
    synchronized (call) {
      connection.sendParam(call);                 // connection線程發送請求
      //以下省略
      }

connection線程中,對call對象進行序列化輸出;

public void sendParam(Call call) throws IOException {
	calls.put(new Integer(call.id), call);
	synchronized (out) {
	  if (LOG.isLoggable(Level.FINE))
	    LOG.fine(getName() + " sending #" + call.id);
	  try {
	    writingCall = call;
	    out.writeInt(call.id);
	    call.param.write(out);  //參數序列化,並寫出
	    out.flush();
	  } finally {
	    writingCall = null;
	  }
}


4、服務端socket監聽線程監聽到請求時,會開啓一個connection線程,構造參數對象,進行反序列化,構造call對象,喚醒call隊列;

        while (running) {
          int id;
          try {
            id = in.readInt();                    // try to read an id
          } catch (SocketTimeoutException e) {
            continue;
          }
        
          if (LOG.isLoggable(Level.FINE))
            LOG.fine(getName() + " got #" + id);
        
          Writable param = makeParam();           // 構造參數對象
          param.readFields(in);                   // 反序列化 
        
          Call call = new Call(id, param, this);  // 構造call對象
        
          synchronized (callQueue) {
            callQueue.addLast(call);              // queue the call
            callQueue.notify();                   // 喚醒call隊列
          }


5、服務端Handler線程被喚醒後,調用服務端對象,獲取結果後,又序列化輸出;

         Writable value = null;
          try {
            value = call(call.param);             // 調用
          } catch (IOException e) {
            LOG.log(Level.INFO, getName() + " call error: " + e, e);
            error = getStackTrace(e);
          } catch (Exception e) {
            LOG.log(Level.INFO, getName() + " call error: " + e, e);
            error = getStackTrace(e);
          }
            
          DataOutputStream out = call.connection.out;
          synchronized (out) {
            out.writeInt(call.id);                // write call id
            out.writeBoolean(error!=null);        // write error flag
            if (error != null)
              value = new UTF8(error);
            value.write(out);                     // write value
            out.flush();
          }

調用過程如下:

    public Writable call(Writable param) throws IOException {

        Invocation call = (Invocation)param;
        if (verbose) log("Call: " + call);
        
        Method method =
          implementation.getMethod(call.getMethodName(),
                                   call.getParameterClasses());  
        Object value = method.invoke(instance, call.getParameters()); //真正調用服務端對象
        if (verbose) log("Return: "+value);

        return new ObjectWritable(method.getReturnType(), value);
      }


6、以DataNode客戶端爲例,DataXceiveServer線程監聽到服務端返回的數據後,就新開啓一個DataXceiver線程,DataXceiver將返回的數據進行反序列化後再進行相應處理;

                    if (op == OP_WRITE_BLOCK) {
                        //
                        // Read in the header
                        //
                        DataOutputStream reply = new DataOutputStream(new BufferedOutputStream(s.getOutputStream()));
                        try {
                            boolean shouldReportBlock = in.readBoolean();
                            Block b = new Block();
                            b.readFields(in);                 //反序列化


這樣就完成了一個完整的RPC調用過程。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章