上一篇讲解了DN如何和NN进行socket连接,在获得一个NN的代理时,连接完毕后会做一个RPC调用,获得服务端的getProtocolVersion,然后和客户端比较,如果相同则会返回代理对象,可以说又是一次暗号对接,继续看getProxy函数
public static VersionedProtocol getProxy(
Class<? extends VersionedProtocol> protocol,
long clientVersion, InetSocketAddress addr, UserGroupInformation ticket,
Configuration conf, SocketFactory factory, int rpcTimeout) throws IOException {
if (UserGroupInformation.isSecurityEnabled()) {
SaslRpcServer.init(conf);
}
//在这里获得NN代理,建立与NN的socket连接
VersionedProtocol proxy =
(VersionedProtocol) Proxy.newProxyInstance(
protocol.getClassLoader(), new Class[] { protocol },
new Invoker(protocol, addr, ticket, conf, factory, rpcTimeout));
//获得服务端协议版本,该调用会通知NN调用相同的函数,并把结果返回给客户端
long serverVersion = proxy.getProtocolVersion(protocol.getName(),
clientVersion);
//版本比较,在上一篇已经提过
if (serverVersion == clientVersion) {
return proxy;
} else {
throw new VersionMismatch(protocol.getName(), clientVersion,
serverVersion);
}
}
现在看getProtocolVersion是如何执行的 public Writable call(Writable param, ConnectionId remoteId)
throws InterruptedException, IOException {
Call call = new Call(param);
//获得连接
Connection connection = getConnection(remoteId, call);
//发送call对象,下面会继续介绍
connection.sendParam(call); // send the parameter
boolean interrupted = false;
synchronized (call) {
while (!call.done) {
try {
//如果服务端没有返回则一直等待在这里
call.wait(); // wait for the result
} catch (InterruptedException ie) {
// save the fact that we were interrupted
interrupted = true;
}
}
........
}
下面看具体的发送格式public void sendParam(Call call) {
if (shouldCloseConnection.get()) {
return;
}
DataOutputBuffer d=null;
try {
synchronized (this.out) {
if (LOG.isDebugEnabled())
LOG.debug(getName() + " sending #" + call.id);
//建立输出缓冲区
d = new DataOutputBuffer();
//写入调用ID
d.writeInt(call.id);
//写入调用参数格式为:方法名、参数个数(4字节Int)、具体参数
call.param.write(d);
byte[] data = d.getData();
int dataLength = d.getLength();
out.writeInt(dataLength); //first put the data length
out.write(data, 0, dataLength);//write the data
//刷新缓冲区
out.flush();
}
} catch(IOException e) {
markClosed(e);
} finally {
//关闭这个临时缓冲区
IOUtils.closeStream(d);
}
}
到目前为止,发送至服务端的调用已经发送完毕,就等服务端的返回了上一篇中已经介绍过,为了接受返回值,DD已经启动了接收线程用于接收服务端的返回值
private void receiveResponse() {
if (shouldCloseConnection.get()) {
return;
}
//更新连接时间戳
touch();
try {
//服务端返回前一直阻塞在这里,知道返回,首先读取ID号,如果调用成功则删除
int id = in.readInt(); // try to read an id
if (LOG.isDebugEnabled())
LOG.debug(getName() + " got value #" + id);
Call call = calls.get(id);
//获得调用标志,是否成功
int state = in.readInt(); // read call status
if (state == Status.SUCCESS.state) {
//构建保留返回值的对象
Writable value = ReflectionUtils.newInstance(valueClass, conf);
//获得返回值
value.readFields(in); // read value
//更新call的成员变量
call.setValue(value);
//删除call对象,在这里删除后,继续回到run函数,进而main线程的wait函数会被唤醒
//接受线程会被销毁
calls.remove(id);
} else if (state == Status.ERROR.state) {
call.setException(new RemoteException(WritableUtils.readString(in),
WritableUtils.readString(in)));
calls.remove(id);
} else if (state == Status.FATAL.state) {
// Close the connection
markClosed(new RemoteException(WritableUtils.readString(in),
WritableUtils.readString(in)));
}
} catch (IOException e) {
markClosed(e);
}
}
至此,client的call对象已经获得返回值,主线程继续执行getProtocolVersion已经有了返回值,如下public static VersionedProtocol getProxy(
Class<? extends VersionedProtocol> protocol,
long clientVersion, InetSocketAddress addr, UserGroupInformation ticket,
Configuration conf, SocketFactory factory, int rpcTimeout) throws IOException {
if (UserGroupInformation.isSecurityEnabled()) {
SaslRpcServer.init(conf);
}
VersionedProtocol proxy =
(VersionedProtocol) Proxy.newProxyInstance(
protocol.getClassLoader(), new Class[] { protocol },
new Invoker(protocol, addr, ticket, conf, factory, rpcTimeout));
//此处serverVersion已经有了返回值,一旦和客户端版本校验成功,则会返回代理对象
long serverVersion = proxy.getProtocolVersion(protocol.getName(),
clientVersion);
if (serverVersion == clientVersion) {
//校验成功后,这里直接返回代理对象
return proxy;
} else {
throw new VersionMismatch(protocol.getName(), clientVersion,
serverVersion);
}
}
整个代理创建的过程就分析到这里,主要流程为创建代理实例并验证,在创建代理实例中并未和服务端通信,一直到RPC调用,核对版本信息后才能返回。