Hadoop-0.20.0源代碼分析(11)

這裏分析一下IPC模型中的Server端的實現。該Server類的實現有點複雜,而且涉及到網絡中字節流緩衝區的操作問題,及其字節數據的反序列化。

Server類

該Server是服務端的抽象實現,定義了一個抽象的IPC服務。 該IPC服務器接收Client發送的參數值,並返回響應值。同時,作爲IPC模型的服務端,它要維護Client端到Server端的一組連接。

首先看Server類定義的幾個屬性:

[java] view plaincopy
  1. private String bindAddress;                     // 服務端綁定的地址  
  2. private int port;                               // 服務端監聽端口  
  3. private int handlerCount;                       // 處理線程的數量  
  4. private Class<? extends Writable> paramClass;   // 調用的參數的類,必須實現Writable序列化接口  
  5. private int maxIdleTime;                        // 當一個客戶端斷開連接後的最大空閒時間   
  6. private int thresholdIdleConnections;           // 可維護的最大連接數量  
  7. int maxConnectionsToNuke;                       // the max number of connections to nuke during a cleanup    
  8. protected RpcMetrics  rpcMetrics;               // 維護RPC統計數據  
  9. private Configuration conf;                     // 配置類實例  
  10.   
  11. private int maxQueueSize;                       // 處理器Handler實例隊列大小   
  12. private int socketSendBufferSize;               // Socket Buffer大小  
  13. private final boolean tcpNoDelay;               // if T then disable Nagle's Algorithm  
  14.   
  15. volatile private boolean running = true;        // Server是否運行  
  16. private BlockingQueue<Call> callQueue;          // 維護調用實例的隊列  
  17.   
  18. private List<Connection> connectionList = Collections.synchronizedList(new LinkedList<Connection>()); // 維護客戶端連接的列表  
  19. private Listener listener = null;               // 監聽Server Socket的線程,爲處理器Handler線程創建任務  
  20. private Responder responder = null;             // 響應客戶端RPC調用的線程,向客戶端調用發送響應信息  
  21. private int numConnections = 0;                 // 連接數量  
  22. private Handler[] handlers = null;              // 處理器Handler線程數組  

一個Server實例的構造基本上基於上面的屬性信息的,構造方法對一個Server實例進行初始化,包括一些靜態信息如綁定地址、維護連接數量、隊列等,還有一些用來處理Server端事務的線程等等。

先對Server類中定義的幾個內部類來分析,這些類都是與Server端一些重要的事務的處理類。然後再分析Server類提供的全部基本操作。

  • Server.Call內部類

該類Server端使用隊列維護的調用實體類,如下所示:

[java] view plaincopy
  1. private static class Call {  
  2.   private int id;                               // 客戶端調用Call的ID  
  3.   private Writable param;                       // 客戶端調用傳遞的參數  
  4.   private Connection connection;                // 到客戶端的連接實例  
  5.   private long timestamp;                       // 向客戶端調用發送響應的時間戳  
  6.   private ByteBuffer response;                  // 向客戶端調用響應的字節緩衝區  
  7.   
  8.   public Call(int id, Writable param, Connection connection) {   
  9.     this.id = id;  
  10.     this.param = param;  
  11.     this.connection = connection;  
  12.     this.timestamp = System.currentTimeMillis();  
  13.     this.response = null;  
  14.   }  
  15.     
  16.   @Override  
  17.   public String toString() {  
  18.     return param.toString() + " from " + connection.toString();  
  19.   }  
  20.   
  21.   public void setResponse(ByteBuffer response) {  
  22.     this.response = response;  
  23.   }  
  24. }  

  • Server.Connection內部類

該類表示服務端一個連接的抽象,主要是讀取從Client發送的調用,並把讀取到的調用Client.Call實例加入到待處理的隊列。

看如何構造一個Server.Connection對象:

[java] view plaincopy
  1. public Connection(SelectionKey key, SocketChannel channel, long lastContact) {  
  2.   this.channel = channel; // Socket通道  
  3.   this.lastContact = lastContact; // 最後連接時間  
  4.   this.data = null;  
  5.   this.dataLengthBuffer = ByteBuffer.allocate(4); //   
  6.   this.socket = channel.socket(); // 獲取到與通道channel關聯的Socket  
  7.   InetAddress addr = socket.getInetAddress(); // 獲取Socket地址  
  8.   if (addr == null) {  
  9.     this.hostAddress = "*Unknown*";  
  10.   } else {  
  11.     this.hostAddress = addr.getHostAddress();  
  12.   }  
  13.   this.remotePort = socket.getPort(); // 獲取到遠程連接的端口號  
  14.   this.responseQueue = new LinkedList<Call>(); // 服務端待處理調用的隊列  
  15.   if (socketSendBufferSize != 0) {  
  16.     try {  
  17.       socket.setSendBufferSize(socketSendBufferSize); // 設置Socket Buffer大小  
  18.     } catch (IOException e) {  
  19.       LOG.warn("Connection: unable to set socket send buffer size to " + socketSendBufferSize);  
  20.     }  
  21.   }  
  22. }   

另外,Server.Connection內部類中還定義瞭如下幾個屬性:

[java] view plaincopy
  1. private boolean versionRead = false// 是否初始化簽名,並讀取了版本信息  
  2. private boolean headerRead = false// 是否讀取了頭信息  
  3. private int dataLength; // 數據長度  
  4. ConnectionHeader header = new ConnectionHeader(); // 連接頭信息  
  5. Class<?> protocol; // 協議類  
  6. Subject user = null// 用戶的Subject信息  

該內部類中,readAndProcess()方法讀取遠程過程調用的數據,從一個Server.Connection的Socket通道中讀取數據,並將調用任務加入到callQueue,轉交給Handler線程去處理。下面看下該方法的實現:

