Hadoop版本Hadoop2.6

RPC主要分为3个部分:(1)交互协议(2)客户端(3)服务端

(2)客户端

先展示RPC客户端实例代码

public class LoginController {
public static void main(String[] args) throws IOException {
  //获取RPC LoginServiceInterface协议接口的代理对象
LoginServiceInterface proxy= RPC.getProxy(LoginServiceInterface.class,1L,new InetSocketAddress("localhost",10000),new Configuration());
String msg=proxy.login("xiaoming","123123");
System.out.println(msg);
}
}

(1)进入上述的RPC.getProxy方法,会发现是通过获取RpcEngine接口(默认实现是WritableRpcEngine),利用WritableRpcEngine的getProxy方法获取Proxy代理,如下所示

public <T> ProtocolProxy<T> getProxy(Class<T> protocol, long clientVersion,
InetSocketAddress addr, UserGroupInformation ticket,
Configuration conf, SocketFactory factory,
int rpcTimeout, RetryPolicy connectionRetryPolicy,
AtomicBoolean fallbackToSimpleAuth)
throws IOException { if (connectionRetryPolicy != null) {
throw new UnsupportedOperationException(
"Not supported: connectionRetryPolicy=" + connectionRetryPolicy);
}
T proxy = (T) Proxy.newProxyInstance(protocol.getClassLoader(),
new Class[] { protocol }, new Invoker(protocol, addr, ticket, conf,
factory, rpcTimeout, fallbackToSimpleAuth));
return new ProtocolProxy<T>(protocol, proxy, true);
}

(2)上述就是客户端获取代理的过程,但是其中是如何从服务端获取通过动态代理类Invoker实现,并将代理封装成ProtocolProxy类,在本文上述的例子中,该ProtocolProxy类没有干什么,只是通过getProxy()方法将封装的代理返回给客户端

那么我们接着分析动态代理类Invoker

Invoker成员有Clinet类,并且全局变量ClientCache对Client进行缓存。

动态代理类Invoker在代理对象发送请求时会自动执行invoke()方法,如下所示:

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(
method.getDeclaringClass().getCanonicalName() +
"." + method.getName());
}
ObjectWritable value;
try {
value = (ObjectWritable)
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();
}

3、上述中动态代理通过client.call方法向服务器发送请求获取返回值。

我们还看到Invocation类封装了方法和参数,Invocation通过实现Writable实现序列化,方便数据在网络中传输,作为数据传输层,相当于VO。

因此我们接着进入Clinet类,查看call方法干了什么。

首先我们先看看Client类的结构,Client类包含了几个内部类:

Call :用于封装Invocation对象,作为VO,写到服务端,同时也用于存储从服务端返回的数据
Connection :用以处理远程连接对象。继承了Thread
ConnectionId :唯一确定一个连接

Client类中call()方法如下所示:

public Writable call(RPC.RpcKind rpcKind, Writable rpcRequest,
ConnectionId remoteId, int serviceClass,
AtomicBoolean fallbackToSimpleAuth) throws IOException {
final Call call = createCall(rpcKind, rpcRequest);//将传入的数据封装成call对象
Connection connection = getConnection(remoteId, call, serviceClass,
fallbackToSimpleAuth);//获得一个连接  
try {
connection.sendRpcRequest(call); // send the rpc request向服务端发送call对象
} catch (RejectedExecutionException e) {
throw new IOException("connection has been closed", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.warn("interrupted waiting to send rpc request to server", e);
throw new IOException(e);
} 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;
}
} if (interrupted) {
// set the interrupt flag now that we are done waiting
Thread.currentThread().interrupt();
} if (call.error != null) {
if (call.error instanceof RemoteException) {
call.error.fillInStackTrace();
throw call.error;
} else { // local exception
InetSocketAddress address = connection.getRemoteAddress();
throw NetUtils.wrapException(address.getHostName(),
address.getPort(),
NetUtils.getHostname(),
0,
call.error);
}
} else {
return call.getRpcResponse();
}
}
}

4、从上述可以看到,rpcRequest是将方法和参数封装后的可序列号的对象,当做请求参数发送给服务端。

在上述方法中主要使用了两个类Call和Connection.

