从0到1编写分布式文件系统——Master节点之通络通信机制

484 阅读4分钟

从0到1编写分布式文件系统——Master节点之通络通信机制

  在上篇文章(从0到1编写分布式文件系统——Master之通信协议)中阐述了Master的功能模块,并介绍通信协议内容,本文继续对网络通信机制进行阐述。

IO模型

  Java进行网络通信无非就两种模式,阻塞IO和多路复用IO。

  阻塞IO(Blocking IO)是JDK1.4之前就推出的网络通信模型,由于该模型下效率较为低下,所以现在的服务端较少使用这种机制。其原理如下:

  1. 通过调用创建出来的ServerSocket的accept方法进行监听。但accept方法是阻塞方法,也就是说调用accept方法后程序会停下来等待连接请求,在接受请求之前程序将不会继续执行;
  2. 当接收到请求后,还需要将数据从内核空间拷贝到用户空间,这个过程也是阻塞的。 image.png   多路复用IO是JDK1.4之后推出的网络通信模型,跟同步阻塞本质一样,不过利用了select系统调用,由内核来负责本来是请求进程该做的轮询操作,因为支持多路IO所以效率得到了提升。
  • 基本原理:当用户线程调用select,那么整个进程会被阻塞,同时内核会监听所有select负责的socket,当任何一个socket就绪,select就会返回,这个时候用户进程在进行操作,将数据拷贝到用户进程进行处理。 image.png   Simple Distributed File System主要使用netty进行网络处理,netty处理原理如下图所示。 image.png   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的内部定义,代码如下所示。

  1. 首先判断收到的请求首字节魔数是否正确;
  2. 之后根据type类型判断请求的类型。因为master后面需要跟work节点保持长连接心跳,所以需要根据不同的请求分发到不同的方法进行处理。
  3. 先查看处理一般请求的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方法。

  1. 在encode中,依次在byteBuf中写入magic,type,id,serialize以及length,再根据序列化算法对packet序列化后写入byteBuf中完成。
  2. 在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);
    }
}

总结

整体处理过程如下图所示。 image.png

其他说明

上篇文章从0到1编写分布式文件系统——Master之通信协议
下篇文章从0到1编写分布式文件系统——Master节点之元数据及镜像机制
详细的代码:Simple Distributed File System