青训营大项目Client端解读|青训营笔记

108 阅读13分钟

##这是我参与「第四届青训营 」笔记创作活动的第7天

整体项目架构图

ruyuan-dfs-datanode目录结构

.
└── main
    ├── java
    │   └── com
    │       └── ruyuan
    │           └── dfs
    │               └── datanode
    │                   ├── DataNode.java
    │                   ├── config
    │                   │   └── DataNodeConfig.java
    │                   ├── namenode
    │                   │   ├── HeartbeatTask.java
    │                   │   └── NameNodeClient.java
    │                   ├── replica
    │                   │   ├── PeerDataNodes.java
    │                   │   └── ReplicateManager.java
    │                   └── server
    │                       ├── DataNodeApis.java
    │                       ├── DataNodeServer.java
    │                       ├── DefaultFileTransportCallback.java
    │                       ├── MultiPortChannelInitializer.java
    │                       ├── StorageInfo.java
    │                       ├── StorageManager.java
    │                       ├── http
    │                       │   └── HttpFileServerHandler.java
    │                       └── locate
    │                           ├── AbstractFileLocator.java
    │                           ├── AesFileLocator.java
    │                           ├── FileLocator.java
    │                           ├── FileLocatorFactory.java
    │                           ├── Md5FileLocator.java
    │                           ├── Sha1FileLocator.java
    │                           └── SimpleFileLocator.java
    └── resources
        └── assembly.xml

主要模块:

  • config: Datanode配置
  • namenode: 主要负责与Namenode之间的通讯
  • replica: 主要负责副本复制管理
  • server: Datanode服务端实现,用于处理客户端的上传文件或下载文件的请求
  • DataNode.java: DataNode启动类的实现

具体模块解读

1、config

主要功能:Datanode配置

包含文件:DataNodeConfig.java

源代码:

/**
* DataNode的配置
**/

// Lombok提供了一系列注解来简化Java代码
@Data
@Builder
public class DataNodeConfig {

    private String baseDir;
    private String nameNodeServers;
    private String dataNodeTransportServer;
    private String dataNodeHttpServer;

    private int heartbeatInterval;
    private int dataNodeId;
    private String fileLocatorType;
    private int dataNodeWorkerThreads;

    // 读取conf/datanode.properties文件
    public static DataNodeConfig parse(Properties properties) {
        // 基础目录,可以理解为根目录
        String baseDir = (String) properties.get("base.dir");
        // namenode服务器地址
        String nameNodeServers = (String) properties.get("namenode.servers");
        // datanode监听的上传文件的服务器地址
        String dataNodeTransportServer = (String) properties.get("datanode.transport.server");
        // datanode监听的Http请求服务端口
        String dataNodeHttpServer = (String) properties.get("datanode.http.server");
        // 心跳间隔时间
        int heartbeatInterval = Integer.parseInt((String) properties.get("datanode.heartbeat.interval"));
        // 每一个datanode的id
        int dataNodeId = Integer.parseInt((String) properties.get("datanode.id"));
        // 根据文件名定位到本地磁盘目录文件位置的方式
        String fileLocatorType = (String) properties.get("file.locator.type");
        // 上传或下载文件的工作线程数
        int dataNodeWorkerThreads = Integer.parseInt((String) properties.get("datanode.worker.threads"));
        return DataNodeConfig.builder()
                .baseDir(baseDir)
                .nameNodeServers(nameNodeServers)
                .dataNodeTransportServer(dataNodeTransportServer)
                .dataNodeHttpServer(dataNodeHttpServer)
                .heartbeatInterval(heartbeatInterval)
                .dataNodeId(dataNodeId)
                .fileLocatorType(fileLocatorType)
                .dataNodeWorkerThreads(dataNodeWorkerThreads)
                .build();
    }


    public int getNameNodePort() {
        return Integer.parseInt(nameNodeServers.split(":")[1]);
    }

    public int getDataNodeHttpPort() {
        return Integer.parseInt(dataNodeHttpServer.split(":")[1]);
    }

    public String getNameNodeAddr() {
        return nameNodeServers.split(":")[0];
    }

    public int getDataNodeTransportPort() {
        return Integer.parseInt(dataNodeTransportServer.split(":")[1]);
    }

    public String getDataNodeTransportAddr() {
        return dataNodeTransportServer.split(":")[0];
    }
}

Lombok相关介绍:blog.csdn.net/death05/art…

2、namenode

主要负责与Namenode之间的通讯

