您的位置:首页 > 运维架构 > Apache

Hadoop-进程端通信org.apache.hadoop.ipc-server端解析<转>

2013-11-19 15:30 711 查看
1. nio的reactor模式




具体的处理方式:
· 1.一个线程来处理所有连接(使用一个Selector)
· 2.一组线程来读取已经建立连接的数据(多个Selector,这里的线程数一般和cpu的核数相当);
· 3.一个线程池(这个线程池大小可以根据业务需求进行设置)
· 4.一个线程处理所有的连接的数据的写操作(一个Selector)


2. 简明流程图




3. RPC Server主要流程
RPC Server作为服务提供者由两个部分组成:接收Call调用和处理Call调用。




接收Call调用负责接收来自RPC Client的调用请求,编码成Call对象后放入到Call队列中。这一过程由Listener线程完成。具体步骤:

l Listener线程监视RPC Client发送过来的数据。

l 当有数据可以接收时,调用Connection的readAndProcess方法。

l Connection边接收边对数据进行处理,如果接收到一个完整的Call包,则构建一个Call对象PUSH到Call队列中,由Handler线程来处理Call队列中的所有Call。



处理Call调用负责处理Call队列中的每个调用请求,由Handler线程完成:

l Handler线程监听Call队列,如果Call队列非空,按FIFO规则从Call队列取出Call。

l 将Call交给RPC.Server处理。

l 借助JDK提供的Method,完成对目标方法的调用,目标方法由具体的业务逻辑实现。

l 返回响应。Server.Handler按照异步非阻塞的方式向RPC Client发送响应,如果有未发送出的数据,则交由Server.Responder来完成。


4. server类的结构
0)抽象类
这里的Server类是个抽象类,唯一抽象的地方,就是
public abstract Writable call(Writable param, long receiveTime) throws IOException;
由RPC.server来实现
1)Call
用以存储客户端发来的请求,这个请求会放入一个BlockQueue中;
2)Listener
监听类,用以监听客户端发来的请求。同时Listener下面还有一个静态类,Listener.Reader,当监听器监听到用户请求,便用让Reader读取用户请求。
Listener主要负责Socket的监听以及Connection的建立,同时监控ClientSocket的数据可读事件,通知Connection进行processData,收到完成请求包以后,封装为一个Call对象(包含Connection对象,从网络流中读取的参数信息,调用方法信息),将其放入队列
3)Responder
响应RPC请求类,请求处理完毕,由Responder发送给请求客户端。
它不断地检查响应队列中是否有调用信息,如果有的话,就把调用的结果返回给客户端
4)Connection
连接类,真正的客户端请求读取逻辑在这个类中。
Connection,代表与Client端的连接,读取客户端的call并放到一个阻塞队列中,Handler负责从这个队列中读取数据并处理
5)Handler
请求(blockQueueCall)处理类,会循环阻塞读取callQueue中的call对象,并对其进行操作。
真正做事的实体。它从调用队列中获取调用信息,然后反射调用真正的对象,得到结果,然后再把此次调用放到响应队列(response queue)里

5. Server的启动,运行
5.1.Namenode的getserver实例使用

privatevoid initialize(Configuration conf) throws IOException {     this.serviceRpcServer = RPC.getServer(this, dnSocketAddr.getHostName(), dnSocketAddr.getPort(), serviceHandlerCount, false, conf, namesystem.getDelegationTokenSecretManager());     serviceRpcServer.start(); }



5.2.Start服务启动

publicsynchronizedvoid start(){    responder.start();    listener.start();    handlers =new Handler[handlerCount];    for (int i =0; i < handlerCount; i++)    {        handlers[i] =new Handler(i);        handlers[i].start();    }}


responder、listener、handlers三个对象的线程均阻塞了,前两个阻塞在selector.select()方法上,handler阻塞在callQueue.take()方法,都在等待客户端请求。Responder设置了超时时间,为15分钟。而listener还开启了Reader线程,该线程也阻塞了。


