最近开始看Hadoop源码,本来想对照着《Hadoop技术内幕》看的,但是发现那本书对应的Hadoop版本太老了,还是基于1.x的,构建工具用的Ant,所以没有完全对照书看。
目前Hadoop最新的stable版本为2.8.0-RC1,本文以及之后的Hadoop源码相关的博文都以这个版本为基础。另外写的可能会糙一些。
首先把源码从GitHub上搞下来,查看一下都有哪些tag,并迁出最新的release (3.0.0-alpha版本就不考虑了)
git tag
git checkout release-2.8.0-RC1
Hadoop 2.x版本已经是采用maven构建了,在Intellij idea里面将Hadoop源码作为maven工程导入。
Hadoop是一个分布式系统,对它的学习我考虑首先从RPC机制开始。
打开 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc
,发现这里面的类还真是多啊……需要选择一个合适的切入点才能开始看。
于是我将目光转向了 src/test/java/org/apache/hadoop/ipc
这个文件夹,里面是一些测试类,通过名字判断 TestPRC.java
是一个不错的切入点,点进去看了一下里面也确实是对Hadoop RPC完整流程的测试。于是开搞吧
如果想要运行测试的话,目前的代码是不行的,你可以看到很多文件都有报错,有一些类找不到。因为源码中只有Protobuf
的定义文件,但是没有生成具体的类。protoc
的安装流程这里不表述了,安装好Hadoop对应的 2.5版本的protoc之后,通过命令
mvn generate-test-sources
就可以生成需要的文件了。之后将target/generated-sources
以及target/generated-test-sources
中的文件复制到main
和 test
文件夹对应的路径中,就可以了
接下来简要的说一下总结的Hadoop RPC的流程
服务端
Server通过Listener启动Java NIO的ServerSocketChannel来监听指定的端口
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(acceptChannel.socket(), address, backlogLength, conf, portRangeConfig);
...
}
Server在接收到Socket连接请求后采用轮循的方式,将接入的Socket推入各个Reader的阻塞队列,并唤醒Reader的selector。由Reader对接入的Socket在selector上进行注册并处理
Reader负责解析Request
- 如果Header是
Http GET
请求的话,就返回一条提示信息 - 否则就从数据中解析
RpcRequest
,这里要根据RpcKind
来决定用哪种RpcEngine
,默认有Buildin
、Writable
、ProtoBuffer
三种 Reader
会根据RpcRequest
声称对应的RpcCall
,并推入一个BlockingQueue
中,等待调度
Request的数据结构:
- 长度为4字节的
RPCHeader
- 应该为常量
hrpc
- 如果为
GET
(GET后有空格)的话,说明收到的是HTTP GET
请求,会返回一条提示信息
- 应该为常量
- 长度为3字节的
connectionHeader
- 长度为4字节的
dataLength
,为表示数据长度的int。 - 长度为
dataLength
字节的data
,data
的数据结构::
Protobuf
类型RpcRequestHeaderProto
的header
header
中会指明RpcKind
- 根据
header
中的RpcKind
,采用不同的request类型来解析后续的数据
- 如果
RpcKind
为protobuf
的话,那么后续的数据类型为RpcProtobufRequest
,数据结构:
RequestHeaderProto
required string methodName = 1;
required string declaringClassProtocolName = 2;
required uint64 clientProtocolVersion = 3;
- 自定义类型的
message
其中
PRC
的headerRpcRequestHeaderProto
通过以下代码解析<T> T readFrom(ByteBuffer bb) throws IOException { // using the parser with a byte[]-backed coded input stream is the // most efficient way to deserialize a protobuf. it has a direct // path to the PB ctor that doesn't create multi-layered streams // that internally buffer. CodedInputStream cis = CodedInputStream.newInstance( bb.array(), bb.position() + bb.arrayOffset(), bb.remaining()); try { cis.pushLimit(cis.readRawVarint32()); message = message.getParserForType().parseFrom(cis); cis.checkLastTagWas(0); } finally { // advance over the bytes read. bb.position(bb.position() + cis.getTotalBytesRead()); } return (T) message; }
Handler负责处理RpcCall
- 多个
Handler
并行从BlockingQueue
中拉取RpcCall
并执行 - Handler根据
protocol
,method
,参数
,调用对应的invoker
执行具体的操作 - 执行结果会推入
RpcCall
对应的Connection
的Respond
队列中,之后由Responder
负责发送
Responsder负责返回RPC调用结果
客户端
- 客户端通过
RPC.getProxy
来获得服务的动态代理
RPC.getProxy
通过调用对应的RpcEngine
的getProxy()
方法来生成动态代理
,RpcEngine
提供了对应的invoker
- 对应的
RpcEngine
生成的动态代理
主要提供数据的序列化 - 远程调用通过
Client
类完成,每个SocketFactory
只生成一个Client
实例,Client
会复用到不同RpcServer的Connection
。 Connection
建立以后会自动启动独立的线程,排队发送Call
,并等待返回结果