[java] view plaincopy
  1. public int readAndProcess() throws IOException, InterruptedException {  
  2.     while (true) {  
  3.         int count = -1;  
  4.         // 從通道channel中讀取字節,加入到dataLengthBuffer字節緩衝區  
  5.         if (dataLengthBuffer.remaining() > 0) {  
  6.             count = channelRead(channel, dataLengthBuffer); // 如果通道已經達到了流的末尾,會返回-1的  
  7.             if (count < 0 || dataLengthBuffer.remaining() > 0// 讀取不成功,直接返回讀取的字節數(讀取失敗可能返回0或-1)  
  8.                 return count;  
  9.         }  
  10.   
  11.         if (!versionRead) { // 如果版本號信息還沒有讀取  
  12.             ByteBuffer versionBuffer = ByteBuffer.allocate(1);  
  13.             count = channelRead(channel, versionBuffer); // 讀取版本號信息  
  14.             if (count <= 0) { // 沒有從通道channel中讀取到版本號信息,直接返回  
  15.                 return count;  
  16.             }  
  17.             int version = versionBuffer.get(0); // 讀取到了版本號信息,從字節緩衝區中獲取出來  
  18.   
  19.             dataLengthBuffer.flip(); // 反轉dataLengthBuffer緩衝區  
  20.             // 如果讀取到的版本號信息不匹配,返回-1(HEADER = ByteBuffer.wrap("hrpc".getBytes()),CURRENT_VERSION = 3)  
  21.             if (!HEADER.equals(dataLengthBuffer) || version != CURRENT_VERSION) {   
  22.                 //Warning is ok since this is not supposed to happen.  
  23.                 LOG.warn("Incorrect header or version mismatch from "  
  24.                         + hostAddress + ":" + remotePort  
  25.                         + " got version " + version  
  26.                         + " expected version " + CURRENT_VERSION);  
  27.                 return -1;  
  28.             }  
  29.             // 成功讀取到了版本號信息,清空dataLengthBuffer以便重用,同時設置versionRead爲true  
  30.             dataLengthBuffer.clear();  
  31.             versionRead = true;  
  32.             continue;  
  33.         }  
  34.   
  35.         if (data == null) {  
  36.             dataLengthBuffer.flip();   
  37.             dataLength = dataLengthBuffer.getInt(); // 讀取數據長度信息,以便分配data字節緩衝區  
  38.   
  39.             if (dataLength == Client.PING_CALL_ID) { // 如果是Client端的ping調用,不需要處理數據,清空dataLengthBuffer,返回  
  40.                 dataLengthBuffer.clear();  
  41.                 return 0;   
  42.             }  
  43.             data = ByteBuffer.allocate(dataLength); // 分配data數據緩衝區,準備接收調用參數數據  
  44.             incRpcCount(); // 增加RPC調用統計計數  
  45.         }  
  46.   
  47.         count = channelRead(channel, data); // 從通道channel中讀取字節到data字節緩衝區中  
  48.   
  49.         if (data.remaining() == 0) { // 如果data已經如期讀滿  
  50.             dataLengthBuffer.clear(); // 清空dataLengthBuffer  
  51.             data.flip(); // 反轉dat字節緩衝區,準備從data緩衝區讀取數據  
  52.             if (headerRead) { // 如果頭信息已經讀過了,讀取到的一定是RPC調用參數數據  
  53.                 processData(); // 調用:處理讀取到的調用數據,通過反序列化操作從網絡字節流中衝重構調用參數數據對象,並構造Server.Call對象,同時加入callQueue隊列,等待Server.Handler線程進行處理  
  54.                 data = null;  
  55.                 return count; // 處理完成後返回  
  56.             } else { // 如果頭信息未讀  
  57.                 processHeader(); // 讀取版本號後面的連接頭信息  
  58.                 headerRead = true// 設置連接頭信息已經讀取過  
  59.                 data = null// 重置data字節緩衝區,以備下一個連接到來時緩衝字節  
  60.   
  61.                 // 通過調用processHeader()方法,已經將用戶的Subject信息從header中讀取到user中  
  62.                 try {  
  63.                     authorize(user, header); // 爲客戶端到來的連接進行授權  
  64.   
  65.                     if (LOG.isDebugEnabled()) {  
  66.                         LOG.debug("Successfully authorized " + header);  
  67.                     }  
  68.                 } catch (AuthorizationException ae) {  
  69.                     authFailedCall.connection = this;  
  70.                     setupResponse(authFailedResponse, authFailedCall,  
  71.                             Status.FATAL, null,  
  72.                             ae.getClass().getName(), ae.getMessage());  
  73.                     responder.doRespond(authFailedCall);  
  74.   
  75.                     // Close this connection  
  76.                     return -1;  
  77.                 }  
  78.   
  79.                 continue;  
  80.             }  
  81.         }  
  82.         return count;  
  83.     }  
  84. }  

上面方法是接收調用數據的核心方法,實現瞭如何從SocketChannel通道中讀取數據。其中processHeader方法與processData方法已經在上面種詳細分析了,不再多說。

另外,作爲Server.Connection是連接到客戶端的,與客戶端調用進行通信,所以一個連接定義了關閉的操作,關閉的時候需要關閉與客戶端Socket關聯的SocketChannel通道。

  • Server.Listener內部類

該類是繼承自Thread線程類,用來監聽服務器Socket,並未Handler處理器線程創建處理任務。從一個Listener線程類的構造來它需要初始化哪些必要信息:

[java] view plaincopy
  1. /* 
  2.  * 構造一個Listener實例,初始化線程數據  
  3.  */  
  4.   public Listener() throws IOException {  
  5.     address = new InetSocketAddress(bindAddress, port);      // 根據bindAddress和port創建一個Socket地址  
  6.     acceptChannel = ServerSocketChannel.open();               // 創建一個Server Socket通道(ServerSocketChannel)  
  7.     acceptChannel.configureBlocking(false);                   // 設置Server Socket通道爲非阻塞模式  
  8.     bind(acceptChannel.socket(), address, backlogLength);     // 綁定  
  9.     port = acceptChannel.socket().getLocalPort();             // Socket綁定端口  
  10.     selector= Selector.open();                                // 創建一個選擇器(使用選擇器,可以使得指定的通道多路複用)  
  11.     acceptChannel.register(selector, SelectionKey.OP_ACCEPT); // 向通道acceptChannel註冊上述selector選擇器,選擇器的鍵爲Server Socket接受的操作集合  
  12.     this.setName("IPC Server listener on " + port);           // 設置監聽線程名稱  
  13.     this.setDaemon(true);                                     // 設置爲後臺線程  
  14.   }  

該線程類定義的方法如下所示:

[java] view plaincopy
  1. /**  
  2.  * 根據連接的空閒時間來清除connectionList中維護的連接 
  3.  */  
  4. private void cleanupConnections(boolean force);  
  5.   
  6. /**  
  7.  * 根據key獲取到與該key關聯的連接,並關閉它 
  8.  */  
  9. private void closeCurrentConnection(SelectionKey key, Throwable e);  
  10.   
  11. /**  
  12.  * 根據key關聯的Server Socket通道,接收該通道上Client端到來的連接 
  13.  */  
  14. void doAccept(SelectionKey key) throws IOException,  OutOfMemoryError {  
  15.   Connection c = null;  
  16.   ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 獲取到Server Socket 通道  
  17.   for (int i=0; i<10; i++) { // 選擇的該通道最多接受10個連接  
  18.     SocketChannel channel = server.accept(); // Client Socket通道  
  19.     if (channel==nullreturn;  
  20.     channel.configureBlocking(false); // 設置爲非阻塞模式  
  21.     channel.socket().setTcpNoDelay(tcpNoDelay); // 設置TCP連接是否延遲  
  22.     SelectionKey readKey = channel.register(selector, SelectionKey.OP_READ); // 向選擇器selector註冊讀操作集合,返回鍵  
  23.     c = new Connection(readKey, channel, System.currentTimeMillis()); // 創建連接  
  24.     readKey.attach(c); // 使連接實例與註冊到選擇器selector相關的讀操作集合鍵相關聯  
  25.     synchronized (connectionList) {  
  26.       connectionList.add(numConnections, c); // 加入Server端連接維護列表  
  27.       numConnections++; // 修改連接計數  
  28.     }  
  29.     if (LOG.isDebugEnabled())  
  30.       LOG.debug("Server connection from " + c.toString() +  
  31.           "; # active connections: " + numConnections +  
  32.           "; # queued calls: " + callQueue.size());  
  33.   }  
  34. }  

上面方法,是一個收集來自客戶端的連接的實現。下面看一下監聽線程的線程體部分實現:

[java] view plaincopy
  1. @Override  
  2. public void run() {  
  3.     LOG.info(getName() + ": starting");  
  4.     SERVER.set(Server.this); // 設置當前監聽線程本地變量的拷貝  
  5.     while (running) { // 如果服務器正在運行中  
  6.         SelectionKey key = null;  
  7.         try {  
  8.             selector.select(); // 選擇一組key集合,這些選擇的key相關聯的通道已經爲I/O操作做好準備  
  9.             Iterator<SelectionKey> iter = selector.selectedKeys().iterator();  
  10.             while (iter.hasNext()) {  
  11.                 key = iter.next(); // 迭代出一個key  
  12.                 iter.remove();  
  13.                 try {  
  14.                     if (key.isValid()) {  
  15.                         if (key.isAcceptable()) // 如果該key對應的通道已經準備好接收新的Socket連接  
  16.                             doAccept(key); // 調用,接收與該key關聯的通道上的連接  
  17.                         else if (key.isReadable()) // 如果該通道爲讀取數據做好準備  
  18.                             doRead(key); // 從通道讀取數據,主要調用了Server.Connection的readAndProcess方法來讀取數據,並設置該連接的最後連接時間  
  19.                     }  
  20.                 } catch (IOException e) {  
  21.                 }  
  22.                 key = null;  
  23.             }  
  24.         } catch (OutOfMemoryError e) {  
  25.             // we can run out of memory if we have too many threads  
  26.             // log the event and sleep for a minute and give   
  27.             // some thread(s) a chance to finish  
  28.             LOG.warn("Out of Memory in server select", e);  
  29.             closeCurrentConnection(key, e);  
  30.             cleanupConnections(true);  
  31.             try {  
  32.                 Thread.sleep(60000);  
  33.             } catch (Exception ie) {  
  34.             }  
  35.         } catch (InterruptedException e) {  
  36.             if (running) { // unexpected -- log it  
  37.                 LOG.info(getName() + " caught: "  
  38.                         + StringUtils.stringifyException(e));  
  39.             }  
  40.         } catch (Exception e) {  
  41.             closeCurrentConnection(key, e);  
  42.         }  
  43.         cleanupConnections(false);  
  44.     }  
  45.     LOG.info("Stopping " + this.getName());  
  46.   
  47.     // 跳出while循環,即running=false,服務器已經不再運行,需要關閉通道、選擇器、全部連接  
  48.     synchronized (this) {  
  49.         try {  
  50.             acceptChannel.close(); // 關閉通道  
  51.             selector.close(); // 關閉通道選擇器  
  52.         } catch (IOException e) {  
  53.         }  
  54.   
  55.         selector = null;  
  56.         acceptChannel = null;  
  57.   
  58.         // 關閉全部連接  
  59.         while (!connectionList.isEmpty()) {  
  60.             closeConnection(connectionList.remove(0)); // 關閉一個連接  
  61.         }  
  62.     }  
  63. }  

可見,Server.Listener主要負責兩個階段的任務:當服務器運行時,不斷地通過選擇器來選擇繼續的通道,處理基於該選擇的通道上通信;當服務器不再運行以後,需要關閉通道、選擇器、全部鏈接,釋放一切資源。

  • Server.Handler內部類

該類是一個處理線程類,負責處理客戶端的全部調用。

該類的源代碼如下所示:

[java] view plaincopy
  1. private class Handler extends Thread {  
  2.     public Handler(int instanceNumber) {  
  3.         this.setDaemon(true); // 作爲後臺線程運行  
  4.         this.setName("IPC Server handler " + instanceNumber + " on "+ port);  
  5.     }  
  6.   
  7.     @Override  
  8.     public void run() {  
  9.         LOG.info(getName() + ": starting");  
  10.         SERVER.set(Server.this); // 設置當前處理線程的本地變量的拷貝  
  11.         ByteArrayOutputStream buf = new ByteArrayOutputStream(10240); // 存放響應信息的緩衝區  
  12.         while (running) {  
  13.             try {  
  14.                 final Call call = callQueue.take(); // 出隊操作,獲取到一個調用Server.Call call  
  15.   
  16.                 if (LOG.isDebugEnabled())  
  17.                     LOG.debug(getName() + ": has #" + call.id + " from " + call.connection);  
  18.   
  19.                 String errorClass = null;  
  20.                 String error = null;  
  21.                 Writable value = null;  
  22.   
  23.                 CurCall.set(call); // 設置當前線程本地變量拷貝的值爲出隊得到的一個call調用實例  
  24.                 try {  
  25.                     // 根據調用Server.Call關聯的連接Server.Connection,所對應的用戶Subject,來執行IPC調用過程  
  26.                     value = Subject.doAs(call.connection.user,  
  27.                             new PrivilegedExceptionAction<Writable>() {  
  28.                                 @Override  
  29.                                 public Writable run() throws Exception {  
  30.                                     // 執行調用  
  31.                                     return call(call.connection.protocol, call.param, call.timestamp);  
  32.   
  33.                                 }  
  34.                             });  
  35.   
  36.                 } catch (PrivilegedActionException pae) {  
  37.                     Exception e = pae.getException();  
  38.                     LOG.info(getName() + ", call " + call + ": error: " + e, e);  
  39.                     errorClass = e.getClass().getName();  
  40.                     error = StringUtils.stringifyException(e);  
  41.                 } catch (Throwable e) {  
  42.                     LOG.info(getName() + ", call " + call + ": error: " + e, e);  
  43.                     errorClass = e.getClass().getName();  
  44.                     error = StringUtils.stringifyException(e);  
  45.                 }  
  46.                 CurCall.set(null); // 當前Handler線程處理完成一個調用call,回收當前線程的局部變量拷貝  
  47.                 // 處理當前獲取到的調用的響應  
  48.                 setupResponse(buf, call, (error == null) ? Status.SUCCESS : Status.ERROR, value, errorClass, error);  
  49.                 responder.doRespond(call); // 將調用call加入到響應隊列中,等待客戶端讀取響應信息  
  50.             } catch (InterruptedException e) {  
  51.                 if (running) { // unexpected -- log it  
  52.                     LOG.info(getName() + " caught: " + StringUtils.stringifyException(e));  
  53.                 }  
  54.             } catch (Exception e) {  
  55.                 LOG.info(getName() + " caught: " + StringUtils.stringifyException(e));  
  56.             }  
  57.         }  
  58.         LOG.info(getName() + ": exiting");  
  59.     }  
  60.   
  61. }  

該線程主要的任務是:真正地實現了處理來自客戶端的調用,並設置每個相關調用的響應。關於響應的實現,有個具體實現的線程類Server.Responder。

  • Server.Responder內部類

該線程類實現發送RPC響應到客戶端。

我們先對該線程類中方法進行閱讀分析,然後再看線程體的實現過程。

[java] view plaincopy
  1. /**  
  2.  * 處理一個通道上調用的響應數據 
  3.  * 如果一個通道空閒,返回true 
  4.  */  
  5. private boolean processResponse(LinkedList<Call> responseQueue, boolean inHandler) throws IOException {  
  6.     boolean error = true;  
  7.     boolean done = false// 一個通道channel有更多的數據待讀取  
  8.     int numElements = 0;  
  9.     Call call = null;  
  10.     try {  
  11.         synchronized (responseQueue) {   
  12.             // 如果該通道channel空閒,處理響應完成  
  13.             numElements = responseQueue.size();  
  14.             if (numElements == 0) {  
  15.                 error = false;  
  16.                 return true// 完成響應的處理,返回  
  17.             }  
  18.             // 從隊列中取出第一個調用call  
  19.             call = responseQueue.removeFirst();  
  20.             SocketChannel channel = call.connection.channel; // 獲取該調用對應的通道channel  
  21.             if (LOG.isDebugEnabled()) {  
  22.                 LOG.debug(getName() + ": responding to #" + call.id + " from " + call.connection);  
  23.             }  
  24.             // Send as much data as we can in the non-blocking fashion  
  25.             int numBytes = channelWrite(channel, call.response); // 向通道channel中寫入響應信息(響應信息位於call.response字節緩衝區中)  
  26.             if (numBytes < 0) { // 如果寫入字節數爲0,說明已經沒有字節可寫,返回  
  27.                 return true;  
  28.             }  
  29.             if (!call.response.hasRemaining()) { // 如果call.response字節緩衝區中沒有響應字節數據,說明已經全部寫入到相關量的通道中  
  30.                 call.connection.decRpcCount(); // 該調用call對應的RPC連接計數減1  
  31.                 if (numElements == 1) { // 最後一個調用已經處理完成  
  32.                     done = true// 該通道channel沒有更多的數據  
  33.                 } else {  
  34.                     done = false// 否則,還存在尚未處理的調用,要向給通道發送數據  
  35.                 }  
  36.                 if (LOG.isDebugEnabled()) {  
  37.                     LOG.debug(getName() + ": responding to #" + call.id + " from " + call.connection + " Wrote " + numBytes + " bytes.");  
  38.                 }  
  39.             } else { // 如果call.response字節緩衝區中還存在未被寫入通道響應字節數據  
  40.                 call.connection.responseQueue.addFirst(call); // 如果不能夠將全部的響應字節數據寫入到通道中,需要暫時插入到Selector選擇其隊列中  
  41.                 if (inHandler) { // 如果指定:現在就對調用call進行處理(該調用的響應還沒有進行處理)  
  42.                     call.timestamp = System.currentTimeMillis(); // 設置調用時間戳  
  43.                     incPending(); // 增加未被處理響應信息的調用計數  
  44.                     try {  
  45.                         writeSelector.wakeup(); // 喚醒阻塞在該通道writeSelector上的線程  
  46.                         channel.register(writeSelector, SelectionKey.OP_WRITE, call); // 調用call註冊通道writeSelector  
  47.                     } catch (ClosedChannelException e) {  
  48.                         done = true;  
  49.                     } finally {  
  50.                         decPending(); // 經過上面處理,不管在處理過程中正常處理,或是發生通道已關閉異常,最後,都將設置該調用完成,更新計數  
  51.                     }  
  52.                 }  
  53.                 if (LOG.isDebugEnabled()) {  
  54.                     LOG.debug(getName() + ": responding to #" + call.id + " from " + call.connection + " Wrote partial " + numBytes + " bytes.");  
  55.                 }  
  56.             }  
  57.             error = false// 設置出錯標誌:完成  
  58.         }  
  59.     } finally {  
  60.         if (error && call != null) {  
  61.             LOG.warn(getName() + ", call " + call + ": output error");  
  62.             done = true;  // error. no more data for this channel.  
  63.             closeConnection(call.connection);  
  64.         }  
  65.     }  
  66.     return done;  
  67. }  

上面方法主要實現的是,處理響應隊列responseQueue中的全部調用Call,對應的響應數據。關於處理響應的調用隊列,是指類似call.connection.responseQueue的響應隊列,可以理解爲某個通道上調用的集合所對應的待處理響應數據的隊列。

看下面的doRespond方法:

[java] view plaincopy
  1. void doRespond(Call call) throws IOException {  
  2.     synchronized (call.connection.responseQueue) {  
  3.         call.connection.responseQueue.addLast(call); // 將執行完成的調用加入隊列,準備響應客戶端  
  4.         if (call.connection.responseQueue.size() == 1) {  
  5.             processResponse(call.connection.responseQueue, true); // 如果隊列中只有一個調用,直接進行處理  
  6.         }  
  7.     }  
  8. }  

當某個通道上可寫的時候,可以執行異步寫響應數據的操作,實現方法爲:

[java] view plaincopy
  1. private void doAsyncWrite(SelectionKey key) throws IOException {  
  2.     Call call = (Call) key.attachment();  
  3.     if (call == null) {  
  4.         return;  
  5.     }  
  6.     if (key.channel() != call.connection.channel) {  
  7.         throw new IOException("doAsyncWrite: bad channel");  
  8.     }  
  9.   
  10.     synchronized (call.connection.responseQueue) {  
  11.         if (processResponse(call.connection.responseQueue, false)) { // 調用processResponse處理與調用關聯的響應數據  
  12.             try {  
  13.                 key.interestOps(0);  
  14.             } catch (CancelledKeyException e) {  
  15.                 LOG.warn("Exception while changing ops : " + e);  
  16.             }  
  17.         }  
  18.     }  
  19. }  

再看doPurge方法:

[java] view plaincopy
  1. /** 
  2.  * 如果未被處理響應的調用在隊列中滯留超過指定時限,要定時清除掉  
  3.  */  
  4. private void doPurge(Call call, long now) throws IOException {  
  5.     LinkedList<Call> responseQueue = call.connection.responseQueue;  
  6.     synchronized (responseQueue) {  
  7.         Iterator<Call> iter = responseQueue.listIterator(0);  
  8.         while (iter.hasNext()) {  
  9.             call = iter.next();  
  10.             if (now > call.timestamp + PURGE_INTERVAL) {  
  11.                 closeConnection(call.connection);  
  12.                 break;  
  13.             }  
  14.         }  
  15.     }  
  16. }  

最後,看一個Responder線程啓動後,是如何工作的,在線程體run方法中可以看到:

[java] view plaincopy
  1. @Override  
  2. public void run() {  
  3.     LOG.info(getName() + ": starting");  
  4.     SERVER.set(Server.this);  
  5.     long lastPurgeTime = 0// 最後一次清除過期調用的時間  
  6.     while (running) { // 如果服務器處於運行狀態  
  7.         try {  
  8.             waitPending(); // 等待一個通道中,接收到來的調用進行註冊  
  9.             writeSelector.select(PURGE_INTERVAL); // 設置超時時限  
  10.             Iterator<SelectionKey> iter = writeSelector.selectedKeys().iterator();  
  11.             while (iter.hasNext()) { // 迭代選擇器writeSelector選擇的key集合  
  12.                 SelectionKey key = iter.next();  
  13.                 iter.remove();  
  14.                 try {  
  15.                     if (key.isValid() && key.isWritable()) { // 如果合法,並且通道可寫  
  16.                         doAsyncWrite(key); // 執行異步寫操作,向通道中寫入調用執行的響應數據  
  17.                     }  
  18.                 } catch (IOException e) {  
  19.                     LOG.info(getName() + ": doAsyncWrite threw exception " + e);  
  20.                 }  
  21.             }  
  22.             long now = System.currentTimeMillis();  
  23.             if (now < lastPurgeTime + PURGE_INTERVAL) {  
  24.                 continue;  
  25.             }  
  26.             lastPurgeTime = now;  
  27.             // 如果存在一些一直沒有被髮送出去的調用,這是時間限制爲lastPurgeTime + PURGE_INTERVAL  
  28.             // 則這些調用被視爲過期調用,進行清除  
  29.             LOG.debug("Checking for old call responses.");  
  30.             ArrayList<Call> calls;  
  31.             synchronized (writeSelector.keys()) {  
  32.                 calls = new ArrayList<Call>(writeSelector.keys().size());  
  33.                 iter = writeSelector.keys().iterator();  
  34.                 while (iter.hasNext()) {  
  35.                     SelectionKey key = iter.next();  
  36.                     Call call = (Call) key.attachment();  
  37.                     if (call != null&& key.channel() == call.connection.channel) {  
  38.                         calls.add(call);  
  39.                     }  
  40.                 }  
  41.             }  
  42.   
  43.             for (Call call : calls) {  
  44.                 try {  
  45.                     doPurge(call, now); // 執行清除  
  46.                 } catch (IOException e) {  
  47.                     LOG.warn("Error in purging old calls " + e);  
  48.                 }  
  49.             }  
  50.         } catch (OutOfMemoryError e) {  
  51.             LOG.warn("Out of Memory in server select", e);  
  52.             try {  
  53.                 Thread.sleep(60000);  
  54.             } catch (Exception ie) {  
  55.             }  
  56.         } catch (Exception e) {  
  57.             LOG.warn("Exception in Responder " + StringUtils.stringifyException(e));  
  58.         }  
  59.     }  
  60.     LOG.info("Stopping " + this.getName());  
  61. }  

通過線程執行可以看到,調用的相應數據的處理,是在服務器運行過程中處理的,而且分爲兩種情況:

1、一種情況是:如果某些調用超過了指定的時限而一直未被處理,這些調用被視爲過期,服務器不會再爲這些調用處理,而是直接清除掉;

2、另一種情況是:如果所選擇的通道上,已經註冊的調用是合法的,並且通道可寫,會直接將調用的相應數據寫入到通道,等待客戶端讀取。

 

上面實現的Server的內部類,基本上定義了一個Server應該實現的基本操作,下面再看Server類中就比較容易了。

啓動服務器:

[java] view plaincopy
  1. public synchronized void start() throws IOException {  
  2.     responder.start(); // 啓動調用的響應數據處理線程  
  3.     listener.start(); // 啓動監聽線程  
  4.     handlers = new Handler[handlerCount]; // 啓動多個處理器線程  
  5.     for (int i = 0; i < handlerCount; i++) {  
  6.         handlers[i] = new Handler(i);  
  7.         handlers[i].start();  
  8.     }  
  9. }  
  

停止服務器:

[java] view plaincopy
  1. public synchronized void stop() {  
  2.     LOG.info("Stopping server on " + port);  
  3.     running = false;  
  4.     if (handlers != null) { // 先中斷全部處理器線程  
  5.         for (int i = 0; i < handlerCount; i++) {  
  6.             if (handlers[i] != null) {  
  7.                 handlers[i].interrupt();  
  8.             }  
  9.         }  
  10.     }  
  11.     listener.interrupt(); // 終止監聽器線程  
  12.     listener.doStop();   
  13.     responder.interrupt(); // 終止響應數據處理線程  
  14.     notifyAll();  
  15.     if (this.rpcMetrics != null) {  
  16.         this.rpcMetrics.shutdown();  
  17.     }  
  18. }  

通過對Server類的分析,應該能夠了解IPC模型的Server端需要做哪些基本的事情。


發佈了4 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章