import com.google.common.collect.Lists;
import com.google.protobuf.InvalidProtocolBufferException;
import com.ruyuan.dfs.common.FileInfo;
import com.ruyuan.dfs.common.NettyPacket;
import com.ruyuan.dfs.common.enums.PacketType;
import com.ruyuan.dfs.common.ha.BackupNodeManager;
import com.ruyuan.dfs.common.network.NetClient;
import com.ruyuan.dfs.common.network.RequestWrapper;
import com.ruyuan.dfs.common.utils.DefaultScheduler;
import com.ruyuan.dfs.datanode.config.DataNodeConfig;
import com.ruyuan.dfs.datanode.replica.PeerDataNodes;
import com.ruyuan.dfs.datanode.replica.ReplicateManager;
import com.ruyuan.dfs.datanode.server.StorageInfo;
import com.ruyuan.dfs.datanode.server.StorageManager;
import com.ruyuan.dfs.model.backup.BackupNodeInfo;
import com.ruyuan.dfs.model.datanode.*;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.ScheduledFuture;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 负责和NameNode通讯的组件
 *
 * @author Sun Dasheng
 */
@Slf4j
public class NameNodeClient {

    private final ReplicateManager replicateManager;
    private final DefaultScheduler defaultScheduler;
    private final BackupNodeManager backupNodeManager;
    private final StorageManager storageManager;
    private NetClient netClient;
    private final DataNodeConfig datanodeConfig;
    private ScheduledFuture<?> scheduledFuture;

    public NameNodeClient(StorageManager storageManager, DataNodeConfig datanodeConfig, DefaultScheduler defaultScheduler,
                          PeerDataNodes peerDataNodes) {
        this.netClient = new NetClient("DataNode-NameNode-" + datanodeConfig.getNameNodeAddr(), defaultScheduler);
        this.datanodeConfig = datanodeConfig;
        this.storageManager = storageManager;
        this.defaultScheduler = defaultScheduler;
        this.backupNodeManager = new BackupNodeManager(defaultScheduler);
        this.replicateManager = new ReplicateManager(defaultScheduler, peerDataNodes, storageManager, this);
        peerDataNodes.setNameNodeClient(this);
    }

    /**
     * 启动NameNode连接的客户端
     */
    public void start() {
        this.netClient.addNettyPackageListener(this::onReceiveMessage);//为客户端添加网络包监听器
        this.netClient.addConnectListener(connected -> {//为客户端添加连接状态监听器
            if (connected) {
                fetchBackupInfo();//连接请求
                register();//注册节点
            } else {
                if (scheduledFuture != null) {
                    scheduledFuture.cancel(true);
                    scheduledFuture = null;
                }
            }
        });
        this.netClient.addNetClientFailListener(() -> {//添加连接失败标记
            log.info("DataNode检测到NameNode挂了,标记NameNode已宕机");
            backupNodeManager.markNameNodeDown();//标记宕机节点
        });
        this.netClient.connect(datanodeConfig.getNameNodeAddr(), datanodeConfig.getNameNodePort());//连接
    }

    /**
     * DatNode节点注册
     */
    private void register() throws InterruptedException {
        StorageInfo storageInfo = storageManager.getStorageInfo();//报告存储信息
        RegisterRequest request = RegisterRequest.newBuilder()
                .setHostname(datanodeConfig.getDataNodeTransportAddr())//设置主机名称
                .setNioPort(datanodeConfig.getDataNodeTransportPort())//设置NIO线程端口
                .setHttpPort(datanodeConfig.getDataNodeHttpPort())//设置http请求端口
                .setStoredDataSize(storageInfo.getStorageSize())//设置存储数据大小
                .setFreeSpace(storageInfo.getFreeSpace())//可用空间
                .setNodeId(datanodeConfig.getDataNodeId())//datanode的id
                .build();
        NettyPacket nettyPacket = NettyPacket.buildPacket(request.toByteArray(),//创建请求
                PacketType.DATA_NODE_REGISTER);
        log.info("DataNode发起注册请求: {}", request.getHostname());
        netClient.send(nettyPacket);//发送注册请求
    }
    //接收信息
    private void onReceiveMessage(RequestWrapper requestWrapper) throws Exception {
        PacketType packetType = PacketType.getEnum(requestWrapper.getRequest().getPacketType());//请求包的类型
        switch (packetType) {
            case FETCH_BACKUP_NODE_INFO://客户端或者DataName往NameNode上发起请求获取备份信息
                handleFetchBackupNodeInfoResponse(requestWrapper);
                break;
            case DATA_NODE_REGISTER://DataNode往NameNode注册请求
                handleDataNodeRegisterResponse(requestWrapper);
                break;
            case HEART_BRET://DataNode往NameNode发送的心跳请求
                handleDataNodeHeartbeatResponse(requestWrapper);
                break;
            default:
                break;
        }
    }
    //DataNode往NameNode注册请求
    private void handleDataNodeHeartbeatResponse(RequestWrapper requestWrapper) throws Exception {
        if (requestWrapper.getRequest().isError()) {
            log.warn("心跳失败, 重新发起注册请求:[error={}]", requestWrapper.getRequest().getError());
            register();
            return;
        }
        HeartbeatResponse heartbeatResponse = HeartbeatResponse.parseFrom(requestWrapper.getRequest().getBody());//回复心跳
        handleCommand(heartbeatResponse);
    }
    //DataNode往NameNode注册请求
    private void handleDataNodeRegisterResponse(RequestWrapper requestWrapper) {
        ChannelHandlerContext ctx = requestWrapper.getCtx();
        if (scheduledFuture == null) {
            log.info("开启定时任务发送心跳,心跳间隔为: [interval={}ms]", datanodeConfig.getHeartbeatInterval());//心跳间隔
            scheduledFuture = ctx.executor().scheduleAtFixedRate(new HeartbeatTask(ctx, datanodeConfig),//心跳定时任务
                    0, datanodeConfig.getHeartbeatInterval(), TimeUnit.MILLISECONDS);//单位毫秒
        }
        if (requestWrapper.getRequest().isSuccess()) {//请求封装器
            StorageInfo storageInfo = storageManager.getStorageInfo();
            log.info("注册成功,发送请求到NameNode进行全量上报存储信息。[size={}]", storageInfo.getFiles().size());
            reportStorageInfo(ctx, storageInfo);
        } else {
            log.info("DataNode重启,不需要全量上报存储信息。");
        }
    }

