Hadoop异步rpc通信机制--org.apache.hadoop.ipc.Server
来源:互联网 发布:淘宝门头图片素材 编辑:程序博客网 时间:2024/05/23 23:20
Java NOI中selector可视为一个观察者,只要我们把要观察的SocketChannel告诉Selector(注册的方式),我们就可以做其余的事情,等到已告知Channel上有事情发生时,Selector会通知我们,传回一组SelectionKey,我们读取这些Key,就可以获得Channel上的数据了。
Client端的底层通信直接采用了阻塞式IO编程,Server是采用Java NIO机制进行RPC通信:
java NIO参考资料:
http://www.iteye.com/topic/834447
http://weixiaolu.iteye.com/blog/1479656
=========================================================================================================================
Server是一个abstract类,抽象之处在call方法中,RPC.Server是ipc.Server的实现类,RPC.Server的构造函数调用了ipc.Server类的构造函数的,Namenode在初始化时调用RPC.getServer方法初始化了RPC.Server:
public static Server getServer(final Object instance, final String bindAddress, final int port, final int numHandlers, final boolean verbose, Configuration conf, SecretManager<? extends TokenIdentifier> secretManager) throws IOException { return new Server(instance, conf, bindAddress, port, numHandlers, verbose, secretManager); }
Server.Call是一个请求类,类似Client.Call,只是添加了Call的时间戳机制:
private static class Call { private int id; // 请求id private Writable param; // 请求的参数 private Connection connection; // 和Client一样,表示一个C/S间的连接 private long timestamp; // 时间戳 private ByteBuffer response; // server对此次请求的响应结果...}
知道了Client.Connection后,显然Server.Connection就是Server到Client的连接。Server.Connection内保存了Client的地址,用于灾难恢复。Server.Connection通过调用readAndProcess对Client进行一些操作:版本校验,读数据头processHeader(获取通信协议protocol,根据头部的ugi信息创建user对象)以及读数据processData(获取Client发送过来的Call.id和params,根据二者建立一个请求call,并将请求call入队callQueue)【readAndProcess方法是在Listener.doRead时调用,此时监听器监听到新连接的读数据事件】。
public int readAndProcess() throws IOException, InterruptedException { //先对connection进行版本校验,校验成功后读取Header头部信息(得到客户端所用的协议和客户端的标识user) //,接着读取数据(Call.id和参数params,其中params),然后建立一个Call while (true) { /* Read at most one RPC. If the header is not read completely yet * then iterate until we read first RPC or until there is no data left. */ int count = -1; if (dataLengthBuffer.remaining() > 0) { count = channelRead(channel, dataLengthBuffer); if (count < 0 || dataLengthBuffer.remaining() > 0) return count; } if (!versionRead) {//尚未版本验证 //Every connection is expected to send the header. ByteBuffer versionBuffer = ByteBuffer.allocate(1); count = channelRead(channel, versionBuffer); if (count <= 0) { return count; } int version = versionBuffer.get(0); //要读取BufferByte前要先flip下 dataLengthBuffer.flip();//.flip();一定得有,如果没有,就是从最后开始读取的,当然读出来的都是byte=0时候的字符。 //通过buffer.flip();这个语句,就能把buffer的当前位置更改为buffer缓冲区的第一个位置 if (!HEADER.equals(dataLengthBuffer) || version != CURRENT_VERSION) { //Warning is ok since this is not supposed to happen. LOG.warn("Incorrect header or version mismatch from " + hostAddress + ":" + remotePort + " got version " + version + " expected version " + CURRENT_VERSION); return -1; } dataLengthBuffer.clear();//清除内容 versionRead = true;//验证版本了 continue; } if (data == null) {//分配新的data dataLengthBuffer.flip(); dataLength = dataLengthBuffer.getInt(); if (dataLength == Client.PING_CALL_ID) { dataLengthBuffer.clear(); return 0; //ping message } data = ByteBuffer.allocate(dataLength); incRpcCount(); // Increment the rpc count } count = channelRead(channel, data);//读数据 if (data.remaining() == 0) {//因为分配刚好的dataLength,所有正常情况会无剩余空间 dataLengthBuffer.clear(); data.flip(); if (headerRead) {//头部已经处理过了 processData();//处理数据 data = null; return count; } else { processHeader(); headerRead = true; data = null; // Authorize the connection try {//尝试授权 authorize(user, header);//user已创建成功 if (LOG.isDebugEnabled()) { LOG.debug("Successfully authorized " + header); } } catch (AuthorizationException ae) {//授权失败 authFailedCall.connection = this; setupResponse(authFailedResponse, authFailedCall, Status.FATAL, null, ae.getClass().getName(), ae.getMessage()); responder.doRespond(authFailedCall); // Close this connection return -1; } continue; } } return count; } }
Server.Listener是一个用于监听新的连接的线程。该线程在run里面循环处理在channel上的事件。当事件为新连接(即key.isAcceptable()=true)时,调用doAccpet接收请求(为channel注册OP_READ监听读,然后根据注册得到的SelectionKey和channel创建一个新的Server.Connection,然后将这个连接加入到连接集合connectionList内【handler进程是从这个list上取出数据进行处理的】);当事件为读事件(即key.isReadable()=true)调用doRead读取connection上的数据(其实是调用connection.readAndProcess进行读);当没有监听到事件时,会调用cleanupConnections(false)清理掉长时间不响应的连接。
public void run() { LOG.info(getName() + ": starting"); SERVER.set(Server.this); while (running) { SelectionKey key = null; try { /*Selector通过select方法通知我们我们感兴趣的事件发生了。 nKeys = selector.select(); 如果有我们注册的事情发生了,它的传回值就会大于0*/ selector.select(); Iterator<SelectionKey> iter = selector.selectedKeys().iterator(); while (iter.hasNext()) { key = iter.next(); iter.remove(); try { if (key.isValid()) {//此键是否有效 if (key.isAcceptable())//通道是否已准备好接受新的套接字连接。 doAccept(key);//接受一个新连接,为通道注册读事件,建立新连接并将新连接加入connectionList else if (key.isReadable())//测试此键的通道是否已准备好进行读取。 doRead(key);//调用Connection.readAndProcess读取call的数据,建立新call } } catch (IOException e) { } key = null; } } catch (OutOfMemoryError e) { // we can run out of memory if we have too many threads // log the event and sleep for a minute and give // some thread(s) a chance to finish LOG.warn("Out of Memory in server select", e); closeCurrentConnection(key, e); cleanupConnections(true); try { Thread.sleep(60000); } catch (Exception ie) {} } catch (InterruptedException e) { if (running) { // unexpected -- log it LOG.info(getName() + " caught: " + StringUtils.stringifyException(e)); } } catch (Exception e) { closeCurrentConnection(key, e); } cleanupConnections(false); } LOG.info("Stopping " + this.getName()); synchronized (this) { try {//善后工作 acceptChannel.close(); selector.close(); } catch (IOException e) { } selector= null; acceptChannel= null; // clean up all connections while (!connectionList.isEmpty()) { closeConnection(connectionList.remove(0)); } } }
Server.Handle"线程主要的任务是用于处理请求。考虑到可能同时有多个请求要处理,所以Handle一般不唯一。Handle线程循环从callQueue中取出一个call,根据调用Server.Call关联的连接Server.Connection所对应的用户Subject,来执行IPC调用过程。然后将处理结果序列化到call并加入到call对应的connection中的responseQueue【responseQueue也是一个Call集合,每个connection都有一个responseQueue,保存了给对应的client的响应信息】:
/** Handles queued calls . */ private class Handler extends Thread { public Handler(int instanceNumber) { this.setDaemon(true); this.setName("IPC Server handler "+ instanceNumber + " on " + port); } @Override public void run() { LOG.info(getName() + ": starting"); SERVER.set(Server.this);// 设置当前处理线程的本地变量的拷贝 ByteArrayOutputStream buf = new ByteArrayOutputStream(10240); while (running) { try { final Call call = callQueue.take(); // pop the queue; maybe blocked here if (LOG.isDebugEnabled()) LOG.debug(getName() + ": has #" + call.id + " from " + call.connection); String errorClass = null; String error = null; Writable value = null; CurCall.set(call);// 设置当前线程本地变量拷贝的值为出队得到的一个call调用实例 try { // Make the call as the user via Subject.doAs, thus associating // the call with the Subject // // 根据调用Server.Call关联的连接Server.Connection所对应的用户Subject,来执行IPC调用过程 value = Subject.doAs(call.connection.user, new PrivilegedExceptionAction<Writable>() { @Override public Writable run() throws Exception { // make the call return call(call.connection.protocol, call.param, call.timestamp); } } ); } catch (PrivilegedActionException pae) { Exception e = pae.getException(); LOG.info(getName()+", call "+call+": error: " + e, e); errorClass = e.getClass().getName(); error = StringUtils.stringifyException(e); } catch (Throwable e) { LOG.info(getName()+", call "+call+": error: " + e, e); errorClass = e.getClass().getName(); error = StringUtils.stringifyException(e); } CurCall.set(null); // 当前Handler线程处理完成一个调用call,回收当前线程的局部变量拷贝 // 处理当前获取到的调用的响应 setupResponse(buf, call, (error == null) ? Status.SUCCESS : Status.ERROR, value, errorClass, error);//将处理的结果序列化到call中 responder.doRespond(call); // 将调用call加入到响应队列中,等待客户端读取响应信息 } catch (InterruptedException e) { if (running) { // unexpected -- log it LOG.info(getName() + " caught: " + StringUtils.stringifyException(e)); } } catch (Exception e) { LOG.info(getName() + " caught: " + StringUtils.stringifyException(e)); } } LOG.info(getName() + ": exiting"); } }
void doRespond(Call call) throws IOException { synchronized (call.connection.responseQueue) { call.connection.responseQueue.addLast(call);//将call加入到call对应的connection对应的responseQueue中 if (call.connection.responseQueue.size() == 1) {//当入队前队列为空时,则调用processResponse将为call.connection对应的通道添加写事件,同时将call附加到通道上。这使得在Responder.run调用doAsyncWrite时可以通过选择键获得附加对象call。 processResponse(call.connection.responseQueue, true); } } }
Responder线程任务是将响应队列中的响应写回客户端。
在run内定期doPurge,根据Call.timestamp判断该请求是否长时间未响应,如果是,关闭掉对应的连接。
/** * 调用异步写doAsyncWrite写回响应消息,并周期性doPurge清理不响应的连接 */ public void run() { LOG.info(getName() + ": starting"); SERVER.set(Server.this);//保存server long lastPurgeTime = 0; // last check for old calls. while (running) { try { waitPending(); // If a channel is being registered, wait. writeSelector.select(PURGE_INTERVAL);//选择一组键,其相应的通道已为 I/O 操作准备就绪。 最多阻塞 timeout 毫秒 Iterator<SelectionKey> iter = writeSelector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); iter.remove(); try { if (key.isValid() && key.isWritable()) {//测试此键的通道是否已准备好进行写入。 doAsyncWrite(key);//调用异步写 } } catch (IOException e) { LOG.info(getName() + ": doAsyncWrite threw exception " + e); } } long now = System.currentTimeMillis(); if (now < lastPurgeTime + PURGE_INTERVAL) { continue; } lastPurgeTime = now; // // If there were some calls that have not been sent out for a // long time, discard them. // LOG.debug("Checking for old call responses."); ArrayList<Call> calls; // get the list of channels from list of keys. synchronized (writeSelector.keys()) {//找出正常的call构成calls calls = new ArrayList<Call>(writeSelector.keys().size()); iter = writeSelector.keys().iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); Call call = (Call)key.attachment(); if (call != null && key.channel() == call.connection.channel) { calls.add(call); } } } //关掉正常calls中长久不响应的连接 for(Call call : calls) { try { doPurge(call, now); } catch (IOException e) { LOG.warn("Error in purging old calls " + e); } } } catch (OutOfMemoryError e) { // // we can run out of memory if we have too many threads // log the event and sleep for a minute and give // some thread(s) a chance to finish // LOG.warn("Out of Memory in server select", e); try { Thread.sleep(60000); } catch (Exception ie) {} } catch (Exception e) { LOG.warn("Exception in Responder " + StringUtils.stringifyException(e)); } } LOG.info("Stopping " + this.getName()); }
/**调用processResponse进行异步写响应消息 * */ private void doAsyncWrite(SelectionKey key) throws IOException { Call call = (Call)key.attachment();//获取当前的附加对象call。在processResponse内将Call作为附件注册到写通道 if (call == null) {//只有当Handler线程有注册写事件并将call附加到通道上,call才不为null return; } //根据得到的附加对象call,将call所在的connection的responseQueue上的response写回给客户端 if (key.channel() != call.connection.channel) { throw new IOException("doAsyncWrite: bad channel"); } synchronized(call.connection.responseQueue) { if (processResponse(call.connection.responseQueue, false)) {// 调用processResponse处理与调用关联的响应数据 try { key.interestOps(0);//将此键的 interest 集合设置为给定值0。 } catch (CancelledKeyException e) { /* The Listener/reader might have closed the socket. * We don't explicitly cancel the key, so not sure if this will * ever fire. * This warning could be removed. */ LOG.warn("Exception while changing ops : " + e); } } } }
//清理工作 // Remove calls that have been pending in the responseQueue // for a long time. // private void doPurge(Call call, long now) throws IOException { LinkedList<Call> responseQueue = call.connection.responseQueue;//一个connection对应多个请求, //也就需要多个响应 synchronized (responseQueue) { Iterator<Call> iter = responseQueue.listIterator(0); while (iter.hasNext()) {//如果发现有长时间未响应的请求,关闭这个connection call = iter.next(); if (now > call.timestamp + PURGE_INTERVAL) { closeConnection(call.connection); break; } } } }
最后,看一下几个线程的开启:
public synchronized void start() throws IOException { responder.start(); listener.start(); handlers = new Handler[handlerCount]; for (int i = 0; i < handlerCount; i++) {//可以有多个处理线程 handlers[i] = new Handler(i); handlers[i].start(); } }stop()操作类似,不再赘述。
=========================================================================================================================
【总结】
Server采用Java NIO非阻塞技术。
Listener用于接收客户端的连接,把连接加入到connectionList内,同时调用Connection的方法进行版本校验、建立Call并将Call加入到callQueue中。
Handler是处理线程,用于从callQueue中取出请求进行处理,调用Respond.doRespond将call加入到responseQueue。
Respond是响应线程,用于将responseQueue上的响应写给对应的Client,同时doPurge清理掉长期不响应的连接。
- Hadoop异步rpc通信机制--org.apache.hadoop.ipc.Server
- Hadoop异步rpc通信机制--org.apache.hadoop.ipc.Client
- Hadoop-org.apache.hadoop.ipc-ipc进程之间通信总体结构和RPC<转>
- Hadoop异步RPC通信机制
- hadoop datanode 问题 INFO org.apache.hadoop.ipc.RPC: Server at /:9000 not available yet, Zzzzz..
- hadoop datanode 问题 INFO org.apache.hadoop.ipc.RPC: Server at /:9000 not available yet, Zzzzz..
- hadoop IPC/RPC 机制
- Hadoop-进程端通信org.apache.hadoop.ipc-server端解析<转>
- INFO org.apache.hadoop.ipc.RPC: Server at /:9000 not available yet, Zzzzz
- org.apache.hadoop.ipc.RPC: Server at master/ip:port not available yet
- hadoop数据节点通信异常【启动hadoop集群遇到错误org.apache.hadoop.ipc.Client: Retrying connect to server】
- org.apache.hadoop.ipc.ProtobufEngine
- org.apache.hadoop.ipc.Client: Retrying connect to server
- org.apache.hadoop.ipc.Client: Retrying connect to server
- hadoop异常:org.apache.hadoop.ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8031
- hadoop遇到的问题: org.apache.hadoop.ipc.Client: Retrying connect to server异常的解决
- hadoop异常:虚拟机上搭建分布式集群org.apache.hadoop.ipc.Client: Retrying connect to server
- hadoop-0.20.2 & hbase-0.90.1 集群启动错误“org.apache.hadoop.ipc.RPC$VersionMismatch: Protocol org.apache.hadoop.hdfs.protocol.ClientP
- Android Setting中添加是否有屏幕锁
- asp.net mvc数据标记国际化
- UItableView Crash--Assertion failure
- .Net多线程总结
- 字符串格式日期转成日期格式
- Hadoop异步rpc通信机制--org.apache.hadoop.ipc.Server
- SP运营攻略
- 积分图像
- vb.net入门——ComboBox 控件的使用
- [调试相关]android log类别
- CentOS 6.3安装配置JDK 1.6 和 Tomcat 7
- Android学习(6)-Android用户界面之菜单
- Do you need to disable swap and how to configure swappiness
- 使用 C# 的 ArrayList