Hadoop學習筆記(一)——RPC

iteye_4515發表於2011-12-01

Hadoop RPC學習筆記

首先RpcEngine。這個介面中了Server端的getServer和Call方法。Client端的getProxy和stopProxy的方法。

從Server端看起


RPC.Server getServer(Class<?> protocol, Object instance, String bindAddress,
                       int port, int numHandlers, int numReaders,
                       int queueSizePerHandler, boolean verbose,
                       Configuration conf, 
                       SecretManager<? extends TokenIdentifier> secretManager
                       ) throws IOException;


這個介面這個引數的函式可以從一個呼叫的例子來看

this.serviceRpcServer = RPC.getServer(NamenodeProtocols.class, this,
          dnSocketAddr.getHostName(), dnSocketAddr.getPort(), serviceHandlerCount,
          false, conf, namesystem.getDelegationTokenSecretManager());
      this.serviceRPCAddress = this.serviceRpcServer.getListenerAddress();
      nn.setRpcServiceServerAddress(conf, serviceRPCAddress);

這是Namenode初始化時的建立RPC Server例項的呼叫。實際呼叫的實現,是每個Protocol對應的RpcEngine的例項的實現。RpcEngine的例項怎麼來的呢?是根據配置的類名(如rpc.engine.NamenodeProtocols)反射得到的。預設RpcEngine是WritableRpcEngine(hadoop還提供了一種AvroRpcEngine,跳過沒看)。WritableRpcEngine的getServer沒有特別,一直追溯到頂層的Server,即ipc包中的抽象Server類。到這個類中,就可以窺視到所有Server的重要元素(靜態內部類),如Call,Listener,Responder,Connection,Handler。不表先,回到Server的建構函式中來。

前面無非是設定一些Server屬性,地址埠,各種佇列長度,read執行緒數等等(paramClass這個單獨說)。重點是這裡。

    listener = new Listener();
    this.port = listener.getAddress().getPort();    
    this.rpcMetrics = RpcMetrics.create(this);
    this.rpcDetailedMetrics = RpcDetailedMetrics.create(this.port);
    this.tcpNoDelay = conf.getBoolean("ipc.server.tcpnodelay", false);
Listener是做了實際事情的,bind埠,啟動讀執行緒等。其中有一些Java網路程式設計的知識點,比如ServerSocketChannel,Selector。

然後是一個Responder的例項。

    responder = new Responder();

一個Server例項就這樣初始化完成了。在各種Server端的Init中就會呼叫他start以啟動服務。start分別start responder,listener,handler。分別看。

Listener的Run迴圈select,對於每個select到的key,DoAccept。DoAccept主要邏輯程式碼如下:

Reader reader = getReader();//輪流喚醒reader執行緒

reader.startAdd();
SelectionKey readKey = reader.registerChannel(channel);//將reader的selector註冊到channel中
c = new Connection(readKey, channel, System.currentTimeMillis());
readKey.attach(c);
synchronized (connectionList) {
connectionList.add(numConnections, c);
numConnections++;
}

reader執行緒喚醒後,根據attach再seletkey上得connection提供的readAndProcess方法,讀取到資料後,根據server註冊的paramClass類,生成方法和引數物件,根據這些物件例項化一個Call例項,放入Call佇列中。

Call類不復雜,就是維護一個呼叫的屬性。引數,連線物件,結果資料和處理時長相關的資訊。

private int id;                               // the client's call id
private Writable param;                       // the parameter passed
private Connection connection;                // connection to client
private long timestamp;     // the time received when response is null
                                   // the time served when response is not null
private ByteBuffer response;                      // the response for this call


自然會有一個執行緒再來消費佇列中的Call,就是Handler,handler執行緒是在Server Start時啟動的。handler取出每個Call物件後,傳入Call物件中維護的方法名稱和引數,呼叫當前server例項實現的call方法,將呼叫invoke。(其中有很多java 反射的東西)。handler得到call返回的結果後,填入Call物件。然後就是呼叫註冊的Responder的doRespond方法了。

Responder在Server例項化時例項化,在Server Start時啟動,是一個daemon執行緒。暴露的外部方法。

void doRespond(Call call) throws IOException {
      synchronized (call.connection.responseQueue) {
        call.connection.responseQueue.addLast(call);
        if (call.connection.responseQueue.size() == 1) {
          processResponse(call.connection.responseQueue, true);
        }
      }
    }
這裡就又涉及到一個生產者消費者佇列,為了完成非同步的Response。當該call是首次產生response,會呼叫processResponse,在這個方法中,會取出佇列中的第一個call,將call的reponse寫入call的connection中維護的channel中,並且會判斷是否寫完,若沒有寫完,就會將這個call重新加入佇列,並會註冊關注可寫的selector給channel。在Response的主迴圈中,當有可寫selectedKeys時,呼叫doAsyncWrite方法繼續寫,而doAsyncWrite還是會呼叫processResponse方法的。如此迴圈往復,直至寫完。並且,主迴圈中會判斷時長已超過15分鐘的連結,主動closeconnection。


大致就是這樣。Server的幾個重要組成部分就是Listener,Handler,Responder,Connection和Call。主要涉及到的知識點有兩部分:Java NIO和反射。

相關文章