    //回复心跳
    private void handleCommand(HeartbeatResponse response) {
        if (response.getCommandsList().isEmpty()) {
            return;
        }
        List<ReplicaCommand> commands = response.getCommandsList();//获取列表指令
        replicateManager.addReplicateTask(commands);//创建副本
    }
    //客户端或者DataName往NameNode上发起请求获取备份信息
    private void handleFetchBackupNodeInfoResponse(RequestWrapper requestWrapper) throws InvalidProtocolBufferException {
        if (requestWrapper.getRequest().getBody().length == 0) {
            log.warn("拉取BackupNode信息为空,设置NetClient为无限重试.");
            netClient.setRetryTime(-1);//超时设置
            return;
        }
        netClient.setRetryTime(3);
        BackupNodeInfo backupNodeInfo = BackupNodeInfo.parseFrom(requestWrapper.getRequest().getBody());//备份信息
        backupNodeManager.maybeEstablishConnect(backupNodeInfo, hostname -> {//和BackupNode简历连接
            datanodeConfig.setNameNodeServers(hostname + ":" + datanodeConfig.getNameNodePort());//配置服务器
            netClient.shutdown();//释放连接
            netClient = new NetClient("DataNode-NameNode-" + datanodeConfig.getNameNodeAddr(), defaultScheduler);//负责和namenode通信
            log.info("检测到BackupNode升级为NameNode了,替换NameNode链接信息,并重新建立链接:[hostname={}, port={}]",
                    datanodeConfig.getNameNodeAddr(), datanodeConfig.getNameNodePort());//获取namenode地址
            start();
        });
    }

    private void fetchBackupInfo() throws InterruptedException {
        //创建请求
        NettyPacket nettyPacket = NettyPacket.buildPacket(new byte[0], PacketType.FETCH_BACKUP_NODE_INFO);
        //发送请求
        netClient.send(nettyPacket);
    }

    /**
     * 上报文件副本信息
     *
     * @param fileName 文件名称
     * @param fileSize 文件大小
     */
    public void informReplicaReceived(String fileName, long fileSize) throws InterruptedException {
        InformReplicaReceivedRequest replicaReceivedRequest = InformReplicaReceivedRequest.newBuilder()//接受备份请求
                .setFilename(fileName)//设置文件名称
                .setHostname(datanodeConfig.getDataNodeTransportAddr())//设置服务器名称为datenode上传文件地址
                .setFileSize(fileSize)//设置文件大小
                .build();
        NettyPacket nettyPacket = NettyPacket.buildPacket(replicaReceivedRequest.toByteArray(), PacketType.REPLICA_RECEIVE);//创建请求
        netClient.send(nettyPacket);
    }