5.3. Listener线程做的工作
Listener监听到请求,获得所有请求的SelectionKey,执行doAccept(key)方法,该方法将所有的连接对象放入list中,并将connection对象与key绑定,以供reader使用。初始化玩所有的conne对象之后,就可以激活Reader线程了.
Reader的run方法和Listener基本一致,也是获得所有的SelectionKey,再执行doRead(key)方法。该方法获得key中绑定的connection,并执行conection的readAndProcess()方法
简明调用函数过程:
Listener:: run-> Listener:: doAccept( 激活Reader线程)->>Reader:: doRead->>connection:: readAndProcess->> connection::processOneRpc->>connection:: processData


privatevoid processData(byte[] buf) throws  IOException, InterruptedException{    DataInputStream dis =new DataInputStream(new ByteArrayInputStream(buf));    int id = dis.readInt();      // 尝试读取id    Writable param = ReflectionUtils.newInstance(paramClass, conf);//读取参数    param.readFields(dis);//这个就是client传递过来的Invocation,包含了函数名和参数    Call call =new Call(id, param, this);  //封装成call    callQueue.put(call);   // 将call存入callQueue    incRpcCount();  // 增加rpc请求的计数}



5.4. Handler线程做的工作
Handler线程的run函数

while (running){    try    {        final Call call = callQueue.take(); //弹出call,可能会阻塞        //调用ipc.Server类中的call()方法,但该call()方法是抽象方法,具体实现在RPC.Server类中        value = call(call.connection.protocol, call.param, call.timestamp);        synchronized (call.connection.responseQueue)        {            setupResponse(buf, call, (error ==null) ? Status.SUCCESS : Status.ERROR, value, errorClass, error);            //给客户端响应请求            responder.doRespond(call);        }    }}


关于call函数的调用,稍后分析

5.5.Responder线程做的工作

void doRespond(Call call) throws IOException{    synchronized (call.connection.responseQueue)    {        call.connection.responseQueue.addLast(call);//放到队列里面去if (call.connection.responseQueue.size() ==1)        {            processResponse(call.connection.responseQueue, true);        }    }}


简明调用结构为:
Responder::run->>doAsyncWrite->>processResponse


5.6.最后来看server.call函数是怎么执行的

public Writable call(Class<?> protocol, Writable param, long receivedTime) throws IOException{    try    {        Invocation call = (Invocation) param;        Method method = protocol.getMethod(call.getMethodName(), call.getParameterClasses());//获取client端调用的函数        Object value = method.invoke(instance, call.getParameters());//instance即启动服务的对象,也即实现protocol的对象returnnew ObjectWritable(method.getReturnType(), value);//将结果序列化    } catch (InvocationTargetException e)    {    } catch (Throwable e)    {    }}


6. 用户可以做的操作

1. Reader数量

正常情况下,一个客户端关联一个Reader,如果有很多客户端(client或DataNode),那么就可以相应增加这个配置

参数:ipc.server.read.threadpool.size,默认是1,需要注意的是,这个配置参数是0.21版本的,不同版本的参数可能不一样

2. Handler数量

对于这种做事的线程,不好把握度,到底多少才是合适。

参数:dfs.namenode.handler.count, 这里是以NameNode举例

3. 客户端重试次数

客户端在调用时发生异常,重试是无可厚非。但如果对实时性有要求,那么这里的重试就有考量。Fackbook在做的Realtime分析就有提到RPC的重试是需要修改的

参数:ipc.client.connect.max.retries,默认是10

4. tcp no delay

不建议对它有什么设置。如果我们对整个调用的过程中数据量大小及网络环境不清楚的话,就是设置了也不知道它是否有作用。

参数:ipc.client.tcpnodelay,默认是false


7. 时序图




8. 类图




9.参考
http://blog.csdn.net/sxf_824/article/details/4842153
http://www.wikieno.com/2012/02/hadoop-ipc-server/
http://caibinbupt.iteye.com/blog/281281
http://www.tbdata.org/archives/1413
http://blog.csdn.net/shirdrn/article/details/4598295
http://lidejiasw.wordpress.com/2011/05/07/hadoop-rpc%E5%88%86%E6%9E%90/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