在org.apache.hadoop.ipc包中,Server类是一个抽象类,抽象了IPC模型中Server端的基本行为。下面对RPC类进行阅读分析。
RPC类给出了一个简单的RPC机制,它的协议是基于一个Java接口,协议界定,所有的参数和返回类型必须是下面之一:
1、一个基本类型:boolean、byte、char、short、int、long、float、double,或void;
2、String;
3、org.apache.hadoop.io.Writable;
4、上述类型的数组。
在RPC类中,RPC.Server类继承自org.apache.hadoop.ipc.Server抽象类,实现了一个RPC服务器。在分析RPC.Server类之前,还是先看一下与它相关的几个内部类的实现。
该内部类定义了方法的调用,包括方法的名称和参数,它实现了Writable,Configurable接口。该内部类定义的属性如下所示:
private String methodName; // 方法名 private Class[] parameterClasses; // 参数类型集合 private Object[] parameters; // 参数值 private Configuration conf; // 配置类实例
因为该内部类实现了Writable接口,所以必须实现该接口定义的两个方法,如下所示:
public void readFields(DataInput in) throws IOException { methodName = UTF8.readString(in); // 读取方法名 parameters = new Object[in.readInt()]; // 读取调用方法的参数值 parameterClasses = new Class[parameters.length]; // 参数类型 ObjectWritable objectWritable = new ObjectWritable(); for (int i = 0; i < parameters.length; i++) { parameters[i] = ObjectWritable.readObject(in, objectWritable, this.conf); // 读取每个调用参数值 parameterClasses[i] = objectWritable.getDeclaredClass(); // 读取每个参数类型 } } public void write(DataOutput out) throws IOException { UTF8.writeString(out, methodName); // 向输出流out中写入方法名 out.writeInt(parameterClasses.length); // 写入方法参数类型个数 for (int i = 0; i < parameterClasses.length; i++) { ObjectWritable.writeObject(out, parameters[i], parameterClasses[i], conf); } }
因此,RPC.Invocation类对象是可序列化的。
该内部类定义了一个缓存Map:
private Map<SocketFactory, Client> clients = new HashMap<SocketFactory, Client>();
通过客户端org.apache.hadoop.ipc.Client的SocketFactory可以快速取出对应的Client实例。
该内部类中实现了获取一个Client的方法:
/** * 从缓存Map中取出一个IPC Client实例,如果缓存够中不存在,就创建一个兵加入到缓存Map中 */ private synchronized Client getClient(Configuration conf, SocketFactory factory) { Client client = clients.get(factory); if (client == null) { client = new Client(ObjectWritable.class, conf, factory); // 通过反射实例化一个ObjectWritable对象,构造Client实例 clients.put(factory, client); // 加入缓存Map } else { client.incCount(); // 增加客户端client实例的引用计数 } return client; }
终止一个RPC客户端连接,实现方法为:
private void stopClient(Client client) { synchronized (this) { client.decCount(); // 该client实例的引用计数减1 if (client.isZeroReference()) { // 如果client实例的引用计数此时为0 clients.remove(client.getSocketFactory()); // 从缓存中删除 } } if (client.isZeroReference()) { // 如果client实例引用计数为0,需要关闭 client.stop(); // 停止所有与该client实例相关的线程 } } }
该内部类主要是用来:当检测到版本与RPC协议不匹配的时候,作为异常来处理信息的类。
该内部类实现了java.lang.reflect.InvocationHandler接口,是一个代理实例的调用处理程序实现类。
该内部类实现如下所示:
private static class Invoker implements InvocationHandler { private InetSocketAddress address; // 远程服务器地址 private UserGroupInformation ticket; // 客户端用户身份信息 private Client client; // 客户端实例 private boolean isClosed = false; // 客户端是否关闭 public Invoker(InetSocketAddress address, UserGroupInformation ticket, Configuration conf, SocketFactory factory) { this.address = address; this.ticket = ticket; this.client = CLIENTS.getClient(conf, factory); } /** * Stop a RPC client connection * @param proxy 代理实例 * @param method 某个类的方法实例 * @param args 方法参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final boolean logDebug = LOG.isDebugEnabled(); long startTime = 0; if (logDebug) { startTime = System.currentTimeMillis(); } // 构造一个RPC.Invocation实例作为参数传递给调用程序,执行调用,返回值为value ObjectWritable value = (ObjectWritable)client.call(new Invocation(method, args), address, method.getDeclaringClass(), ticket); if (logDebug) { long callTime = System.currentTimeMillis() - startTime; LOG.debug("Call: " + method.getName() + " " + callTime); } return value.get(); // 返回调用处理结果(value的实例) } /* 关闭client */ synchronized private void close() { if (!isClosed) { isClosed = true; CLIENTS.stopClient(client); } } }
该内部类出列了客户端调用,并返回调用处理结果。
RPC.Server内部类
该内部类是RPC最核心的,它继承自org.apache.hadoop.ipc.Server抽象类,实现的最核心的是调用处理方法:
public Writable call(Class<?> protocol, Writable param, long receivedTime) throws IOException { try { Invocation call = (Invocation)param; if (verbose) log("Call: " + call); Method method = protocol.getMethod(call.getMethodName(), call.getParameterClasses()); // 通过反射,根据调用方法名和方法参数类型得到Method实例 method.setAccessible(true); // 设置反射的对象在使用时取消Java语言访问检查,提高效率 long startTime = System.currentTimeMillis(); Object value = method.invoke(instance, call.getParameters()); // 执行调用(instance是调用底层方法的对象,第二个参数是方法调用的参数) int processingTime = (int) (System.currentTimeMillis() - startTime); int qTime = (int) (startTime-receivedTime); if (LOG.isDebugEnabled()) { LOG.debug("Served: " + call.getMethodName() + " queueTime= " + qTime + " procesingTime= " + processingTime); } rpcMetrics.rpcQueueTime.inc(qTime); rpcMetrics.rpcProcessingTime.inc(processingTime); MetricsTimeVaryingRate m = (MetricsTimeVaryingRate) rpcMetrics.registry.get(call.getMethodName()); if (m == null) { try { m = new MetricsTimeVaryingRate(call.getMethodName(), rpcMetrics.registry); } catch (IllegalArgumentException iae) { // the metrics has been registered; re-fetch the handle LOG.info("Error register " + call.getMethodName(), iae); m = (MetricsTimeVaryingRate) rpcMetrics.registry.get(call.getMethodName()); } } m.inc(processingTime); if (verbose) log("Return: "+value); return new ObjectWritable(method.getReturnType(), value); // 返回:调用的返回值对象 } catch (InvocationTargetException e) { Throwable target = e.getTargetException(); if (target instanceof IOException) { throw (IOException)target; } else { IOException ioe = new IOException(target.toString()); ioe.setStackTrace(target.getStackTrace()); throw ioe; } } catch (Throwable e) { IOException ioe = new IOException(e.toString()); ioe.setStackTrace(e.getStackTrace()); throw ioe; } }
还有一个方法实现了对用户的授权,这在org.apache.hadoop.ipc.Server抽象类中并没实现,如下所示:
@Override public void authorize(Subject user, ConnectionHeader connection) throws AuthorizationException { if (authorize) { // authorize默认为false,除非在Configuration配置类实例中获取到的为true。可见,该简单的RPC默认不需要对用户进行授权操作 Class<?> protocol = null; try { protocol = getProtocolClass(connection.getProtocol(), getConf()); } catch (ClassNotFoundException cfne) { throw new AuthorizationException("Unknown protocol: " + connection.getProtocol()); } ServiceAuthorizationManager.authorize(user, protocol); // 执行授权操作,使得用户可以访问被使用的Protocol } } }
上面,已经对RPC类的内部类的实现进行了阅读分析,现在看RPC类提供的操作。
获取到一个到远程服务器的代理:
/** * 获取到一个到远程服务器的代理连接 * @param protocol 协议类 * @param clientVersion 客户端版本 * @param addr 远程地址 * @param conf 使用配置类实例 * @param timeout 超时时间 * @return 返回代理 */ static VersionedProtocol waitForProxy(Class protocol, long clientVersion, InetSocketAddress addr, Configuration conf, long timeout ) throws IOException;
另外,还有几个获取代理的getProxy方法:
public static VersionedProtocol getProxy(Class<?> protocol, long clientVersion, InetSocketAddress addr, Configuration conf) throws IOException; public static VersionedProtocol getProxy(Class<?> protocol, long clientVersion, InetSocketAddress addr, Configuration conf, SocketFactory factory) throws IOException; public static VersionedProtocol getProxy(Class<?> protocol, long clientVersion, InetSocketAddress addr, UserGroupInformation ticket, Configuration conf, SocketFactory factory) throws IOException;
下面是RPC服务器处理调用的过程实现:
public static Object[] call(Method method, Object[][] params, InetSocketAddress[] addrs, UserGroupInformation ticket, Configuration conf) throws IOException { Invocation[] invocations = new Invocation[params.length]; // 一组方法调用实例 for (int i = 0; i < params.length; i++) invocations[i] = new Invocation(method, params[i]); Client client = CLIENTS.getClient(conf); // 创建并缓存一个org.apache.hadoop.ipc.Client实例 try { Writable[] wrappedValues = client.call(invocations, addrs, method.getDeclaringClass(), ticket); // 根据参数,客户端发送调用方法及其参数 if (method.getReturnType() == Void.TYPE) { return null; } Object[] values = (Object[])Array.newInstance(method.getReturnType(), wrappedValues.length); // 客户端执行RPC调用,获取到返回值 for (int i = 0; i < values.length; i++) if (wrappedValues[i] != null) values[i] = ((ObjectWritable)wrappedValues[i]).get(); // 获取返回值的实例 return values; // 返回 } finally { CLIENTS.stopClient(client); // 如果该client实例的引用计数为0,该client就被关闭 } }