在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类之前,还是先看一下与它相关的几个内部类的实现。
- RPC.Invocation内部类
该内部类定义了方法的调用,包括方法的名称和参数,它实现了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类对象是可序列化的。
- RPC.ClientCache内部类
该内部类定义了一个缓存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.VersionMismatch内部类
该内部类主要是用来:当检测到版本与RPC协议不匹配的时候,作为异常来处理信息的类。
- RPC.Invoker内部类
该内部类实现了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就被关闭
- }
- }