HDFS1.0源代码解析—Hadoop的RPC机制之Server端解析

11 篇文章 0 订阅
10 篇文章 0 订阅

上一篇博客中结合DN的应用简单的介绍了一下Hadoop RPC中client端的基本执行流程,算是给大家阅读源代码的一点提示吧。本文将对RPC的Server端的机制做一下简单的分析,但愿对大家的理解有所帮助,也算是对自己工作的一点总结。

按照惯例先来看一下Server类主要的组成部分Listener、Handler、Responser,通过名字很容易看出Server功能由三部分构成,负责获取用户请求的Listener,处理请求的Handler和响应用户请求的Responser。

首先来看下程序的入口Server中的start函数

1576   public synchronized void start() {
1577     responder.start();
1578     listener.start();
1579     handlers = new Handler[handlerCount];
1580
1581     for (int i = 0; i < handlerCount; i++) {
1582       handlers[i] = new Handler(i);
1583       handlers[i].start();
1584     }
1585   }
很明显函数分别启动了上边提到的基本的线程,当然我们也可以看出处理用户请求的handler线程的数目比较多,而Listener和Responser都是单线程。


上面提到Listener是一个单线程,有经验的程序猿就会想到这会不会成为RPC的一个瓶颈,因为如果请求的参数很长,那么读取一个call的就会花费比较长的时间,Listener性能可能不能满足高并发的需求。所以有必要提一下Listener两个很重要的辅助类Reader和Connection。

Reader是一个Runnable类,是实际进行监听的线程,Listener初始化时会创建一个readPool,一个readPool其实就是一坨Reader线程的集合,每个Reader线程都包含一个Selector对象(NIO的选择器,具体可以google一下)因为一个Reader线程可能监听多个Connection连接。

 305       readers = new Reader[readThreads];
 306       readPool = Executors.newFixedThreadPool(readThreads);
 307       for (int i = 0; i < readThreads; i++) {
 308         Selector readSelector = Selector.open();
 309         Reader reader = new Reader(readSelector);
 310         readers[i] = reader;
 311         readPool.execute(reader);
 312       }
Connection类每个客户端请求都会生成一个对应的Connection对象,并且该对象 被attach到reader的selector对象上。

按照一般的理解顺序我们先来看一下Listener线程的处理流程:

先看一个Listener的构造函数做了哪些事情

 294     public Listener() throws IOException {
 295       address = new InetSocketAddress(bindAddress, port);
 296       // Create a new server socket and set to non blocking mode
 297       acceptChannel = ServerSocketChannel.open();
 298       acceptChannel.configureBlocking(false);
 299
 300       // Bind the server socket to the local host and port
 301       bind(acceptChannel.socket(), address, backlogLength);
 302       port = acceptChannel.socket().getLocalPort(); //Could be an ephemeral port
 303       // create a selector;
 304       selector= Selector.open();
 305       readers = new Reader[readThreads];
 306       readPool = Executors.newFixedThreadPool(readThreads);
 307       for (int i = 0; i < readThreads; i++) {
 308         Selector readSelector = Selector.open();
 309         Reader reader = new Reader(readSelector);
 310         readers[i] = reader;
 311         readPool.execute(reader);
 312       }
 313
 314       // Register accepts on the server socket with the selector.
 315       acceptChannel.register(selector, SelectionKey.OP_ACCEPT);
 316       this.setName("IPC Server listener on " + port);
 317       this.setDaemon(true);
 318     }
首选是创建一个ServerSocketChannel对象,创建一个选择器,初始化一个readpool,将创建的ServerSocketChannel注册到选择器上,将自己声明为一个后台线程。