Call:封装了与服务端请求的状态,包括:

    final int id;               // call id该请求连接ID
final int retry; // retry count该请求重试次数
final Writable rpcRequest; // the serialized rpc request该请求参数
Writable rpcResponse; // null if rpc has error该请求的返回值
IOException error; // exception, null if success该请求成功标示
final RPC.RpcKind rpcKind; // Rpc EngineKind使用RpcEngine的类型
boolean done; // true when call is done该请求完成标示

Connection则是实现了与服务端建立连接,发送请求,获取数据等功能。

5、Connection类解析

Connection类继承线程类Thread.

从3步可以看到在Clinet的call()方法通过getConnection()方法获取Connection,如下所示:

可以看出Client使用connections对客户端每一个connection进行缓存,

并通过setupIOstreams()方法与服务器建立Socket连接,并创建输入输出流connection.in,connection.out,

并通过start()方法启动该线程也就是运行Connection类的run()方法,等待服务端传回数据。

因此Connection类主要通过run()方法接受数据,通过sendRpcRequest()向服务端发送请求。

private Connection getConnection(ConnectionId remoteId,
Call call, int serviceClass, AtomicBoolean fallbackToSimpleAuth)
throws IOException {
if (!running.get()) {
// the client is stopped
throw new IOException("The client is stopped");
}
Connection connection;
/* we could avoid this allocation for each RPC by having a
* connectionsId object and with set() method. We need to manage the
* refs for keys in HashMap properly. For now its ok.
*/
do {
synchronized (connections) {
connection = connections.get(remoteId);
if (connection == null) {
connection = new Connection(remoteId, serviceClass);
connections.put(remoteId, connection);
}
}
} while (!connection.addCall(call)); //we don't invoke the method below inside "synchronized (connections)"
//block above. The reason for that is if the server happens to be slow,
//it will take longer to establish a connection and that will slow the
//entire system down.
connection.setupIOstreams(fallbackToSimpleAuth);
return connection;
}

5.1 Connection 的sendRpcRequest()向服务端发送请求

public void sendRpcRequest(final Call call)
throws InterruptedException, IOException {
if (shouldCloseConnection.get()) {
return;
} // Serialize the call to be sent. This is done from the actual
// caller thread, rather than the sendParamsExecutor thread, // so that if the serialization throws an error, it is reported
// properly. This also parallelizes the serialization.
//
// Format of a call on the wire:
// 0) Length of rest below (1 + 2)
// 1) RpcRequestHeader - is serialized Delimited hence contains length
// 2) RpcRequest
//
// Items '1' and '2' are prepared here.
final DataOutputBuffer d = new DataOutputBuffer();
RpcRequestHeaderProto header = ProtoUtil.makeRpcRequestHeader(
call.rpcKind, OperationProto.RPC_FINAL_PACKET, call.id, call.retry,
clientId);
header.writeDelimitedTo(d);
call.rpcRequest.write(d); synchronized (sendRpcRequestLock) {
Future<?> senderFuture = sendParamsExecutor.submit(new Runnable() {
@Override
public void run() {
try {
synchronized (Connection.this.out) {
if (shouldCloseConnection.get()) {
return;
} if (LOG.isDebugEnabled())
LOG.debug(getName() + " sending #" + call.id); byte[] data = d.getData();
int totalLength = d.getLength();
out.writeInt(totalLength); // Total Length
out.write(data, 0, totalLength);// RpcRequestHeader + RpcRequest
out.flush();
}
} catch (IOException e) {
// exception at this point would leave the connection in an
// unrecoverable state (eg half a call left on the wire).
// So, close the connection, killing any outstanding calls
markClosed(e);
} finally {
//the buffer is just an in-memory buffer, but it is still polite to
// close early
IOUtils.closeStream(d);
}
}
}); try {
senderFuture.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause(); // cause should only be a RuntimeException as the Runnable above
// catches IOException
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else {
throw new RuntimeException("unexpected checked exception", cause);
}
}
}
}

5.2 Connection 的run()获取服务端返回的数据

可以看到通过receiveRpcResponse()方法通过之前建立的输入流in获取服务器传来的数据,并将数据value传给call数据对象call.setRpcResponse(value);,

