关于Hadoop已经小记了六篇,《Hadoop实战》也已经翻完7章。仔细想想,这么好的一个框架,不能只是流于应用层面,跑跑数据排序、单表链接等,想得其精髓,还需深入内部。
按照《 Hadoop阅读笔记(五)——重返Hadoop目录结构 》中介绍的hadoop目录结构,前面已经介绍了MapReduce的内部运行机制,今天准备入手Hadoop RPC,它是hadoop一种通信机制。
RPC ( Remote Procedure Call Protocol )——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
不同于其他RPC产品,Hadoop有属于自己RPC组件,依赖于《 Hadoop阅读笔记(六)——洞悉Hadoop序列化机制Writable 》中介绍的Hadoop Writable类型的支持。Hadoop Writable接口要求每个实现类都得确保将本类的对象正确序列化(writeObject)与反序列化(readObject)。因此,Hadoop RPC使用 Java动态代理与反射 实现对象调用方式,客户端到服务器数据的序列化与反序列化由Hadoop框架或用户自己来实现,也就是数据组装是定制的。
显然我们没法上来就直接剖析RPC,因为这里有一些绕不开的东西要先介绍:Writable序列化反序列化(已在第六篇介绍)、动态代理。所以,这里先介绍什么是动态代理。
动态代理是代理模式的一种,还有一种就是静态代理。
代理模式 :为其他对象提供一种代理,并以控制对这个对象的访问。而对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。它是给某一个对象提供一个替代者(占位者),使之在client对象和subject对象之间编码更有效率。代理可以提供延迟实例化(lazy instantiation),控制访问, 等等,包括只在调用中传递。
静态代理 :由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理 :在程序运行时,运用 反射机制 动态创建而成。
Person.java(接口)
1 package hadoop.jackie.dao; 2 3 public interface Person { 4 5 public void eat();//人具有吃饭的行为 6 7 public void sleep();//人具有睡觉的行为 8 9 }
PersonImpl.java(实现类,也就是这里的委托类)
1 package hadoop.jackie.dao.impl; 2 3 import hadoop.jackie.dao.Person; 4 5 public class PersonImpl implements Person { 6 7 @Override 8 public void eat() { 9 System.out.println("我是吃货"); 10 11 } 12 13 @Override 14 public void sleep() { 15 System.out.println("我是猪,别管我"); 16 17 } 18 19 }
PersonProxy.java(代理类)
1 package hadoop.jackie.dao.impl; 2 3 import hadoop.jackie.dao.Person; 4 5 public class PersonProxy implements Person { 6 7 private PersonImpl personImpl; 8 9 public PersonProxy(PersonImpl personImpl){ 10 this.personImpl = personImpl; 11 } 12 13 @Override 14 public void eat() { 15 System.out.println("吃饭前"); 16 personImpl.eat();//调用委托类PersonImpl的吃饭方法 17 System.out.println("吃饭后"); 18 19 } 20 21 @Override 22 public void sleep() { 23 System.out.println("睡觉前"); 24 personImpl.sleep();//调用委托类PersonImpl的睡觉方法 25 System.out.println("睡觉后"); 26 27 } 28 29 }
PersonTest.java
1 package hadoop.jackie.dao.test; 2 3 import hadoop.jackie.dao.impl.PersonImpl; 4 import hadoop.jackie.dao.impl.PersonProxy; 5 6 public class PersonTest { 7 8 /** 9 * @param args 10 */ 11 public static void main(String[] args) { 12 13 PersonImpl personImpl = new PersonImpl(); 14 PersonProxy personProxy = new PersonProxy(personImpl); 15 personProxy.eat(); 16 personProxy.sleep(); 17 18 } 19 20 }
PersonTest.java运行结果为:
从运行结果可以发现,类PersonProxy具备了PersonImpl的eat()和sleep()的方法,实现了对于类PersonImpl的委托。
但是为什么称该模式为静态代理模式,从代码中可以发现,在代理类PersonProxy中需要手动配置委托类PersonImpl
该模式效率较低,适用性较弱,代理类和委托类的耦合性较高。如果需要为多个委托类做代理,则会出现很多的代理类,从而有可能产生冗余代码。
能够克服以上缺点的模式就是动态代理。
介绍动态代理前,需要先了解相关的一个接口InvocationHandler和一个类Proxy
该接口只有一个方法:Object invoke(Object proxy, Method method, Object[] args)
该方法用来处理方法的调用,实现类也有同样的意义;与代理类对象相关联则表示,
负责处理代理类应该有的动作,把所有的方法请求分发到invoke这个方法上。 (具体可以结合后面的代码理解)
Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
该方法用于产生代理对象,它与代理对象关联,当请求分发到代理对象后,会自动执行h.invoke(...)方法,
Person.java(接口)
1 package hadoop.jackie.dynamic.dao; 2 3 public interface Person { 4 5 public void addGirlFriend();//小伙子具备找女朋友的方法 6 7 }
PersonImpl.java(实现类,即委托类)
1 package hadoop.jackie.dynamic.dao.impl; 2 3 import hadoop.jackie.dynamic.dao.Person; 4 5 public class PersonImpl implements Person { 6 7 @Override 8 public void addGirlFriend() { 9 10 System.out.println("吃饱睡好,该找个女盆友啦"); 11 12 } 13 14 }
PersonProxy.java(代理类)
1 package hadoop.jackie.dynamic.dao.impl; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 7 public class PersonProxy implements InvocationHandler { 8 9 private Object targetGirl; 10 11 public Object bind(Object targetGirl){ 12 this.targetGirl = targetGirl; 13 return Proxy.newProxyInstance(targetGirl.getClass().getClassLoader(), targetGirl.getClass().getInterfaces(), this); 14 } 15 16 @Override 17 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 18 Object result = null; 19 System.out.println("找女盆友前"); 20 result = method.invoke(targetGirl, args); 21 System.out.println("找女盆友后"); 22 return result; 23 } 24 25 }
PersonTest.java
1 package hadoop.jackie.dynamic.dao; 2 3 import hadoop.jackie.dynamic.dao.impl.PersonImpl; 4 import hadoop.jackie.dynamic.dao.impl.PersonProxy; 5 6 public class PersonTest { 7 8 /** 9 * @param args 10 */ 11 public static void main(String[] args) { 12 PersonProxy proxy = new PersonProxy(); 13 Person personProxy = (Person)proxy.bind(new PersonImpl()); 14 personProxy.addGirlFriend(); 15 16 } 17 18 }
运行结果为:
通过以上两种模式的介绍以及展示的代码,需要注意一下几点:
就这里的动态代理的代码逻辑来看:某小伙(Person.java)已经快奔三,急缺女友一枚(PersonImpl.java),但是由于种种原因如长得相貌出众、海拔太高、人品太好等,已经到了自己完全搞不定的时候了,而且这时家里催的又紧,怎么办?找代理啊,谁?媒婆啊(PersonProxy.java)。媒婆手头资源丰富,轻轻松松帮你搞定,还能按照你的需求进行定制比如瓜子壳脸还是猪腰子脸。这就是代理!
在请求代理类的方法时,比如PersonTest.java:
personProxy.addGirlFriend();
这个请求会被转到执行与代理类关联InvocationHandler 的invoke方法,然后通过
result = method.invoke(targetGirl, args);
利用java的反射机制调用到方法addGirlFriend()。
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。
在攻克hadoop源码中rpc模块的道路上,又前进了一个关卡,多走了几步^_^
本文链接:《 Hadoop阅读笔记(七)—— 代理模式 》
如果对你有帮助,欢迎点赞,对于Hadoop等大数据技术有兴趣的欢迎加群413471695交流讨论^_^。