    /**
     * 上报文件副本信息
     *
     * @param fileName 文件名称
     * @param fileSize 文件大小
     */
    public void informReplicaRemoved(String fileName, long fileSize) throws InterruptedException {
        InformReplicaReceivedRequest replicaReceivedRequest = InformReplicaReceivedRequest.newBuilder()
                .setFilename(fileName)
                .setHostname(datanodeConfig.getDataNodeTransportAddr())
                .setFileSize(fileSize)
                .build();
        NettyPacket nettyPacket = NettyPacket.buildPacket(replicaReceivedRequest.toByteArray(), PacketType.REPLICA_REMOVE);
        netClient.send(nettyPacket);
    }
    //存储信息
    private void reportStorageInfo(ChannelHandlerContext ctx, StorageInfo storageInfo) {
        List<FileInfo> files = storageInfo.getFiles();
        // 每次最多上传100个文件信息
        if (files.isEmpty()) {
            ReportCompleteStorageInfoRequest request = ReportCompleteStorageInfoRequest.newBuilder()
                    .setHostname(datanodeConfig.getDataNodeTransportAddr())
                    .setFinished(true)//标记已完成
                    .build();
            NettyPacket nettyPacket = NettyPacket.buildPacket(request.toByteArray(), PacketType.REPORT_STORAGE_INFO);//新建包
            ctx.writeAndFlush(nettyPacket);//写队列并刷新
        } else {
            List<List<FileInfo>> partition = Lists.partition(files, 100);
            for (int i = 0; i < partition.size(); i++) {
                List<FileInfo> fileInfos = partition.get(i);
                List<FileMetaInfo> fileMetaInfos = fileInfos.stream()
                        .map(e -> FileMetaInfo.newBuilder()
                                .setFilename(e.getFileName())
                                .setFileSize(e.getFileSize())
                                .build())
                        .collect(Collectors.toList());
                boolean isFinish = i == partition.size() - 1;
                ReportCompleteStorageInfoRequest.Builder builder = ReportCompleteStorageInfoRequest.newBuilder()
                        .setHostname(datanodeConfig.getDataNodeTransportAddr())
                        .addAllFileInfos(fileMetaInfos)
                        .setFinished(isFinish);
                ReportCompleteStorageInfoRequest request = builder.build();
                NettyPacket nettyPacket = NettyPacket.buildPacket(request.toByteArray(), PacketType.REPORT_STORAGE_INFO);
                ctx.writeAndFlush(nettyPacket);
            }
        }
    }

    /**
     * 停止服务
     */
    public void shutdown() {
        if (netClient != null) {
            netClient.shutdown();
        }
    }
}

3、 replica

PeerDataNodes.java

虚线是重写方法

源代码解读:

package com.ruyuan.dfs.datanode.replica;

import com.ruyuan.dfs.common.NettyPacket;
import com.ruyuan.dfs.common.enums.PacketType;
import com.ruyuan.dfs.common.network.NetClient;
import com.ruyuan.dfs.common.utils.DefaultScheduler;
import com.ruyuan.dfs.datanode.config.DataNodeConfig;
import com.ruyuan.dfs.datanode.namenode.NameNodeClient;
import com.ruyuan.dfs.datanode.server.DataNodeApis;
import com.ruyuan.dfs.model.common.GetFileRequest;
import com.ruyuan.dfs.model.datanode.PeerNodeAwareRequest;
import io.netty.channel.socket.SocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * DataNode相互感知的组件
 *
 * <pre>
 *     在需要复制副本的时候,假设datanode01需要从datanode02复制副本
 *
 *     则会从datanode01发起一个和datanode02的网络连接:  datanode01 -> datanode02
 *
 *     1. datanode01向datanode02发起连接,并保存这个链接
 *     2. datanode02收到下发文件的请求,同时将datanod01的连接保存在 dataNodeChannelMap 中
 *     3. 当datanode02需要从datanode01复制副本的时候,复用之前建立的链接
 *
 * </pre>
 *
 * @author Sun Dasheng
 */
@Slf4j
/*slf4j的全称是Simple Loging Facade For Java,
即它仅仅是一个为Java程序提供日志输出的统一接口,
并不是一个具体的日志实现方案,就比如JDBC一样,只是一种规则而已。*/
//PeerDataNode目标DataNode
public class PeerDataNodes {

    private DataNodeApis dataNodeApis;
    private DataNodeConfig dataNodeConfig;
    private DefaultScheduler defaultScheduler;
    private Map<String, PeerDataNode> dataNodeChannelMap = new ConcurrentHashMap<>();

    public PeerDataNodes(DefaultScheduler defaultScheduler, DataNodeConfig dataNodeConfig, DataNodeApis dataNodeApis) {
        this.defaultScheduler = defaultScheduler;
        this.dataNodeConfig = dataNodeConfig;
        this.dataNodeApis = dataNodeApis;
        this.dataNodeApis.setPeerDataNodes(this);
    }

    //设置和NameNode通讯组件
    public void setNameNodeClient(NameNodeClient nameNodeClient) {
        this.dataNodeApis.setNameNodeClient(nameNodeClient);
    }