在call.setRpcResponse(value)方法中通过callComplete()将call数据对象设置成已完成,并通过notify()唤醒该call对象。

在Client的call()方法中,检测到call对象已完成后,就将call对象中的响应数据返回给调用者。

至此,一个完整的RPC远程过程调用的过程就完成了。

public void run() {
if (LOG.isDebugEnabled())
LOG.debug(getName() + ": starting, having connections "
+ connections.size()); try {
while (waitForWork()) {//wait here for work - read or close connection循环等待获取服务端数据
receiveRpcResponse();//获取服务端数据的具体实现
}
} catch (Throwable t) {
// This truly is unexpected, since we catch IOException in receiveResponse
// -- this is only to be really sure that we don't leave a client hanging
// forever.
LOG.warn("Unexpected error reading responses on connection " + this, t);
markClosed(new IOException("Error reading responses", t));
} close(); if (LOG.isDebugEnabled())
LOG.debug(getName() + ": stopped, remaining connections "
+ connections.size());
}
private void receiveRpcResponse() {
if (shouldCloseConnection.get()) {
return;
}
touch(); try {
int totalLen = in.readInt();
RpcResponseHeaderProto header =
RpcResponseHeaderProto.parseDelimitedFrom(in);
checkResponse(header); int headerLen = header.getSerializedSize();
headerLen += CodedOutputStream.computeRawVarint32Size(headerLen); int callId = header.getCallId();
if (LOG.isDebugEnabled())
LOG.debug(getName() + " got value #" + callId); Call call = calls.get(callId);
RpcStatusProto status = header.getStatus();
if (status == RpcStatusProto.SUCCESS) {
Writable value = ReflectionUtils.newInstance(valueClass, conf);
value.readFields(in); // read value
calls.remove(callId);
call.setRpcResponse(value); // verify that length was correct
// only for ProtobufEngine where len can be verified easily
if (call.getRpcResponse() instanceof ProtobufRpcEngine.RpcWrapper) {
ProtobufRpcEngine.RpcWrapper resWrapper =
(ProtobufRpcEngine.RpcWrapper) call.getRpcResponse();
if (totalLen != headerLen + resWrapper.getLength()) {
throw new RpcClientException(
"RPC response length mismatch on rpc success");
}
}
} else { // Rpc Request failed
// Verify that length was correct
if (totalLen != headerLen) {
throw new RpcClientException(
"RPC response length mismatch on rpc error");
} final String exceptionClassName = header.hasExceptionClassName() ?
header.getExceptionClassName() :
"ServerDidNotSetExceptionClassName";
final String errorMsg = header.hasErrorMsg() ?
header.getErrorMsg() : "ServerDidNotSetErrorMsg" ;
final RpcErrorCodeProto erCode =
(header.hasErrorDetail() ? header.getErrorDetail() : null);
if (erCode == null) {
LOG.warn("Detailed error code not set by server on rpc error");
}
RemoteException re =
( (erCode == null) ?
new RemoteException(exceptionClassName, errorMsg) :
new RemoteException(exceptionClassName, errorMsg, erCode));
if (status == RpcStatusProto.ERROR) {
calls.remove(callId);
call.setException(re);
} else if (status == RpcStatusProto.FATAL) {
// Close the connection
markClosed(re);
}
}
} catch (IOException e) {
markClosed(e);
}
}

