Hadoop RPC機制
轉載:http://www.iteye.com/topic/709993
1、心跳機制
心跳的機制大概是這樣的:
1) master啓動的時候,會開一個ipc server在那裏。
2) slave啓動時,會連接master,並每隔3秒鐘主動向master發送一個“心跳”,將自己的狀態信息告訴master,然後master也是通過這個心跳的返回值,向slave節點傳達指令。
2、找到心跳的代碼
拿namenode和datanode來說,在datanode的offerService方法中,每隔3秒向namenode發送心跳的代碼:
- /**
- * Main loop for the DataNode. Runs until shutdown,
- * forever calling remote NameNode functions.
- */
- public void offerService() throws Exception {
- ...
- //
- // Now loop for a long time....
- //
- while (shouldRun) {
- try {
- long startTime = now();
- //
- // Every so often, send heartbeat or block-report
- //
- // 如果到了3秒鐘,就向namenode發心跳
- if (startTime - lastHeartbeat > heartBeatInterval) {
- //
- // All heartbeat messages include following info:
- // -- Datanode name
- // -- data transfer port
- // -- Total capacity
- // -- Bytes remaining
- //
- lastHeartbeat = startTime;
- DatanodeCommand[] cmds = namenode.sendHeartbeat(dnRegistration,
- data.getCapacity(),
- data.getDfsUsed(),
- data.getRemaining(),
- xmitsInProgress.get(),
- getXceiverCount());
- // 注意上面這行代碼,“發送心跳”竟然就是調用namenode的一個方法??
- myMetrics.heartbeats.inc(now() - startTime);
- //LOG.info("Just sent heartbeat, with name " + localName);
- // 處理對心跳的返回值(namenode傳給datanode的指令)
- if (!processCommand(cmds))
- continue;
- }
- // 這裏省略很多代碼
- ...
- } // while (shouldRun)
- } // offerService
上面這段代碼,如果是單機的程序,沒什麼值得奇怪的。但是,這是hadoop集羣!datanode和namenode在2臺不同的機器(或2個JVM)上運行!datanode機器竟然直接調用namenode的方法!這是怎麼實現的?難道是傳說中的RMI嗎??
下面我們主要就來分析這個方法調用的細節。
3、心跳的底層細節一:datanode怎麼獲得namenode對象的?
首先,DataNode類中,有一個namenode的成員變量:
- public class DataNode extends Configured
- implements InterDatanodeProtocol, ClientDatanodeProtocol, FSConstants, Runnable {
- ...
- public DatanodeProtocol namenode = null;
- ...
- }
下面是NameNode類的定義:
- public class NameNode implements ClientProtocol, DatanodeProtocol,
- NamenodeProtocol, FSConstants,
- RefreshAuthorizationPolicyProtocol {
- ...
- }
注意:NameNode實現了DatanodeProtocol接口,DatanodeProtocol接口定義了namenode和datanode之間通信的方法。
那麼,DataNode類是怎麼獲取到NameNode類的引用呢?
在Datanode端,爲namenode變量賦值的代碼:
- // connect to name node
- this.namenode = (DatanodeProtocol)
- RPC.waitForProxy(DatanodeProtocol.class,
- DatanodeProtocol.versionID,
- nameNodeAddr,
- conf);
在繼續去RPC類中追蹤:
- VersionedProtocol proxy =
- (VersionedProtocol) Proxy.newProxyInstance(
- protocol.getClassLoader(), new Class[] { protocol },
- new Invoker(addr, ticket, conf, factory));
現在,明白了!
1) 對namenode的賦值,並不是真正的new了一個實現了DatanodeProtocol接口的對象,而是獲得了一個動態代理!!
2) 上面這段代碼中,protocol的類型是DatanodeProtocol.class
3) 對namenode的所有調用,都被委託(delegate)給了Invoker
4、心跳的底層細節二:看看Invoker類
Invoker類是org.apache.hadoop.ipc.RPC類的一個靜態內部類:
- private static class Invoker implements InvocationHandler {
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- ...
- ObjectWritable value = (ObjectWritable)
- client.call(new Invocation(method, args), address,
- method.getDeclaringClass(), ticket);
- ...
- return value.get();
- }
所有的方法調用又被delegate給client的call方法了!
client是Invoker中的成員變量:
- private Client client;
所以可以看出:DatanodeProtocol中的每個方法調用,都被包裝成一個Invocation對象,再由client.call()調用
5、心跳的底層細節三:Invocation類
Invocation類是org.apache.hadoop.ipc.RPC類的一個靜態內部類
沒有什麼業務邏輯方法,主要作用就是一個VO
6、心跳的底層細節四:client類的call方法
接下來重點看client類的call方法:
- public Writable call(Writable param, InetSocketAddress addr,
- Class<?> protocol, UserGroupInformation ticket)
- throws InterruptedException, IOException {
- Call call = new Call(param);
- // 將Invocation轉化爲Call
- Connection connection = getConnection(addr, protocol, ticket, call);
- // 連接遠程服務器
- connection.sendParam(call); // send the parameter
- // 將“序列化”後的call發給過去
- boolean interrupted = false;
- synchronized (call) {
- while (!call.done) {
- try {
- call.wait(); // wait for the result
- // 等待調用結果
- } catch (InterruptedException ie) {
- // save the fact that we were interrupted
- interrupted = true;
- }
- }
- if (interrupted) {
- // set the interrupt flag now that we are done waiting
- Thread.currentThread().interrupt();
- }
- if (call.error != null) {
- if (call.error instanceof RemoteException) {
- call.error.fillInStackTrace();
- throw call.error;
- } else { // local exception
- throw wrapException(addr, call.error);
- }
- } else {
- return call.value;
- // 返回
- }
- }
- }
7、現在,一目瞭然了
- datanode向namenode發送heartbeat過程是這樣的:
- a) 在datanode初始化獲得namenode的proxy
- b) 在datanode上,調用namenode proxy的heartbeat方法:
- namenode.sendHeartbeat(dnRegistration,
- data.getCapacity(),
- data.getDfsUsed(),
- data.getRemaining(),
- xmitsInProgress.get(),
- getXceiverCount());
- c) 在datanode上的namenode動態代理類將這個調用包裝成(或者叫“序列化成”)一個Invocation對象,並調用client.call方法
- d) client call方法將Invocation轉化爲Call對象
- e) client 將call發送到真正的namenode服務器
- f) namenode接收後,轉化成namenode端的Call,並process後,通過Responder發回來!
- g) datanode接收結果,並將結果轉化爲DatanodeCommand[]
8、再看動態代理
動態代理:讓“只有接口,沒事對應的實現類”成爲可能,因爲具體方法的實現可以委託給另一個類!!
在這個例子中,就datanode而言,DatanodeProtocol接口是沒有實現類的!