    /**
     * <pre>
     * 从目标DataNode获取文件
     *
     *  发送一个GET_FILE的网络包, 会被Peer DataNode收到, 从而把文件发送过来
     *
     *  这里有两种可能:
     *  1. datanode01和datanode02没有建立连接:
     *
     *      1.1 如果当前实例是datanode01, 会主动发起一个连接:datanode01 -> datanode02
     *      1.2 如果当前实例是datanode02, 会主动发起一个连接:datanode02 -> datanode01
     *
     *  2. 如果datanode02和datanode02已经建立连接,则会从连接池中获取到对应的连接,发送一个GET_FILE请求,从而获取文件
     *
     * </pre>
     *
     * @param hostname 主机名
     * @param port     端口号
     * @param filename 文件名
     */
    public void getFileFromPeerDataNode(String hostname, int port, String filename) throws InterruptedException {
        GetFileRequest request = GetFileRequest.newBuilder()
                .setFilename(filename)
                .build();//获取文件请求
        NettyPacket nettyPacket = NettyPacket.buildPacket(request.toByteArray(), PacketType.GET_FILE);
        PeerDataNode peerDataNode = maybeConnectPeerDataNode(hostname, port);//检查是否有连接,没有就新建,有就找到
        peerDataNode.send(nettyPacket);//往PeerDataNode发送网络包
        log.info("PeerDataNode发送GET_FILE请求,请求下载文件:[hostname={}, port={}, filename={}]", hostname, port, filename);
    }


    private PeerDataNode maybeConnectPeerDataNode(String hostname, int port) throws InterruptedException {
        synchronized (this) {
            String peerDataNode = hostname + ":" + port;
            PeerDataNode peer = dataNodeChannelMap.get(peerDataNode);
            if (peer == null) {//新建连接
                NetClient netClient = new NetClient("DataNode-PeerNode-" + hostname, defaultScheduler);
                netClient.addHandlers(Collections.singletonList(dataNodeApis));
                netClient.addConnectListener(connected -> sendPeerNodeAwareRequest(netClient));//监听
                netClient.connect(hostname, port);//启动连接
                netClient.ensureConnected();//同步等待确保连接建立,堵塞了就等会儿,直到重新连接建立
                peer = new PeerDataNodeClient(netClient, dataNodeConfig.getDataNodeId());
                dataNodeChannelMap.put(peerDataNode, peer);
                log.info("新建PeerDataNode的链接:{}", peerDataNode);
            } else {
                log.info("从保存的连接中获取PeerDataNode的链接:{}", peerDataNode);
            }
            return peer;//返回目标datanode
        }
    }

    private void sendPeerNodeAwareRequest(NetClient netClient) throws InterruptedException {
        PeerNodeAwareRequest request = PeerNodeAwareRequest.newBuilder()
                .setPeerDataNode(dataNodeConfig.getDataNodeTransportServer())
                .setDataNodeId(dataNodeConfig.getDataNodeId())
                .build();//其他PeerDataNode
        NettyPacket nettyPacket = NettyPacket.buildPacket(request.toByteArray(),
                PacketType.DATA_NODE_PEER_AWARE);
        log.info("连接上其他PeerDataNode了, 发送一个通知网络包:{}", request.getPeerDataNode());
        netClient.send(nettyPacket);//发送网络包
    }

