本篇文章将延续前篇文章的内容考察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 通信数据流以下图的形式进行展示: