Hadoop建立IPC连接和数据读写
建立IPC连接
IPC Client通过调用getConnection获取IPC连接,具体流程图如下:
服务器端的IPC连接代码分散在Listener和Server.Connection中。
Listener.run() 实现了NIO中的选择器循环。如下代码:
//Listener构造函数
public Listener() throws IOException {
address = new InetSocketAddress(bindAddress, port);
// Create a new server socket and set to non blocking mode
acceptChannel = ServerSocketChannel.open();
acceptChannel.configureBlocking(false); // Bind the server socket to the local host and port
bind(acceptChannel.socket(), address, backlogLength);
port = acceptChannel.socket().getLocalPort(); //Could be an ephemeral port
// create a selector;
selector= Selector.open();
readers = new Reader[readThreads];
readPool = Executors.newFixedThreadPool(readThreads);
for (int i = 0; i < readThreads; i++) {
Selector readSelector = Selector.open();
Reader reader = new Reader(readSelector);
readers[i] = reader;
readPool.execute(reader);
}
Listener.run()开启选择器循环,并处理Accept请求,如下:
//Listener运行函数
public void run() {
LOG.info(getName() + ": starting");
SERVER.set(Server.this);
while (running) {
SelectionKey key = null;
try {
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);
}
} 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 (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));
}
}
}
doAccept()中通过server.accpet获取SocketChannel,并获取一个Reader对象,该对象包含一个Selector:readerSelector,通过reader.registerChannel,将SocketChannel注册到readerSelector下.并新建connection对象。
//Do_Accept
void doAccept(SelectionKey key) throws IOException, OutOfMemoryError {
Connection c = null;
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel;
while ((channel = server.accept()) != null) {
channel.configureBlocking(false);
channel.socket().setTcpNoDelay(tcpNoDelay);
Reader reader = getReader();
try {
reader.startAdd();
SelectionKey readKey = reader.registerChannel(channel);
c = new Connection(readKey, channel, System.currentTimeMillis());
readKey.attach(c);
synchronized (connectionList) {
connectionList.add(numConnections, c);
numConnections++;
}
if (LOG.isDebugEnabled())
LOG.debug("Server connection from " + c.toString() +
"; # active connections: " + numConnections +
"; # queued calls: " + callQueue.size());
} finally {
reader.finishAdd();
} }
}
public synchronized SelectionKey registerChannel(SocketChannel channel)
throws IOException {
return channel.register(readSelector, SelectionKey.OP_READ);
}
Listener拥有一个ExecutorService线程池用来运行Reader对象,Reader对象采用轮叫调度(Round Robin Scheduling算法就是以轮叫的方式依次将请求调度不同的服务器),如下:
Reader getReader() {
currentReader = (currentReader + 1) % readers.length;
return readers[currentReader];
}
Reader对象的startAdd方法和finishAdd方法之间来注册channel和新建conncetion。
以下是Reader对象的run方法,可以看出,当Reader对象调用startAdd方法后,线程从上一次的select阻塞中返回,并循环等待,直到finishAdd方法调用后,adding参数由true转false,跳出循环,再次进入select中。
public void run() {
LOG.info("Starting SocketReader");
synchronized (this) {
while (running) {
SelectionKey key = null;
try {
readSelector.select();
while (adding) {
this.wait(1000);
} Iterator<SelectionKey> iter = readSelector.selectedKeys().iterator();
while (iter.hasNext()) {
key = iter.next();
iter.remove();
if (key.isValid()) {
if (key.isReadable()) {
doRead(key);
}
}
key = null;
}
} catch (InterruptedException e) {
if (running) { // unexpected -- log it
LOG.info(getName() + " caught: " +
StringUtils.stringifyException(e));
}
} catch (IOException ex) {
LOG.error("Error in Reader", ex);
}
}
}
}
当有新的数据到来时,调用Listener的doRead方法进行读取数据。
doRead方法主要调用了Server.Connection的readAndProcess方法来读取客户端发送过来的数据并处理。
readAndProcess方法,先读取IPC连接魔数和协议版本号分别在缓冲区dataLengthBuffer和versionBuffer读入。
if (dataLengthBuffer.remaining() > 0) {
count = channelRead(channel, dataLengthBuffer); // dataLengthBuffer长度为4byte,第一次循环读取IPC连接魔数hrpc
//非第一次循环读取数据data
if (count < 0 || dataLengthBuffer.remaining() > 0)
return count;
} if (!rpcHeaderRead) {
//Every connection is expected to send the header.
if (rpcHeaderBuffer == null) {
rpcHeaderBuffer = ByteBuffer.allocate(2);
}
count = channelRead(channel, rpcHeaderBuffer); //rpcHeaderBuffer长度为2byte,读取协议版本号和authMehod
if (count < 0 || rpcHeaderBuffer.remaining() > 0) {
return count;
}
int version = rpcHeaderBuffer.get(0);//协议版本号
byte[] method = new byte[] {rpcHeaderBuffer.get(1)};//authMehod
authMethod = AuthMethod.read(new DataInputStream(
new ByteArrayInputStream(method)));
dataLengthBuffer.flip();
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();
……//sasl处理部分,可以忽略
rpcHeaderBuffer = null;
rpcHeaderRead = true;
continue;
}
在接下类的数据处理部分,readAndProcess主要调用了saslReadAndProcess方法,进而调用processOneRpc方法,进而处理processHeader方法和processData方法。processHeader方法如下:
private void processHeader(byte[] buf) throws IOException {
DataInputStream in =
new DataInputStream(new ByteArrayInputStream(buf));
header.readFields(in);
try {
String protocolClassName = header.getProtocol();
if (protocolClassName != null) {
protocol = getProtocolClass(header.getProtocol(), conf);
// getProtocolClass只判断Server是否实现了ConnectionHeader中要求的IPC接口
//如果实现,返回Class,否则抛异常。
}
} catch (ClassNotFoundException cnfe) {
throw new IOException("Unknown protocol: " + header.getProtocol());
}
......//获取用户信息
}
数据分帧和读取
Tcp通信基于字节流,没有消息边界的概念,常用的分帧方法如下:
1. 定长消息:通信双方发送的消息长度为固定的,接收者只需要将数据读入到相应的缓冲区即可。
2. 基于界定符:消息结束由唯一标示(特殊字符序列)指出,这个特殊字符序列不能在传输的消息数据中出现,接收者简单扫描输入信息查找界定符。
3. 显式长度:具体消息前面附加固定大小的字段,用来指示消息包含多少字节。
IPC客户端发送请求采用“显式长度”方法,长度是int类型。如下是服务器端接收数据的相关代码:
if (data == null) {
dataLengthBuffer.flip();
dataLength = dataLengthBuffer.getInt();//读取数据长度
//dataLengthBuffer长度为4byte,正好一个int长度。 if (dataLength == Client.PING_CALL_ID) {//心跳消息
if(!useWrap) { //covers the !useSasl too
dataLengthBuffer.clear();
return 0; //ping message
}
}
if (dataLength < 0) {
LOG.warn("Unexpected data length " + dataLength + "!! from " +
getHostAddress());
}
data = ByteBuffer.allocate(dataLength);
//根据长度,为读取数据分配缓冲区。
} count = channelRead(channel, data); if (data.remaining() == 0) {//读到一个完整的消息
dataLengthBuffer.clear();
data.flip();
if (skipInitialSaslHandshake) {
data = null;
skipInitialSaslHandshake = false;
continue;
}
boolean isHeaderRead = headerRead;
if (useSasl) {
saslReadAndProcess(data.array());
} else {
processOneRpc(data.array());//处理消息
}
data = null;
if (!isHeaderRead) {
continue;
}
}
而IPC服务器端到客户端用的是“定长消息”的变种,即利用前面介绍的序列化机制Writable发送响应。双方无需界定符和长度信息,其本质是一种“定长消息”。服务器端往客户端写数据的代码见Server.setupResponse,客户端读数据的代码在Connection.receiveResponse。
Hadoop建立IPC连接和数据读写的更多相关文章
- 在对方电脑建立IPC连接, 利用IPC$入侵 运行木马
第一大步: IPC漏洞的建立 1)在目标主机上设置组策略:開始->执行-〉gpedit.msc 2)计算机配置->windows配置-〉本地策略-〉安全选项 3)在安全选项中, 将网络訪 ...
- 浅谈MySQL、Hadoop、BigTable、Clickhouse数据读写机制
个人理解,欢迎指正 数据库 引擎 写数据 读数据 补充 MySql InnoDB:支持事务,高速读写性能一般 Myisam:不支持事务,高速读写性能好 以InnoDB更新一条记录为例 1.B+Tree ...
- hadoop单线程实现server多socket连接读取数据原理分析
一.问题引出. Hadoop 的Server 采用了Java 的NIO,这样的话就仅需要为每一个socket 连接建立一个线程,读取socket 上的数据.在Server 中,只需要一个线程,就可以a ...
- Hadoop 中 IPC 的源码分析
最近开始看 Hadoop 的一些源码,展开hadoop的源码包,各个组件分得比较清楚,于是开始看一下 IPC 的一些源码. IPC模块,也就是进程间通信模块,如果是在不同的机器上,那就可以理解为 RP ...
- 【详解】换一个角度看Socket的数据读写
前言 以前对IO.NIO还算了解,也写过Netty的项目.但是对底层的数据传递不是很了解,一直存有这方面的疑惑.但是由于有其他事情就被打断了.前阵子因为想要了解volatile关键字的原理,学习了下J ...
- 在libuv中使用openssl建立ssl连接
在libuv中使用openssl建立ssl连接 @(blogs) 使用openssl进行加密通信时,通常是先建立socket连接,然后使用SSL_XXX系列函数在普通socket之上建立安全连接,然后 ...
- 【Hadoop】二、HDFS文件读写流程
(二)HDFS数据流 作为一个文件系统,文件的读和写是最基本的需求,这一部分我们来了解客户端是如何与HDFS进行交互的,也就是客户端与HDFS,以及构成HDFS的两类节点(namenode和dat ...
- 数据读写API——IO流
理清一些概念 1.Java 中的IO是干啥的? IO指的是Input和Output,主要目的是实现数据在存储介质之间的传输.[流:数据流,类比与水流的流动] 2.IO分类 按照操作单元来划分,可以分为 ...
- socket 建立网络连接,client && server
client代码: package socket; import java.io.IOException; import java.net.Socket; /** * 客户端_聊天室 * * @aut ...
随机推荐
- 优化MyBatis配置文件中的配置
一.为实体类定义别名,简化sql映射xml文件中的引用 之前,我们在sql映射xml文件中的引用实体类时,需要写上实体类的全类名(包名+类名),如下: <!-- 创建用户(Create) --& ...
- NYOJ题目1162数字
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAr8AAAJ/CAIAAAD+kywNAAAgAElEQVR4nO3dO1LjzN4H4G8T5CyE2A ...
- Ionic环境搭建
stepts npm install -g ionic@beta Make sure you have NodeJS installed. Download the installer here or ...
- CLR via C#(03)- 对象创建和类型转换
一. 创建对象 CLR要求用new操作符创建对象,这个操作符在编译时产生的IL指令为newobj.例如: Student XiaoJing=new Student(“XiaoJing”,”1986”) ...
- SQLSERVER查询连接数
SELECT * FROM [Master].[dbo].[SYSPROCESSES] WHERE [DBID] IN (SELECT [DBID]FROM [Master].[dbo].[SYSDA ...
- WebService - 怎样提高WebService性能 大数据量网络传输处理
直接返回DataSet对象 返回DataSet对象用Binary序列化后的字节数组 返回DataSetSurrogate对象用Binary序列化后的字节数组 返回DataSetSurrogate对象用 ...
- java中文乱码解决方法汇总
public static void main(String[] argv){ try { System.out.println(“中文”);//1 ...
- 10g 11g配置Logical Standby
1.创建一个物理Standby数据库 详细见11g Physical Standby配置 2.Standby数据库取消managed recovery ALTER DATABASE RECOVER ...
- 攻城狮在路上(叁)Linux(二十一)--- linux磁盘检查 fsck \ badblocks
若系统掉电或磁盘发生问题,可利用fsck命令对文件系统进行检查.这一步是可选的,尽量少用. 使用前的建议:使用fsck命令时,被检查的分区务必不要挂载在系统上. 一.fsck: 命令格式:fsck [ ...
- ASP.NET多线程下使用HttpContext.Current为null解决方案 2015-01-22 15:23 349人阅读 评论(0) 收藏
问题一:多线程下获取文件绝对路径 当我们使用HttpContext.Current.Server.MapPath(strPath)获取绝对路径时HttpContext.Current为null,解决办 ...