考察Hadoop的底层rpc通信(二)
来源:互联网 发布:网络综艺节目受众人群 编辑:程序博客网 时间:2024/05/16 17:12
本篇文章将延续前篇文章的内容考察Hadoop的底层ipc通信(一),继续剖析Hadoop的底层ipc通信。通过debug的方式,对自己所写的ipc demo进行通信数据流的分析。
Client端发送数据过程源码跟踪及分析
org.apache.hadoop.ipc.WritableRpcEngine类
// 调用了代理对象的invoke方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = 0; if (LOG.isDebugEnabled()) { startTime = Time.now(); } TraceScope traceScope = null; if (Trace.isTracing()) { traceScope = Trace.startSpan(RpcClientUtil.methodToTraceString(method)); } // 考察ObjectWritable ObjectWritable value; try { value = (ObjectWritable) // 也调用了客户端的call方法 走了4个参数 // a.类别 b.Invocation c.remoteId d.认证 client.call(RPC.RpcKind.RPC_WRITABLE, new Invocation(method, args), remoteId, fallbackToSimpleAuth); } finally { if (traceScope != null) traceScope.close(); } if (LOG.isDebugEnabled()) { long callTime = Time.now() - startTime; LOG.debug("Call: " + method.getName() + " " + callTime); } return value.get(); }...// 这是一个方法的调用(因为远程过程调用,实际上就是对方法的调用)包含了方法名称和他的参数// 方法的实现在远程服务端,因此需要将方法名称和相关参数打包之后传给远程服务端private static class Invocation implements Writable, Configurable { // 服务器通过以下信息,可知需要调用哪个类的哪个方法调用哪些参数 // methodName :sayHello // parameterClasses :[class java.lang.String] // parameters :[world] // conf :配置信息 // clientVersion :1 // clientMethodsHash :998346840 // declaringClassProtocolName :com.zhaotao.hadoop.ipc.HelloWorldService private String methodName; private Class<?>[] parameterClasses; private Object[] parameters; private Configuration conf; private long clientVersion; private int clientMethodsHash; private String declaringClassProtocolName; ... // Object[] parameters 为所传进来的参数数组,在该调用示例中parameters的值为:world public Invocation(Method method, Object[] parameters) { // 方法名称 取得方法名称:sayHello this.methodName = method.getName(); // 参数类型 取得参数类型集:[class java.lang.String] parameterClasses为数组形式 this.parameterClasses = method.getParameterTypes(); // 把参数的赋值给局部变量 parameters的值为[world](parameters为一个数组) this.parameters = parameters; // rpc版本 writableRpcVersion为一个固定值:2 rpcVersion = writableRpcVersion; // 得到method方法所声明的类 // method的值为:public abstract java.lang.String com.zhaotao.hadoop.ipc.HelloWorldService.sayHello(java.lang.String) // 声明的类与VersionedProtocol进行比较 VersionedProtocol为HelloWorldService的超类 if (method.getDeclaringClass().equals(VersionedProtocol.class)) { //VersionedProtocol is exempted from version check. clientVersion = 0; clientMethodsHash = 0; } else { // getDeclaringClass()方法得到方法所在的类 // getProtocolVersion()方法并取得它对应的版本号 // clientVersion的值为1 this.clientVersion = RPC.getProtocolVersion(method.getDeclaringClass()); // 取得哈希值,clientMethodsHash的值为998346840 this.clientMethodsHash = ProtocolSignature.getFingerprint(method .getDeclaringClass().getMethods()); } // 获得所声明的类的协议名称 // declaringClassProtocolName的值为com.zhaotao.hadoop.ipc.HelloWorldService this.declaringClassProtocolName = RPC.getProtocolName(method.getDeclaringClass()); } ... // 该过程为一个串行化过程 public void write(DataOutput out) throws IOException { out.writeLong(rpcVersion); UTF8.writeString(out, declaringClassProtocolName); UTF8.writeString(out, methodName); out.writeLong(clientVersion); out.writeInt(clientMethodsHash); // 写入参数类的长度 out.writeInt(parameterClasses.length); // 循环写入参数类、参数值、配置信息 for (int i = 0; i < parameterClasses.length; i++) { ObjectWritable.writeObject(out, parameters[i], parameterClasses[i], conf, true); } }}...private static class Invoker implements RpcInvocationHandler { ... // 实质上在构造的时候,走的是这个Invoker()方法 public Invoker(Class<?> protocol, InetSocketAddress address, UserGroupInformation ticket, Configuration conf, SocketFactory factory, int rpcTimeout, AtomicBoolean fallbackToSimpleAuth) throws IOException { this.remoteId = Client.ConnectionId.getConnectionId(address, protocol, ticket, rpcTimeout, conf); this.client = CLIENTS.getClient(conf, factory); this.fallbackToSimpleAuth = fallbackToSimpleAuth; } ...}
org.apache.hadoop.io.ObjectWritable类
这是多态 对象可写入 写入一个实例(是一个类名),处理数组、字符串、基本类型(不需要Writable wrapper的包装)
// 声明的类 private Class declaredClass; // 实例 private Object instance; // 配置 private Configuration conf; ... // 静态代码块,实现了java中的8种基本类型(一一对应) private static final Map<String, Class<?>> PRIMITIVE_NAMES = new HashMap<String, Class<?>>(); static { PRIMITIVE_NAMES.put("boolean", Boolean.TYPE); PRIMITIVE_NAMES.put("byte", Byte.TYPE); PRIMITIVE_NAMES.put("char", Character.TYPE); PRIMITIVE_NAMES.put("short", Short.TYPE); PRIMITIVE_NAMES.put("int", Integer.TYPE); PRIMITIVE_NAMES.put("long", Long.TYPE); PRIMITIVE_NAMES.put("float", Float.TYPE); PRIMITIVE_NAMES.put("double", Double.TYPE); PRIMITIVE_NAMES.put("void", Void.TYPE); } ...
org.apache.hadoop.ipc.Client类
// 进行参数的考察 // rpcKind :RPC_WRITABLE 使用了WRITABLE的RPC引擎 // rpcRequest :WritableRpcEngine$Invocation // 具体值 :sayHello(world), rpc version=2, client version=1, methodsFingerPrint=998346840(方法的指纹,类似于方法的版本) public Writable call(RPC.RpcKind rpcKind, Writable rpcRequest, ConnectionId remoteId, AtomicBoolean fallbackToSimpleAuth) throws IOException { return call(rpcKind, rpcRequest, remoteId, RPC.RPC_SERVICE_CLASS_DEFAULT, fallbackToSimpleAuth); } ... public Writable call(...){ ... // 用rpcKind和rpcRequest创建一个Call对象 final Call call = createCall(rpcKind, rpcRequest); // 得到连接之后,会跳转到setupConnection()去 Connection connection = getConnection(remoteId, call, serviceClass, fallbackToSimpleAuth); // 将Call对象发给远端 connection.sendRpcRequest(call); ... } ... // Call对象里面包含了RpcKind和Writable类型的rpcRequest(实质上是在客户端封装完成之后发给服务端的消息) Call createCall(RPC.RpcKind rpcKind, Writable rpcRequest) { return new Call(rpcKind, rpcRequest); } ... // 在得到连接之后,进行安装连接 private synchronized void setupConnection() throws IOException { short ioFailures = 0; short timeoutFailures = 0; while (true) { try { // 使用套接字工厂去创建套接字 this.socket = socketFactory.createSocket(); this.socket.setTcpNoDelay(tcpNoDelay); this.socket.setKeepAlive(true); /* * Bind the socket to the host specified in the principal name of the * client, to ensure Server matching address of the client connection * to host name in principal passed. */ UserGroupInformation ticket = remoteId.getTicket(); if (ticket != null && ticket.hasKerberosCredentials()) { KerberosInfo krbInfo = remoteId.getProtocol().getAnnotation(KerberosInfo.class); if (krbInfo != null && krbInfo.clientPrincipal() != null) { String host = SecurityUtil.getHostFromPrincipal(remoteId.getTicket().getUserName()); // If host name is a valid local address then bind socket to it InetAddress localAddr = NetUtils.getLocalInetAddress(host); if (localAddr != null) { this.socket.bind(new InetSocketAddress(localAddr, 0)); } } } // 在NetUtils工具类中,去连接到服务器 // socket套接字 连接到server服务器 使用了connectionTimeout超时进行判断 NetUtils.connect(this.socket, server, connectionTimeout); if (rpcTimeout > 0) { pingInterval = rpcTimeout; // rpcTimeout overwrites pingInterval } // 设置一个套接字通信的timeout超时,继续运行进入到 this.socket.setSoTimeout(pingInterval); return; } catch (ConnectTimeoutException toe) { /* Check for an address change and update the local reference. * Reset the failure counter if the address was changed */ if (updateAddress()) { timeoutFailures = ioFailures = 0; } handleConnectionTimeout(timeoutFailures++, maxRetriesOnSocketTimeouts, toe); } catch (IOException ie) { if (updateAddress()) { timeoutFailures = ioFailures = 0; } handleConnectionFailure(ioFailures++, ie); } } } ... private synchronized void setupIOstreams(...){ ... while (true) { ... setupConnection(); // 先从套接字中得到输入流 // inStream的值为:org.apache.hadoop.net.SocketInputWrapper@660acfb InputStream inStream = NetUtils.getInputStream(socket); // 再从套接字中得到输出流 // outStream的值为:org.apache.hadoop.net.SocketOutputStream@5d908d47 OutputStream outStream = NetUtils.getOutputStream(socket); // 将连接头 写入到outStream流里去 writeConnectionHeader(outStream); ... // 向服务器发送一个测试信号 if (doPing) { inStream = new PingInputStream(inStream); } // 将ping的流封装成数据的输入流 this.in = new DataInputStream(new BufferedInputStream(inStream)); ... } ... } ... /** * 当连接被建立的时候,发送给服务器 * hrpc(4个字节) + 版本(1个字节) + 服务类(1个字节) + 认证协议(1个字节) 实质上为一个报文 * Write the connection header - this is sent when connection is established * +----------------------------------+ * | "hrpc" 4 bytes | * +----------------------------------+ * | Version (1 byte) | * +----------------------------------+ * | Service Class (1 byte) | * +----------------------------------+ * | AuthProtocol (1 byte) | * +----------------------------------+ */ private void writeConnectionHeader(OutputStream outStream) throws IOException { ... // 将传进来的outStream输出流封装成了DataOutputStream类型的数据输出流 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(outStream)); // Write out the header, version and authentication method // 依次将报文中的四个内容写入到流中去 out.write(RpcConstants.HEADER.array()); //RpcConstants.HEADER.array()的值为[104, 114, 112, 99],其实就是写入hrpc四个字母 out.write(RpcConstants.CURRENT_VERSION); //版本号为9(为固定值) out.write(serviceClass); //服务类的值为0 out.write(authProtocol.callId); //认证ID的值为0 // 清理之后,将内容发给服务器 out.flush(); ... } ... public void sendRpcRequest(final Call call) throws InterruptedException, IOException { ... // 数据输出流缓冲区(将数据写入到这里,它在本地) final DataOutputBuffer d = new DataOutputBuffer(); // 通过ProtoUtil制作类,去制作一个RpcRequestHeader(Rpc的请求头) // RpcRequestHeaderProto这个类继承了com.google.protobuf.GeneratedMessage(谷歌的) // 注意:在RpcRequestHeaderProto这个类中,将传入的消息、对象转换成谷歌protobuf在内部识别的一些消息格式 // protobuf在网络间传输的消耗小 RpcRequestHeaderProto header = ProtoUtil.makeRpcRequestHeader( call.rpcKind, OperationProto.RPC_FINAL_PACKET, call.id, call.retry, clientId); // header的值为: // rpcKind: RPC_WRITABLE // rpcOp: RPC_FINAL_PACKET(rpcOp为rpc的操作) // callId: 0 // clientId: "\036]\207\256#GG\223\224\004\335\204\023\205\357\240" // etryCount: 0 // 将header写入到输出流缓冲区 header.writeDelimitedTo(d); call.rpcRequest.write(d); ... // 使用线程池将消息发送到服务端 synchronized (sendRpcRequestLock) { ... @Override public void run() { ... // d为DataOutputBuffer数据输出缓冲区;数据先写入到DataOutputBuffer中(DataOutputBuffer在本地) // 写入完成之后,将里面的内容写入到out中去,使用线程池技术,将其里面的内容一并发送到服务端 // out的值为java.io.DataOutputStream@14008db3 // out <--- BufferedOutputStream <--- SocketOutputStream byte[] data = d.getData(); int totalLength = d.getLength(); out.writeInt(totalLength); // Total Length out.write(data, 0, totalLength);// RpcRequestHeader + RpcRequest // 清除操作,发送完毕 out.flush(); ... } ... } ... }
com.google.protobuf.AbstractMessageLite类
public void writeDelimitedTo(final OutputStream output) throws IOException { // 得到串行化大小 serialized的值为26(即为26个字节) final int serialized = getSerializedSize(); // 使用CodedOutputStream编码输出流 去计算computePreferredBufferSize首选的缓冲区大小 // 计算当前消息的大小 bufferSize的值为27(即为27个字节) final int bufferSize = CodedOutputStream.computePreferredBufferSize( CodedOutputStream.computeRawVarint32Size(serialized) + serialized); // 创建一个编码输出流实例 final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output, bufferSize); // 将serialized写入打变长的32位整数里面去 codedOutput.writeRawVarint32(serialized); // 将codedOutput对象写入到流里去 writeTo(codedOutput); // 清除codedOutput流 codedOutput.flush(); }
Server端接收数据过程源码跟踪及分析
org.apache.hadoop.ipc.Server类
// 启动服务,必须在任何操作被调用之前进行操作 /** Starts the service. Must be called before any calls will be handled. */ public synchronized void start() { // 启动一个响应对象 responder.start(); // listener开始监听 listener.start(); // 处理器 有多少个就启动多少个 handlers = new Handler[handlerCount]; for (int i = 0; i < handlerCount; i++) { handlers[i] = new Handler(i); handlers[i].start(); } } ... // 监听负责客户端发来的请求,进行连接的动作 // 监听套接字,为处理器线程创建作业 /** Listens on the socket. Creates jobs for the handler threads*/ private class Listener extends Thread { ... @Override public void run() { LOG.info(Thread.currentThread().getName() + ": starting"); SERVER.set(Server.this); // 连接管理器 connectionManager.startIdleScan(); // running的值为true,因此为一个死循环 while (running) { SelectionKey key = null; try { // 得到挑选器,得到它的挑选方法 // 在这里会阻塞,等待客户端发来请求才会继续执行 getSelector().select(); // 从挑选的集合中,迭代所有的key值 Iterator<SelectionKey> iter = getSelector().selectedKeys().iterator(); // 对每一个元素进行迭代 while (iter.hasNext()) { // 取得下一个元素 key = iter.next(); // 迭代完一个,就删除 iter.remove(); try { // 判断key是否有效 if (key.isValid()) { // 判断key是否可接受 if (key.isAcceptable()) // 开始接受key // key的值为:sun.nio.ch.SelectionKeyImpl@305b7c14 doAccept(key); } } } } } } ... void doAccept(SelectionKey key) throws InterruptedException, IOException, OutOfMemoryError { ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 通道:channel 里用channel进行接收 SocketChannel channel; while ((channel = server.accept()) != null) { // 设置阻塞(false为非阻塞) channel.configureBlocking(false); channel.socket().setTcpNoDelay(tcpNoDelay); channel.socket().setKeepAlive(true); Reader reader = getReader(); // 进行连接注册 Connection c = connectionManager.register(channel); // If the connectionManager can't take it, close the connection. if (c == null) { if (channel.isOpen()) { IOUtils.cleanup(null, channel); } continue; } // 关联 key.attach(c); // so closeCurrentConnection can get the object // 添加到连接中去 reader.addConnection(c); } } ... } ... private class Handler extends Thread { public Handler(int instanceNumber) { // 将其设置为守护线程 this.setDaemon(true); this.setName("IPC Server handler "+ instanceNumber + " on " + port); } ... public void run() { LOG.debug(Thread.currentThread().getName() + ": starting"); SERVER.set(Server.this); ByteArrayOutputStream buf = new ByteArrayOutputStream(INITIAL_RESP_BUF_SIZE); while(running){ ... TraceScope traceScope = null; // 从队列中去提取一个调用对象 final Call call = callQueue.take(); // pop the queue; maybe blocked here ... } } ... } ... public class Connection { ... private void processOneRpc(byte[] buf){ ... // dis的值为:java.io.DataInputStream@480eaf50 final DataInputStream dis = new DataInputStream(new ByteArrayInputStream(buf)); // header的值为: // rpcKind: RPC_PROTOCOL_BUFFER // rpcOp: RPC_FINAL_PACKET // callId: -3 // clientId: "\220x\223:\030\317D\025\235a?\206\266+\330Z" // retryCount: -1 // 服务端接收到流之后,首先进行解码decodeProtobufFromStream(解码流) final RpcRequestHeaderProto header = // newBuilder()对象和dis流(实质为从客户端接收到的数据输入流)进行解码 // 从而得到请求头协议RpcRequestHeaderProto header decodeProtobufFromStream(RpcRequestHeaderProto.newBuilder(), dis); // 拿到呼叫ID,callId的值为0 callId = header.getCallId(); // 拿到重试次数,retry的值为0 retry = header.getRetryCount(); ... // 检查Rpc的头协议 checkRpcHeaders(header); ... // 开始处理Rpc请求 processRpcRequest(header, dis); ... } ... private void processRpcRequest(RpcRequestHeaderProto header, DataInputStream dis){ ... // 得到类别 // rpcRequestClass的值为:class org.apache.hadoop.ipc.WritableRpcEngine$Invocation Class<? extends Writable> rpcRequestClass = getRpcRequestWrapper(header.getRpcKind()); ... // 通过反射去创建Rpc请求对象 // rpcRequest = WritableRpcEngine$Invocation // rpcRequest的值为:com.sun.jdi.InvocationException occurred invoking method. // rpcRequest就是一个Invocation rpcRequest = ReflectionUtils.newInstance(rpcRequestClass, conf); // 读取内容(这是一个反串行化过程;这和客户端向服务端发送数据时串行化写入到流里是一一对应的) rpcRequest.readFields(dis); ... } ... }
ipc通信数据流图解
通过debug对源码跟踪之后,可以将Hadoop的底层 ipc 通信数据流以下图的形式进行展示:
阅读全文
0 0
- 考察Hadoop的底层rpc通信(二)
- 考察Hadoop的底层rpc通信(一)
- Hadoop的RPC通信(二)------>框架封装思想
- Hadoop的RPC通信原理
- Hadoop的RPC通信------>java实现
- Hadoop的底层架构——RPC机制
- Hadoop的底层架构——RPC机制
- 【远程调用框架】如何实现一个简单的RPC框架(四)优化二:改变底层通信框架
- 使用scala实现Akka底层的rpc通信
- Hadoop RPC通信原理
- 理解Hadoop通信 RPC
- Spark底层通信RPC源码分析
- Hadoop异步RPC通信机制
- hadoop通信核心:RPC学习
- hadoop通信核心:初探RPC
- Hadoop RPC通信Client客户端的流程分析
- Hadoop RPC通信Server端的流程分析
- Hadoop学习<四>--HDFS的RPC通信原理总结
- 文件IO小项目-模拟登陆系统
- Google算法题:二进制手表
- bugku never give up
- 使用toggle()方法进行显示隐藏
- 集合(3)张飞
- 考察Hadoop的底层rpc通信(二)
- Java经典算法题(五)
- 使用Java蓝牙无线通讯技术API概述
- leetcode 257. Binary Tree Paths
- python pydub 用法 (2)
- Sticks POJ
- javaee学习日记之java基础之类和对象
- (三)libevent源文件结构
- JAVA中的反射机制