    /**
     * DataNodeServer识别出是DataNode Peer后将连接保存起来
     *
     * <pre>
     *
     * 这里可能出现一种情况, 两个DataNode节点同时发起连接
     *   datanode01 -> datanode02
     *   datanode02 -> datanode01
     *
     * 所以每个DataNode定义一个ID,遇到这种情况的时候,DataNode ID更小的断开连接
     *
     * 考虑这样的场景:
     *   datanode01 : id = 1
     *   datanode02 : id = 2
     *
     * 两个DataNode同时发起连接:
     *
     *  1.  datanode01往datanode02发起连接,带上自己的id:
     *           datanode01的连接集合: [datanodeId=2]
     *
     *  2.  datanode02往datanode01发起连接,带上自己的id:
     *          datanode02的连接集合: [datanodeId=1]
     *
     *  3.  datanode02收到datanode01的链接,发现新连接的dataNodeId=1, 维护的连接集合中已存在该连接:
     *       3.1 如果自身的id比新的连接要大,则关闭新的连接
     *       3.2 如果自身的id比新的连接要小,则关闭原本自身作为客户端的链接
     *       结果是保留链接 : [datanode02 -> datanode01]
     *
     *  4.  datanode01收到datanode02的链接,发现新连接的dataNodeId=2, 维护的连接集合中已存在该连接:
     *       4.1 如果自身的id比新的连接要大,则关闭新的连接
     *       4.2 如果自身的id比新的连接要小,则关闭原本自身作为客户端的链接
     *        结果是保留链接 : [datanode02 -> datanode01]
     *
     * </pre>
     *
     * @param peerDataNode Peer Node
     * @param channel      连接
     * @param dataNodeId   DataNode ID
     */
    public void addPeerNode(String peerDataNode, int dataNodeId, SocketChannel channel, int selfDataNodeId) {
        synchronized (this) {
            PeerDataNode oldPeer = dataNodeChannelMap.get(peerDataNode);
            PeerDataNode newPeer = new PeerDataNodeServer(channel, dataNodeId);
            if (oldPeer == null) {
                log.info("收到PeerDataNode的通知网络包, 保存连接以便下一次使用");
                dataNodeChannelMap.put(peerDataNode, newPeer);
            } else {
                if (oldPeer instanceof PeerDataNodeServer && newPeer.getDataNodeId() == oldPeer.getDataNodeId()) {
                    // 此种情况为断线重连, 需要更新channel
                    PeerDataNodeServer peerDataNodeServer = (PeerDataNodeServer) oldPeer;
                    peerDataNodeServer.setSocketChannel(channel);
                    log.info("PeerDataNode断线重连,更新channel: {}", oldPeer.getDataNodeId());
                } else {
                    // 此种情况为本身作为客户端以及发起连接,但是作为服务端同样收到了链接请求
                    // 首先判断是不是旧的连接已经不可用,如果已经不可用的话,直接替换新的
                    if (!oldPeer.isConnected()) {//旧的不能用了
                        oldPeer.close();//旧的关闭
                        dataNodeChannelMap.put(peerDataNode, newPeer);
                        log.info("旧的连接已经失效,则关闭旧的连接, 并替换链接: {}", oldPeer.getDataNodeId());
                        return;
                    }
                    if (selfDataNodeId > dataNodeId) {//新ID小于自己的
                        newPeer.close();//新的关闭
                        log.info("新的连接dataNodeId比较小,关闭新的连接: {}", newPeer.getDataNodeId());
                    } else {
                        oldPeer.close();//新ID大于等于自己的 就把旧的关了
                        dataNodeChannelMap.put(peerDataNode, newPeer);
                        log.info("新的连接dataNodeId比较大,则关闭旧的连接, 并替换链接: {}", oldPeer.getDataNodeId());
                    }
                }
            }
        }
    }

    /**
     * 优雅停止
     */
    public void shutdown() {
        log.info("Shutdown PeerDataNodes");
        for (PeerDataNode peerDataNode : dataNodeChannelMap.values()) {
            peerDataNode.close();
        }
    }

    /**
     * 表示一个DataNode节点的连接
     */
    private interface PeerDataNode {//接口
        /**
         * 往 Peer DataNode 发送网络包, 如果连接断开了,会同步等待连接重新建立
         *
         * @param nettyPacket 网络包
         * @throws InterruptedException 中断异常
         */
        void send(NettyPacket nettyPacket) throws InterruptedException;//发送网络包

        /**
         * 关闭连接
         */
        void close();

        /**
         * 获取DataNodeId
         *
         * @return DataNode ID
         */
        int getDataNodeId();

        /**
         * 是否连接上
         *
         * @return 是否连接上
         */
        boolean isConnected();

    }

    private static abstract class AbstractPeerDataNode implements PeerDataNode {
        private int dataNodeId;

        public AbstractPeerDataNode(int dataNodeId) {
            this.dataNodeId = dataNodeId;
        }

        @Override
        public int getDataNodeId() {//获取DataNode的ID
            return dataNodeId;
        }
    }


    /**
     * 表示和PeerDataNode的连接,当前DataNode作为客户端
     */
    private static class PeerDataNodeClient extends AbstractPeerDataNode {
        private NetClient netClient;


        public PeerDataNodeClient(NetClient netClient, int dataNodeId) {
            super(dataNodeId);
            this.netClient = netClient;
        }

        @Override
        public void send(NettyPacket nettyPacket) throws InterruptedException {
            netClient.send(nettyPacket);//发送网络包
        }

        @Override
        public void close() {
            netClient.shutdown();//关闭连接
        }

        @Override
        public boolean isConnected() {
            return netClient.isConnected();//检查是否连接上
        }
    }

    /**
     * 表示和PeerDataNode的连接,当前DataNode作为服务端
     */
    private static class PeerDataNodeServer extends AbstractPeerDataNode {
        private volatile SocketChannel socketChannel;//套连接字通道

