心跳的机制大概是这样的:
1) master启动的时候,会开一个ipc server在那里。
2) slave启动时,会连接master,并每隔3秒钟主动向master发送一个“心跳”,将自己的状态信息告诉master,然后master也是通过这个心跳的返回值,向slave节点传达指令。

2、找到心跳的代码

拿namenode和datanode来说,在datanode的offerService方法中,每隔3秒向namenode发送心跳的代码:

 


[java]  view plain copy


1. /**
2.   * Main loop for the DataNode.  Runs until shutdown,
3.   * forever calling remote NameNode functions.
4.   */  
5. public void offerService() throws Exception {  
6.       
7.    ...  
8.   
9. //  
10. // Now loop for a long time....  
11. //  
12.   
13. while (shouldRun) {  
14. try {  
15. long startTime = now();  
16.   
17. //  
18. // Every so often, send heartbeat or block-report  
19. //  
20.          
21. // 如果到了3秒钟,就向namenode发心跳  
22. if (startTime - lastHeartbeat > heartBeatInterval) {  
23. //  
24. // All heartbeat messages include following info:  
25. // -- Datanode name  
26. // -- data transfer port  
27. // -- Total capacity  
28. // -- Bytes remaining  
29. //  
30.          lastHeartbeat = startTime;  
31.          DatanodeCommand[] cmds = namenode.sendHeartbeat(dnRegistration,  
32.                                                       data.getCapacity(),  
33.                                                       data.getDfsUsed(),  
34.                                                       data.getRemaining(),  
35.                                                       xmitsInProgress.get(),  
36.                                                       getXceiverCount());  
37.   
38. // 注意上面这行代码,“发送心跳”竟然就是调用namenode的一个方法??  
39.   
40.          myMetrics.heartbeats.inc(now() - startTime);  
41. //LOG.info("Just sent heartbeat, with name " + localName);  
42.   
43. // 处理对心跳的返回值(namenode传给datanode的指令)  
44. if (!processCommand(cmds))  
45. continue;  
46.        }  
47.   
48. // 这里省略很多代码  
49. ...  
50. // while (shouldRun)  
51. // offerService

上面这段代码,如果是单机的程序,没什么值得奇怪的。但是,这是hadoop集群!datanode和namenode在2台不同的机器(或2个JVM)上运行!datanode机器竟然直接调用namenode的方法!这是怎么实现的?难道是传说中的RMI吗??

下面我们主要就来分析这个方法调用的细节。

 

3、心跳的底层细节一:datanode怎么获得namenode对象的?

首先,DataNode类中,有一个namenode的成员变量:


[java]  view plain copy



    1. public class DataNode extends Configured   
    2. implements InterDatanodeProtocol, ClientDatanodeProtocol, FSConstants, Runnable {  
    3.   ...  
    4. public DatanodeProtocol namenode = null;  
    5.   ...   
    6. }


     

    下面是NameNode类的定义:


    [java]  view plain copy



    1. public class NameNode implements ClientProtocol, DatanodeProtocol,  
    2.                                  NamenodeProtocol, FSConstants,  
    3.                                  RefreshAuthorizationPolicyProtocol {  
    4.   ...   
    5. }



     

    注意:NameNode实现了DatanodeProtocol接口,DatanodeProtocol接口定义了namenode和datanode之间通信的方法。

    那么,DataNode类是怎么获取到NameNode类的引用呢?

    在Datanode端,为namenode变量赋值的代码:


    [java]  view plain copy



      1. // connect to name node  
      2. this.namenode = (DatanodeProtocol)   
      3. class,  
      4.                    DatanodeProtocol.versionID,  
      5.                    nameNodeAddr,   
      6.                    conf);



      在继续去RPC类中追踪:在继续去RPC类中追踪:


      [java]  view plain copy

      1. VersionedProtocol proxy =  
      2.         (VersionedProtocol) Proxy.newProxyInstance(  
      3. new Class[] { protocol },  
      4. new Invoker(addr, ticket, conf, factory));



      现在,明白了!
      1) 对namenode的赋值,并不是真正的new了一个实现了DatanodeProtocol接口的对象,而是获得了一个动态代理!!
      2) 上面这段代码中,protocol的类型是DatanodeProtocol.class
      3) 对namenode的所有调用,都被委托(delegate)给了Invoker
      现在,明白了! 
      1) 对namenode的赋值,并不是真正的new了一个实现了DatanodeProtocol接口的对象,而是获得了一个动态代理!! 
      2) 上面这段代码中,protocol的类型是DatanodeProtocol.class 
      3) 对namenode的所有调用,都被委托(delegate)给了Invoker 

      4、心跳的底层细节二:看看Invoker类 
      Invoker类是org.apache.hadoop.ipc.RPC类的一个静态内部类: 


      [java]  view plain copy



        1. private static class Invoker implements InvocationHandler {


         

        所有的方法调用又被delegate给client的call方法了!


        [java]  view plain copy


        1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        2.             ...  
        3.   
        4.               ObjectWritable value = (ObjectWritable)  
        5. new Invocation(method, args), address,   
        6.                             method.getDeclaringClass(), ticket);  
        7.                 ...  
        8. return value.get();  
        9.            }


        client是Invoker中的成员变量:


        [java]  view plain copy


        1. private Client client;

        所以可以看出:DatanodeProtocol中的每个方法调用,都被包装成一个Invocation对象,再由client.call()调用

        5、心跳的底层细节三:Invocation类

        Invocation类是org.apache.hadoop.ipc.RPC类的一个静态内部类

        没有什么业务逻辑方法,主要作用就是一个VO

        6、心跳的底层细节四:client类的call方法

        接下来重点看client类的call方法:


        [java]  view plain copy



        1. public Writable call(Writable param, InetSocketAddress addr,   
        2.                      Class<?> protocol, UserGroupInformation ticket)    
        3. throws InterruptedException, IOException {  
        4.   
        5. new Call(param);     
        6. // 将Invocation转化为Call  
        7.   Connection connection = getConnection(addr, protocol, ticket, call);  
        8. // 连接远程服务器  
        9. // send the parameter  
        10. // 将“序列化”后的call发给过去  
        11. boolean interrupted = false;  
        12. synchronized (call) {  
        13. while (!call.done) {  
        14. try {  
        15. // wait for the result  
        16. // 等待调用结果  
        17. catch (InterruptedException ie) {  
        18. // save the fact that we were interrupted  
        19. true;  
        20.       }  
        21.     }  
        22.   
        23. if (interrupted) {  
        24. // set the interrupt flag now that we are done waiting  
        25.       Thread.currentThread().interrupt();  
        26.     }  
        27.   
        28. if (call.error != null) {  
        29. if (call.error instanceof RemoteException) {  
        30.         call.error.fillInStackTrace();  
        31. throw call.error;  
        32. else { // local exception  
        33. throw wrapException(addr, call.error);  
        34.       }  
        35. else {  
        36. return call.value;  
        37. // 返回  
        38.     }  
        39.   }  
        40. }


         

        7、现在,一目了然了


        [java]  view plain copy



        1. datanode向namenode发送heartbeat过程是这样的:  
        1.   
        2.     a) 在datanode初始化获得namenode的proxy  
        3.     b) 在datanode上,调用namenode proxy的heartbeat方法:  
        4.         namenode.sendHeartbeat(dnRegistration,  
        5.                                                        data.getCapacity(),  
        6.                                                        data.getDfsUsed(),  
        7.                                                        data.getRemaining(),  
        8.                                                        xmitsInProgress.get(),  
        9.                                                        getXceiverCount());  
        10.     c) 在datanode上的namenode动态代理类将这个调用包装成(或者叫“序列化成”)一个Invocation对象,并调用client.call方法  
        11.     d) client call方法将Invocation转化为Call对象  
        12.     e) client 将call发送到真正的namenode服务器  
        13.     f) namenode接收后,转化成namenode端的Call,并process后,通过Responder发回来!  
        14.     g) datanode接收结果,并将结果转化为DatanodeCommand[]  
        15.



         

        8、再看动态代理

        动态代理:让“只有接口,没事对应的实现类”成为可能,因为具体方法的实现可以委托给另一个类!!

        在这个例子中,就datanode而言,DatanodeProtocol接口是没有实现类的!