接着来看线程的主方法run:

 436       while (running) {
 437         SelectionKey key = null;
 438         try {
 439           selector.select();
 440           Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
 441           while (iter.hasNext()) {
 442             key = iter.next();
 443             iter.remove();
 444             try {
 445               if (key.isValid()) {
 446                 if (key.isAcceptable())
 447                   doAccept(key);
 448               }
不断的检查选择器的状态判断是不是有新的客户端请求,如果有且满足指定的类型调用doAccept方法。我们来看doAccept有哪些操作

 504       while ((channel = server.accept()) != null) {
 505         channel.configureBlocking(false);
 506         channel.socket().setTcpNoDelay(tcpNoDelay);
 507         Reader reader = getReader();
 508         try {
 509           reader.startAdd();
 510           SelectionKey readKey = reader.registerChannel(channel);
 511           c = new Connection(readKey, channel, System.currentTimeMillis());
 512           readKey.attach(c);
 513           synchronized (connectionList) {
 514             connectionList.add(numConnections, c);
 515             numConnections++;
 516           }
首先得到客户端的连接,对该链接进行一个设置,从上文提到的readpool中获取一个reader,唤醒该reader包含的selector,将获取的connection channel注册到相应的selector上。针对这个客户端请求在创建一个connection对象,并将该对象attach到相应的selector上会调用这个connection中方法接受客户端发送的查询信息。

到这里我们可以看出Listener线程的角色只是为readpool分配任务,具体执行任务是每个reader线程。

Reader线程的执行流程:

 330           while (running) {
 331             SelectionKey key = null;
 332             try {
 333               readSelector.select();
 334               while (adding) {
 335                 this.wait(1000);
 336               }
 337
 338               Iterator<SelectionKey> iter = readSelector.selectedKeys().iterator();
 339               while (iter.hasNext()) {
 340                 key = iter.next();
 341                 iter.remove();
 342                 if (key.isValid()) {
 343                   if (key.isReadable()) {
 344                     doRead(key);
 345                   }
 346                 }
 347                 key = null;
 348               }
主要功能就是判断reader监听的channel是否有数据写入,如果有将进行数据的读取。接着来看读取的doRead方法:

 530       Connection c = (Connection)key.attachment();
 531       if (c == null) {
 532         return;
 533       }
 534       c.setLastContact(System.currentTimeMillis());
 535
 536       try {
 537         count = c.readAndProcess();
这里我们看到了前边提到的attach到selector上的connection对象,调用connection对象的readAndProcess方法读取客户端写入的数据。c.readAndProcess的主要功能是读取客户端写入的信息,包括一开始的头信息,后便RPC调用的函数和参数的信息。接受完数据之后调用方法processOneRpc进行处理,核心代码如下:

1279     private void processOneRpc(byte[] buf) throws IOException,
1280         InterruptedException {
1281       if (headerRead) {
1282         processData(buf);
1283       } else {
1284         processHeader(buf);
1285         headerRead = true;
首先判断头信息是否已经接收,如果接受通过processData处理获取的RPC调用的函数和参数。processData的具体处理流程如下:

1294     private void processData(byte[] buf) throws  IOException, InterruptedException {
1295       DataInputStream dis =
1296         new DataInputStream(new ByteArrayInputStream(buf));
1297       int id = dis.readInt();                    // try to read an id
1298
1299       if (LOG.isDebugEnabled())
1300         LOG.debug(" got #" + id);
1301
1302       Writable param = ReflectionUtils.newInstance(paramClass, conf);//read param
1303       param.readFields(dis);
1304
1305       Call call = new Call(id, param, this);
1306       callQueue.put(call);              // queue the call; maybe blocked here
1307       incRpcCount();  // Increment the rpc count
1308     }
首选获取call的id(上边blog中提过传递的调用信息的具体结构),接受函数和参数信息存储在param中,同样创建一个Call对象,将整个Call对象添加到 BlockingQueue<Call> callQueue; 这个队列是Listener和Handerler交互的区域,Listener不断将请求添加到这个Queue中,handler不断检查这个Queue是否有元素,有就进行处理。

到此Listener部分的工作介绍完毕,总结一下就是Listener主要读取客户端的请求信息,将信息存储到一个Queue中,供Handler处理使用。

开始Handler线程的介绍:

1362       while (running) {
1363         try {
1364           final Call call = callQueue.take(); // pop the queue; maybe blocked here

1379               value = call(call.connection.protocol, call.param,
1380                            call.timestamp);

1406             setupResponse(buf, call,
1407                         (error == null) ? Status.SUCCESS : Status.ERROR,
1408                         value, errorClass, error);
1416             responder.doRespond(call);

正如前面介绍的Hander首先从callQueue获取一个call请求,在调用Server的call方法具体执行call指明的函数和相应的参数。这里需要说明的一点是这个call方法具体实现在RPC.Server中,Server其实是一个abstract类而RPC.Server是Server的一个子类,这就再一次显示了RPC类作为对外交互的接口类的作用。

setupResponse函数的主要作用就是将执行结果的Status添加到call对象的response中,要将查询执行结果的状态通知客户端,客户端会据此进行相应的动作。最后调用的Responser的方法。

 800     void doRespond(Call call) throws IOException {
 801       synchronized (call.connection.responseQueue) {
 802         call.connection.responseQueue.addLast(call);
 803         if (call.connection.responseQueue.size() == 1) {
 804           processResponse(call.connection.responseQueue, true);
 805         }
 806       }
 807     }
主要就是将返回的结果添加到call.connection.responseQueue中,这是handler与respr交互的区域,handler负责把结果放进去,responser负责把结果返回客户端。这里需要解释下call.connection.responseQueue这个看上去很诡异的变量名,因为每个connection对象对应一个客户端请求,一个客户端可能发送n多请求,所以每个connection需要维护自己的Queue,而每个查询被封装成call对象,每个call对象记录了自己属于哪个connection,所以就出现了这样一个比较奇葩的变量名。

Responser线程介绍:

首先还是Responser的主函数run方法:

 607           while (iter.hasNext()) {
 608             SelectionKey key = iter.next();
 609             iter.remove();
 610             try {
 611               if (key.isValid() && key.isWritable()) {
 612                   doAsyncWrite(key);
 613               }
其中主要的工作是在doAsyncWrite中完成。其中大家可能注意到有一个writeSelector,这个selector的作用就是监听客户端连接是否可以写入数据了,具体绑定是在processResponse方法中。那么一开始绑定的调用在哪呢,我们在看一下上边提到的doRespond方法当call.connection.responseQueue的大小为1时会调用processResponse,也就是说当第一个call查询结果加入时,会唤醒writeSelector,并将对应的channel绑定上。

 767               try {
 768                 // Wakeup the thread blocked on select, only then can the call
 769                 // to channel.register() complete.
 770                 writeSelector.wakeup();
 771                 channel.register(writeSelector, SelectionKey.OP_WRITE, call);
 772               } catch (ClosedChannelException e) {
下面看一下doAsyncWrite方法都完成了那些工作:

 675       synchronized(call.connection.responseQueue) {
 676         if (processResponse(call.connection.responseQueue, false)) {
 677           try {
 678             key.interestOps(0);
 679           } catch (CancelledKeyException e) {
正如函数名字一样这是一个同步操作的过程,主要的实现还是在上边提到的processResponse完成。

最后我们来看一下这个完成工作比较多的processResponse方法。

 718       try {
 719         synchronized (responseQueue) {
 720           //
 721           // If there are no items for this channel, then we are done
 722           //
 723           numElements = responseQueue.size();
 724           if (numElements == 0) {
 725             error = false;
 726             return true;              // no more data for this channel.
 727           }
 728           //
 729           // Extract the first call
 730           //
 731           call = responseQueue.removeFirst();
 732           SocketChannel channel = call.connection.channel;
 733           if (LOG.isDebugEnabled()) {
 734             LOG.debug(getName() + ": responding to #" + call.id + " from " +
 735                       call.connection);
 736           }
 737           //
 738           // Send as much data as we can in the non-blocking fashion
 739           //
 740           int numBytes = channelWrite(channel, call.response);
 741           if (numBytes < 0) {
 742             return true;
 743           }
 744           if (!call.response.hasRemaining()) {
 745             call.connection.decRpcCount();
 746             if (numElements == 1) {    // last call fully processes.
 747               done = true;             // no more data for this channel.
主要的工作还是做一些判断处理,真正写入实在方法channWrite中完成。


到此把RPC框架上的东西算是总结一了一遍,但是RPC机制远没有我介绍的这么简单,里边有很多细节还需要大家仔细思考和研究,我的工作也就是能让大家看代码不至于迷失方向,仅此而已。最后欢迎大家留言讨论交流,谢谢!



  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值