小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
YARN-高并发RPC源码实现
上一篇文章讲述了RPC相关的基础内容,接下来咱们讲解一下YARN-高并发RPC源码的具体实现
注意:
- 阅读本文需要一些RPC相关的基础内容,请点击YARN-RPC网络通信架构设计查看。
- 本文将以注释的方式对代码进行说明
1.RPC 服务器端源码分析
本文章从NameNode中找RPCServer 创建的位置
NameNode.class->createNameNode->new NameNode->父类构造方法-> initialize(getConf())->rpcServer = createRpcServer(conf)->new NameNodeRpcServer->new RPC.Builder(conf) .build()->return getProtocolEngine(...).getServer (RPC类中)
Server 所在位置: org.apache.hadoop.ipc.Server。
RPC Server 的创建的代码入口是: RPC.getProtocolEngine(....).getServer();
RPC 中有两个方法:
- 获取 RPC 服务端:getServer()
- 获取 RPC 客户端:getClient()
RPC.Server server = new RPC.Builder(new Configuration()).setXXX().setXXXX().build(){
// ProtocolEngine 有两种实现,WritableRpcEngine 和 ProtobufRpcEngine 此处为ProtobufRpcEngine
return getProtocolEngine(this.protocol, this.conf).getServer(...){
return new Server(protocol, protocolImpl, conf, bindAddress, port, numHandlers, numReaders, ....){
super(bindAddress, port, null, numHandlers, numReaders,...){
super(bindAddress, port, paramClass, handlerCount, numReaders, queueSizePerHandler, conf, serverName,
...){
// 初始化一个 Call 队列
this.callQueue = new CallQueueManager<Call>(.....);
// 创建一个 Listener 线程: 内部 启动 NIO 服务端,监听链接请求 ServerSocketChannel.open();
listener = new Listener(port){
// 启动 NIO 服务端
address = new InetSocketAddress(bindAddress, port);
acceptChannel = ServerSocketChannel.open();
acceptChannel.configureBlocking(false);
bind(acceptChannel.socket(), address, backlogLength, conf, portRangeConfig);
selector = Selector.open();
// 初始化一组 Reader 线程,用来处理 IO
readers = new Reader[readThreads];
for(int i = 0; i < readThreads; i++) {
Reader reader = new Reader("Socket Reader #" + (i + 1) + " for port " + port);
readers[i] = reader;
reader.start();
}
// NIO 服务端注册 OP_ACCEPT 事件,用来监听 链接请求
acceptChannel.register(selector, SelectionKey.OP_ACCEPT);
}
// 创建一个链接管理器
connectionManager = new ConnectionManager();
// 初始化一个响应线程
responder = new Responder();
}
}
}
}
}
到此为止:
- callQueue初始化完毕
- Listener初始化完毕
- Reader初始化完毕;并且启动完毕
- responder初始化完毕
- connection创建完毕
- Handler目前还没有看到创建
RPC Server 的启动的代码入口是:RPC.Server.start()
RPC.Server.start(){
// 负责给 Client 返回响应的线程启动
responder.start();
// 监听连接请求的线程启动
listener.start();
// 创建并启动 handlerCount 个 Handler 线程用来执行真正的 RpcCall 的处理
handlers = new Handler[handlerCount];
for (int i = 0; i < handlerCount; i++) {
handlers[i] = new Handler(i);
handlers[i].start();
}
}
到此为止:
- callQueue初始化完毕
- Listener初始化完毕;启动完毕
- Reader初始化完毕;启动完毕
- responder初始化完毕;启动完毕
- connection创建完毕
- Handler初始化完毕;启动完毕
当 Rpc Server 接收到一个 RPC 请求:
Listener线程:
Listener.run(){
// 启动定时任务,用来检测闲置的 Connection
connectionManager.startIdleScan();
// 侦听链接请求
while(running) {
getSelector().select();
Iterator<SelectionKey> iter = getSelector().selectedKeys().iterator();
while(iter.hasNext()) {
key = iter.next();
iter.remove();
try {
if(key.isValid()) {
if(key.isAcceptable()) {
// 处理链接请求
doAccept(key){
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel;
// 完成链接
while((channel = server.accept()) != null) {
// 获取一个 Reader 线程用来完成该 SocketChannel 的处理
// 获取策略: 轮询
Reader reader = getReader();
Connection c = connectionManager.register(channel, this.listenPort,
this.isOnAuxiliaryPort);
key.attach(c);
// SocketChannel 对应的 Connection 和 Reader 线程的绑定
reader.addConnection(c);
}
}
}
}
}
}
}
}
Reader线程:
Reader.run(){
doRunLoop(){
while(running) {
// 给每个刚完成链接的 Connection 注册 OP_READ 事件
int size = pendingConnections.size();
for(int i = size; i > 0; i--) {
Connection conn = pendingConnections.take();
conn.channel.register(readSelector, SelectionKey.OP_READ, conn);
}
// 进行 select 此处是阻塞的
readSelector.select();
// 选择有 OP_READ 响应的客户端,执行 read 动作
Iterator<SelectionKey> iter = readSelector.selectedKeys().iterator();
while(iter.hasNext()) {
key = iter.next();
iter.remove();
if(key.isReadable()) {
// 执行 read 动作
doRead(key){
// 记录最近一次活跃时间
Connection c = (Connection) key.attachment();
c.setLastContact(Time.now());
// 处理 read
count = c.readAndProcess(){
// 如果之前没有读取过请求头,则先读取请求头
if(!connectionHeaderRead) {
count = channelRead(channel, connectionHeaderBuf);
}
// 读取请求数据
count = channelRead(channel, data);
// 处理一次 RPC 请求
processOneRpc(requestData){
processRpcRequest(header, buffer){
// 反序列化得到 rpcRequest
Writable rpcRequest;
rpcRequest = buffer.newInstance(rpcRequestClass, conf);
// 构建一个 RpcCall 请求对象
RpcCall call = new RpcCall(this, ....);
// 加入 callQueue 队列
internalQueueCall(call){
internalQueueCall(call, true){
if(blocking) {
callQueue.put(call);
} else {
callQueue.add(call);
}
}
}
}
}
}
}
}
}
}
}
}
Handler 线程消耗callQueue:
Handler.run(){
// 从队列获取 RpcCall 实例
call = callQueue.take();
// 执行
call.run(){
// 执行 Call 的处理
value = call(rpcKind, connection.protocolName, rpcRequest, timestampNanos){
// RpcInvoker 有两种: WritableRpcInvoker 和 ProtoBufRpcInvoker
return getRpcInvoker(rpcKind).call(this, protocol, rpcRequest, receiveTime){
// 处理请求头,还有各种参数等
// 调用 RPC 方法执行处理
result = service.callBlockingMethod(methodDescriptor, null, param);
// 返回 RPC 方法执行结果
return RpcWritable.wrap(result);
}
}
// 设置 RPC 处理结果
setResponseFields(value, responseParams);
// 开始发送响应
sendResponse(){
doResponse(null){
doResponse(t, RpcStatusProto.FATAL){
// 设置该 Call 的响应结果
setupResponse(call, ....);
// 处理该 Call 的响应
connection.sendResponse(call){
// 最终调用 responder 线程来完成最终结果的输出
responder.doRespond(call){
// 将 call 加入响应队列
call.connection.responseQueue.addLast(call);
// 处理 Call 的响应
if(call.connection.responseQueue.size() == 1) {
// 最终,无论是同步写,还是异步写,都是这个方法
// 同步写: processResponse 在 Handler 线程中执行
// 异步写: processResponse 在 Responder 线程中执行
processResponse(call.connection.responseQueue, true){
// 写出响应
int numBytes = channelWrite(channel, call.rpcResponse);
//
if(numBytes < 0) {
return true;
}
// TODO_MA 注释: 一次写完所有数据了
if(!call.rpcResponse.hasRemaining()) {
...
} else {
// 注册 OP_WRITE 执行多步异步写出
writeSelector.wakeup();
channel.register(writeSelector, SelectionKey.OP_WRITE, call);
}
}
}
}
}
}
}
}
}
}
Responder 线程:
Responder.run() {
doRunLoop(){
while(running) {
writeSelector.select(TimeUnit.NANOSECONDS.toMillis(PURGE_INTERVAL_NANOS));
Iterator<SelectionKey> iter = writeSelector.selectedKeys().iterator();
while(iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
// 处理 Write
if(key.isWritable()) {
doAsyncWrite(key){
// 又回到跟之前一样的回路
processResponse(call.connection.responseQueue, false);
}
}
}
}
}
}
}
注意:
- Listener线程是只有一个的,它处理的速度是非常快的
- Reader线程初始为1个,可以调节
- Handler线程初始为10个,可以调节
- 在Hadoop高并发调优的时候可以适当的调节Reader线程和Handler线程(Call队列的消费者和生产者的消费水平和生产水平达到一个平衡的状态)
RPC称为是高并发的网络通信框架的主要原因就是因为Reader和Handler线程
至此,下图的所有流程都已经讲解完毕,包括:
- Listener、Reader、Handler、Responder各个线程的创建初始化,以及启动的流程
- 针对各个线程的具体内容细节进行说明
YARN 是一个分布式资源管理系统,它包含了分布的多个组件,我们可以通过这些组件之间设计的交互协议来说明。
本人是该领域的小白,在学习的路上,上述文章如有错误还请指出批评。