从0到1编写分布式文件系统——Master节点之通络通信机制
在上篇文章(从0到1编写分布式文件系统——Master之通信协议)中阐述了Master的功能模块,并介绍通信协议内容,本文继续对网络通信机制进行阐述。
IO模型
Java进行网络通信无非就两种模式,阻塞IO和多路复用IO。
阻塞IO(Blocking IO)是JDK1.4之前就推出的网络通信模型,由于该模型下效率较为低下,所以现在的服务端较少使用这种机制。其原理如下:
- 通过调用创建出来的ServerSocket的accept方法进行监听。但accept方法是阻塞方法,也就是说调用accept方法后程序会停下来等待连接请求,在接受请求之前程序将不会继续执行;
- 当接收到请求后,还需要将数据从内核空间拷贝到用户空间,这个过程也是阻塞的。
多路复用IO是JDK1.4之后推出的网络通信模型,跟同步阻塞本质一样,不过利用了select系统调用,由内核来负责本来是请求进程该做的轮询操作,因为支持多路IO所以效率得到了提升。
- 基本原理:当用户线程调用select,那么整个进程会被阻塞,同时内核会监听所有select负责的socket,当任何一个socket就绪,select就会返回,这个时候用户进程在进行操作,将数据拷贝到用户进程进行处理。
Simple Distributed File System主要使用netty进行网络处理,netty处理原理如下图所示。
SDFS中,首先定义了一个Server通用接口,可以用任何网络框架或自己调用Java NIO API进行实现。
public interface Server {
public void start();
}
SDFS内部默认采用了nett对Server接口进行实现,并定义了DefaultServer通用服务端类。start方法中就是netty服务器启动常用代码,不同future通过异步监听的方式来检测服务端是否启动成功,一旦启动失败(如端口被占用)则关闭netty服务端。
public class DefaultServer implements Server {
...
@Override
public void start(){
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
StopWatch watch = new StopWatch();
//启动计时
watch.start();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(initializer);
ChannelFuture future = bootstrap.bind(port);
//异步启动,通过监听器监听是否启动成功
future.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if (future.isSuccess()) {
watch.stop();
long cost = watch.getTime();
LOGGER.info("[{}] Startup at port:{} cost:{}[ms]", clazz.getSimpleName(), port, cost);
}else{
LOGGER.error("[{}] boot failure cause by {}, port is {}, master will exit...", clazz.getSimpleName(),
future.cause().getMessage(), port);
future.cancel(true);
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
});
}
}
处理机制
在Master的Server中,定义了MasterServerInitializer作请求的处理类,该类十分简单,只是生成了PacketDispatcher作为初始请求的处理器,具体来看看PacketDispatcher的内部定义,代码如下所示。
- 首先判断收到的请求首字节魔数是否正确;
- 之后根据type类型判断请求的类型。因为master后面需要跟work节点保持长连接心跳,所以需要根据不同的请求分发到不同的方法进行处理。
- 先查看处理一般请求的dispatchToPacket方法。该方法中重新添加了处理器,并重新分发了请求包,主要在DefaultMasterPacketHandler对一般的请求包进行处理。
public class PacketDispatcher extends ByteToMessageDecoder {
...
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> list) throws Exception {
if (in.readableBytes() < 15) {
return;
}
int readerIndex = in.readerIndex();
final byte magic = in.getByte(readerIndex);
final byte type = in.getByte(readerIndex + 1);
if (isPacket(magic)) {
if (isHeartPacket(type)){
//处理长连接心跳机制
dispatchToHeartPacket(ctx);
}else{
//处理客户端发送过来的请求
dispatchToPacket(ctx);
}
...
} else {
in.clear();
ctx.close();
}
}
private void dispatchToPacket(ChannelHandlerContext ctx) {
ChannelPipeline pipeline = ctx.pipeline();
pipeline.addLast(new PacketMessageCodec());
pipeline.addLast(new DefaultMasterPacketHandler(meta));
// 将所有所需的ChannelHandler添加到pipeline之后,一定要将自身移除掉
// 否则该Channel之后的请求仍会重新执行协议的分发,而这是要避免的
pipeline.remove(this);
// 将channelActive事件传递到PacketHandler
ctx.fireChannelActive();
}
...
}
在DefaultMasterPacketHandler中,定义了Actuator对所有的request进行处理。Actuator是一个接口,主要的实现在DefaultActuator中。
public class DefaultMasterPacketHandler extends SimpleChannelInboundHandler<Packet> {
private Actuator actuator;
@Override
protected void channelRead0(ChannelHandlerContext ctx, Packet packet) throws Exception {
byte type = packet.getType();
if (RequestResponseUtils.isRequest(type)) {
onRequest(ctx, packet);
} else {
onResponse(packet);
}
}
private void onRequest(ChannelHandlerContext ctx, Packet packet) {
Request request = packet.getRequest();
actuator.execute(ctx, request, meta, packet.getId());
}
private void onResponse(Packet packet) {
System.out.println(packet);
}
}
在DefaultActuator,定义了一个线程出处理所有的request,通过从0到1编写分布式文件系统——Master之通信协议)文章中处理机制中所说,通过request生成该请求的processor线程,通过executor处理该线程,至此,所有的request到这里都交由相应的processor去处理,对应的request只需要编写相应的processor即可。
public class DefaultActuator implements Actuator {
private Executor executor;
private DefaultActuator() {
executor = new ThreadPoolExecutor(
10,
20,
5,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new DefaultThreadFactory("packet-executor-pool", true));
}
@Override
public void execute(ChannelHandlerContext ctx, Request request, Context context, Long packetId) {
Processor processor = request.buildSelfProcessor(ctx, request, context, packetId);
executor.execute(processor);
}
...
}
编解码
再介绍下PacketMessageCodec编解码类,主要是encode和decode方法。
- 在encode中,依次在byteBuf中写入magic,type,id,serialize以及length,再根据序列化算法对packet序列化后写入byteBuf中完成。
- 在decode,依次读取magic,type,id,serialize等字段并进行验证,最后反序列化得到完成的packet包。
public class PacketMessageCodec extends ByteToMessageCodec<Packet> {
...
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Packet packet, ByteBuf byteBuf) throws Exception {
// check the packet
if (!checkPacket(packet)) {
throw new RuntimeException("checkPacket failed!");
}
byte serialize = packet.getSerialize();
Serializer serializer = chooser.choose(serialize);
// get packet content bytes
byte[] content = serializer.serialize(packet);
// do encode
byteBuf.writeByte(packet.getMAGIC());
byteBuf.writeByte(packet.getType());
byteBuf.writeLong(packet.getId());
byteBuf.writeByte(serialize);
byteBuf.writeInt(content.length);
byteBuf.writeBytes(content);
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
// len = 1byte(magic) + 1byte(type) + 8byte(id) + 1type(serialize) + 4bytes(content length)
int leastPacketLen = 15;
if (byteBuf.readableBytes() < leastPacketLen) {
return;
}
byteBuf.markReaderIndex();
byte magic = byteBuf.readByte();
byte type = byteBuf.readByte();
long id = byteBuf.readLong();
byte serialize = byteBuf.readByte();
Serializer serializer = chooser.choose(serialize);
int len = byteBuf.readInt();
// until we have the entire packet received
if (byteBuf.readableBytes() < len) {
byteBuf.resetReaderIndex();
return;
}
// read content
byte[] content = new byte[len];
byteBuf.readBytes(content);
Packet packet = serializer.deserialize(content, Packet.class);
list.add(packet);
}
}
总结
整体处理过程如下图所示。
其他说明
上篇文章从0到1编写分布式文件系统——Master之通信协议
下篇文章从0到1编写分布式文件系统——Master节点之元数据及镜像机制
详细的代码:Simple Distributed File System