        public PeerDataNodeServer(SocketChannel socketChannel, int dataNodeId) {
            super(dataNodeId);
            this.socketChannel = socketChannel;
        }

        public void setSocketChannel(SocketChannel socketChannel) {//设置socketchannel
            synchronized (this) {//加锁
                this.socketChannel = socketChannel;
                notifyAll();//释放锁,通知所有等待这个对象控制权的线程继续运行
            }
        }

        @Override
        public void send(NettyPacket nettyPacket) throws InterruptedException {
            synchronized (this) {//加锁
                // 如果这里断开连接了,会一直等待直到客户端会重新建立连接
                while (!socketChannel.isActive()) {//当阻塞时
                    try {
                        wait(10);//等待
                    } catch (InterruptedException e) {//捕获异常
                        log.error("PeerDataNodeServer#send has Interrupted !!");
                    }
                }
            }
            socketChannel.writeAndFlush(nettyPacket);//向客户端返回nettyPacket
        }

        @Override
        public void close() {
            socketChannel.close();//关闭连接
        }

        @Override
        public boolean isConnected() {//检查是否连接上
            return socketChannel != null && socketChannel.isActive();
        }
    }
}

RelicateManager.java

功能:复制管理组件

\

private class ReplicateWorker implements Runnable {
    @Override
    public void run() {
        try {
            ReplicaCommand command = replicaCommandQueue.poll();
            if (command == null) {
                return;
            }
            if (CommandType.REPLICA_COPY.getValue() == command.getCommand()) {
                log.info("收到副本复制任务:[hostname={}, filename={}]", command.getHostname(), command.getFilename());
                peerDataNodes.getFileFromPeerDataNode(command.getHostname(), command.getPort(), command.getFilename());
            } 
            else if (CommandType.REPLICA_REMOVE.getValue() == command.getCommand()) {
                //存储管理器中文件路径
                String absolutePathByFileName = storageManager.getAbsolutePathByFileName(command.getFilename());
                //在定义好的路径创建文件
                File file = new File(absolutePathByFileName);
                long length = 0;
                if (file.exists()) {//file存在,得到文件长度
                    length = file.length();
                }
                FileUtil.delete(absolutePathByFileName);//删除文件
                if (length > 0) {
                    nameNodeClient.informReplicaRemoved(command.getFilename(), length);//上报文件副本信息
                }
                log.info("收到副本删除任务:[filename={}, path={}]", command.getFilename(), absolutePathByFileName);
            }
        } catch (Exception e) {//捕获异常
            log.info("ReplicateWorker处理副本任务异常:", e);
        }
    }
    }

4、server

server分为http, locate文件夹和一系列服务提供文件,其目录结构如下:

server
    │   DataNodeApis.java
    │   DataNodeServer.java
    │   DefaultFileTransportCallback.java
    │   MultiPortChannelInitializer.java
    │   StorageInfo.java
    │   StorageManager.java
    │
    ├───http
    │       HttpFileServerHandler.java
    │
    └───locate
            AbstractFileLocator.java
            AesFileLocator.java
            FileLocator.java
            FileLocatorFactory.java
            Md5FileLocator.java
            Sha1FileLocator.java
            SimpleFileLocator.java

1) http文件夹

这个文件夹下的HttpFileServerHandler 负责实现DataNode使用http协议与其他节点通信,主要是用于传输文件,它的构造函数需要一个StorageManager作为参数,继承了SimpleChannelInBoundHandler

HttpFileServerHandler 由重写的channelRead0()方法负责具体实现,其中会判断http请求下载文件是否合法,若否则由sendError()方法构造对应的HttpResponse返回错误信息

2) locate文件夹

这个文件夹中的类实现了根据文件的绝对路径查找文件位置(因为文件名经过加密所以与原来的不一样),接口,抽象类,与实现文件的关系如下,文件名分别可以用aes, sha1, md5加密,或者不加密,四个Locator实现不不同的加密方法

3) 其他server需要提供的服务

DataNodeApis

DataNodeApis继承了AbstractChannelHandler抽象类,重写了interestPackageTypes()和handlePackage()方法,可以根据不同NettyPackage的类型选择不同的处理方式

DataNodeServer

DataNodeServer类是DataNode提供服务的实体,它有私有的DataNodeApis、StorageManager、NetServer、DataNodeConfig、PeerDataNodes。start()和shutdown方法则是调用其私有属性的对应start()和shutdown(),代码如下

public class DataNodeServer {

    private DataNodeApis dataNodeApis;
    private StorageManager storageManager;
    private NetServer netServer;
    private DataNodeConfig dataNodeConfig;
    private PeerDataNodes peerDataNodes;