Hadoop RPC源码阅读-客户端的更多相关文章

  1. Hadoop RPC源码阅读-交互协议

    Hadoop版本Hadoop2.6 RPC主要分为3个部分:(1)交互协议(2)客户端 (3)服务端 (1)交互协议 协议:把某些接口和接口中的方法称为协议,客户端和服务端只要实现这些接口中的方法就可 ...

  2. Hadoop RPC源码阅读-服务端Server

    Hadoop版本Hadoop2.6 RPC主要分为3个部分:(1)交互协议 (2)客户端(3)服务端 (3)服务端 RPC服务端的实例代码: public class Starter { public ...

  3. Hadoop RPC源码分析

    Hadoop RPC源码分析 上一篇文章http://www.cnblogs.com/dycg/p/rpc.html 讲了Hadoop RPC的使用方法,这一次我们从demo中一层层进行分析. RPC ...

  4. 踏着前人的脚印学Hadoop——RPC源码

    A simple RPC mechanism.A protocol  is a Java interface.  All parameters and return types must be one ...

  5. 【深入浅出 Yarn 架构与实现】1-2 搭建 Hadoop 源码阅读环境

    本文将介绍如何使用 idea 搭建 Hadoop 源码阅读环境.(默认已安装好 Java.Maven 环境) 一.搭建源码阅读环境 一)idea 导入 hadoop 工程 从 github 上拉取代码 ...

  6. 详细讲解Hadoop源码阅读工程(以hadoop-2.6.0-src.tar.gz和hadoop-2.6.0-cdh5.4.5-src.tar.gz为代表)

    首先,说的是,本人到现在为止,已经玩过.                   对于,这样的软件,博友,可以去看我博客的相关博文.在此,不一一赘述! Eclipse *版本 Eclipse *下载 Jd ...

  7. Mac搭建Hadoop源码阅读环境

    1.本次Hadoop源码阅读环境使用的阅读工具是idea,Hadoop版本是2.7.3.需要安装的工具包括idea.jdk.maven.protobuf等 2.jdk,使用的版本是1.8版,在jdk官 ...

  8. Rpc框架dubbo-client(v2.6.3) 源码阅读(二)

    接上一篇 dubbo-server 之后,再来看一下 dubbo-client 是如何工作的. dubbo提供者服务示例, 其结构是这样的!dubbo://192.168.11.6:20880/com ...

  9. Hadoop源码阅读环境搭建(IDEA)

    拿到一份Hadoop源码之后,经常关注的两件事情就是 1.怎么阅读?涉及IDEA和Eclipse工程搭建.IDEA搭建,选择源码,逐步导入即可:Eclipse可以选择后台生成工程,也可以选择IDE导入 ...

随机推荐

  1. EF中使用语句 或存储过程 查询(转)

    EF中使用语句 或存储过程 查询 1.无参数查询 var model = db.Database.SqlQuery("select* from UserInfoes ").ToLi ...

  2. UIWebView和UIActivityIndicatorView的结合使用

    环境:Xcode6.1 UIWebView是iOS开发中常用的一个控件,是内置的浏览器控件,我们可以用它来浏览网页,加载文档等.这篇文件将结合UIActivityIndicatorView控件制作一个 ...

  3. 跳转界面方法 (runtime实用篇一)

    在开发项目中,会有这样变态的需求: 推送:根据服务端推送过来的数据规则,跳转到对应的控制器 feeds列表:不同类似的cell,可能跳转不同的控制器(嘘!产品经理是这样要求:我也不确定会跳转哪个界面哦 ...

  4. struts2类型转换中的错误处理

    由于类型转换过程中有可能出现原始参数无法转换为目标类型的错误,所以struts2提供了类型转换中的异常处理机制. 在struts2的默认配置文件struts-default.xml中有如下一段配置代码 ...

  5. struts2标签学习笔记(一)

    struts2所有标签都定义在一个s标签库里.虽然struts2把所有的标签都定义在URI为"/struts-tags"空间下,但依然可以对struts2标签进行简单的分类. 1. ...

  6. requirejs实践二 加载其它JavaScript与运行

    上一篇中介绍了requirejs加载JavaScript文件,在这一篇中介绍加载JavaScript后执行代码 这次是test2.html文件, <!DOCTYPE html> <h ...

  7. jquery 文本框失去焦点显示提示信息&&单击置空文本框

    1.<textarea rows="4" placeholder="请输入提醒内容"></textarea> 2. /** * @par ...

  8. Codevs 1814 最长链

    1814 最长链 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题目描述 Description 现给出一棵N个结点二叉树,问这棵二叉树中最长链的长度为多少, ...

  9. MateSublg

    MateSublg 说明 使用MetaWeblog的方式提交文章,并自动上传图片. 本插件的官方地址:MateSublg – Sollyu博客 本插件的开源地址:sollyu / MetaSubolg ...

  10. jquery ajax post, get, javascript ajax post, get 处理

    ajax 创建 XMLHttp 对象IE7 以上的版本都支持 XMLHttpRequestIE7 以下的用 ActiveXObject async:true,  // 当false 时,当执行完这个才 ...