    public DataNodeServer(DataNodeConfig dataNodeConfig, DefaultScheduler defaultScheduler, StorageManager storageManager,
                          PeerDataNodes peerDataNodes, DataNodeApis dataNodeApis) {
        this.dataNodeConfig = dataNodeConfig;
        this.peerDataNodes = peerDataNodes;
        this.storageManager = storageManager;
        this.dataNodeApis = dataNodeApis;
        this.netServer = new NetServer("DataNode-Server", defaultScheduler, dataNodeConfig.getDataNodeWorkerThreads());
    }

    /**
     * 启动
     */
    public void start() throws InterruptedException {
        // 用于接收PeerDataNode发过来的通知信息
        MultiPortChannelInitializer multiPortChannelInitializer = new MultiPortChannelInitializer(dataNodeConfig, storageManager);
        multiPortChannelInitializer.addHandlers(Collections.singletonList(dataNodeApis));
        this.netServer.setChannelInitializer(multiPortChannelInitializer);
        this.netServer.bind(Arrays.asList(dataNodeConfig.getDataNodeTransportPort(), dataNodeConfig.getDataNodeHttpPort()));
    }

    /**
     * 优雅停止
     */
    public void shutdown() {
        log.info("Shutdown DataNodeServer");
        this.netServer.shutdown();  // 关闭网络服务器
        this.peerDataNodes.shutdown();  // 关闭所有通信的DataNode
    }

}
DefaultFileTransportCallback

DefaultFileTransportCallback类负责检测文件传输,可以获得文件路径,在文件传输过程中,或是传输完成时输出传输速率,记录收到的文件,它是接口FileTransportCallback的实现类

MultiPortChannelInitializer

MultiPortChannelInitializer继承自BaseChannelInitializer,用于建立连接时添加初始化channel,除了构造函数外,它只重写了initChannel()方法

@Override
    protected void initChannel(SocketChannel ch) {
        int localPort = ch.localAddress().getPort();
        if (localPort == dataNodeConfig.getDataNodeTransportPort()) {
            super.initChannel(ch);
        } else if (localPort == dataNodeConfig.getDataNodeHttpPort()) {
            ch.pipeline().addLast(new HttpServerCodec());  // 用于编码和解码
            ch.pipeline().addLast(new HttpObjectAggregator(65536));  // 会将httpMessage 和 HttpContent聚合到一个FullHttpRequest中或者FullHttpResponse中。
            /** ChunkedWriteHandler顾名思义:分块写入处理器,分批读取数据。比如我们通过ChunkedWriteHandler传递File文件,原理是  
            * -   1.将File分割成ChunkedFile,指定ChunkedSize和file的start offset以及 end offset
            * -   2.每次最多生成chunkedSize大小的message,每次分配堆内存bytebuf,然后将message发送到bytebuf并且填满
            * -   3.然后只要channel一直可写就一直发送
            */
            ch.pipeline().addLast(new ChunkedWriteHandler());
            ch.pipeline().addLast(new HttpFileServerHandler(storageManager));
        }
    }
StorageInfo

StorageInfo是一个记录存储信息的类,用于保存存储数据

@Data
public class StorageInfo {
    // 文件信息
    private List<FileInfo> files;
    // 已用空间
    private long storageSize;
    // 可用空间
    private long freeSpace;

    public StorageInfo() {
        this.storageSize = 0L;
    }
}
StorageManager

StorageManager类用于管理一个DataNode上存储的文件

5、DataNode.java

功能:DataNode启动类

\

main函数源代码:

public static void main(String[] args) {
        if (args == null || args.length == 0) {
            throw new IllegalArgumentException("配置文件不能为空");
        }
        // 初始化配置文件
        DataNodeConfig dataNodeConfig = null;
        try {
            Path path = Paths.get(args[0]);
            try (InputStream inputStream = Files.newInputStream(path)) {
                Properties properties = new Properties();
                properties.load(inputStream);
                dataNodeConfig = DataNodeConfig.parse(properties);
            }
            log.info("DataNode启动配置文件: {}", path.toAbsolutePath());
        } catch (Exception e) {
            log.error("无法加载配置文件 : ", e);
            System.exit(1);
        }
        // 读取args中的自定义配置(比如basedir),并修改默认配置
        parseOption(args, dataNodeConfig);
        try {
            DataNode datanode = new DataNode(dataNodeConfig);
            // shutdown hook是一个已初始化但尚未启动的线程,当JVM开始关闭时,便会启动datanode的shutdown线程
            Runtime.getRuntime().addShutdownHook(new Thread(datanode::shutdown));
            datanode.start();
        } catch (Exception e) {
            log.error("启动DataNode失败:", e);
            System.exit(1);
        }
    }

\


\