Namesrv源码解析

NameSrvStartup 启动入口类

  1. 构建命令行参数 -c 配置文件 和 -p 打印配置项
  2. 将参数映射到NamesrvConfig和NettyServerConfig中,如listenPort可以配置启动端口
  3. 创建NamesrvController并启动

NamesrvController

NamesrvController 构造函数

public NamesrvController(NamesrvConfig namesrvConfig, NettyServerConfig nettyServerConfig) {
    this.namesrvConfig = namesrvConfig;
    this.nettyServerConfig = nettyServerConfig;
    this.kvConfigManager = new KVConfigManager(this);
    this.routeInfoManager = new RouteInfoManager();
    this.brokerHousekeepingService = new BrokerHousekeepingService(this);
    this.configuration = new Configuration(
        log,
        this.namesrvConfig, this.nettyServerConfig
    );
    this.configuration.setStorePathFromConfig(this.namesrvConfig, "configStorePath");
}
  • namesrvConfig 主要有rocketmqHome(rocketMq的Home目录)、kvConfigPath 等属性
  • nettyServerConfig 远程通信的一些配置项 如listenPort 端口
  • kvConfigManager kv配置管理器
  • routeInfoManager 路由信息管理器
  • brokerHousekeepingService 该类实现了ChannelEventListener接口,主要用于管理连接创建、关闭、销毁、异常时的一些处理
  • configuration 将namesrvConfig与nettyServerConfig封装在这个配置类中

controller.initialize() 初始化

public boolean initialize() {
    //根据kvConfigPath加载kv配置
    this.kvConfigManager.load();

    //创建一个netty远程服务器
    this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);

    //默认处理器的线程池
    this.remotingExecutor =
        Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));

    //注册默认的请求处理器
    this.registerProcessor();

    //定时任务 scanNotActiveBroker 每隔10秒扫描剔除不活跃的broker
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

        @Override
        public void run() {
            NamesrvController.this.routeInfoManager.scanNotActiveBroker();
        }
    }, 5, 10, TimeUnit.SECONDS);

    //定时任务 每隔10分钟打印全部kv信息
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

        @Override
        public void run() {
            NamesrvController.this.kvConfigManager.printAllPeriodically();
        }
    }, 1, 10, TimeUnit.MINUTES);

    return true;
}

1.根据kvConfigPath加载kv配置

2.创建一个netty远程服务器

3.创建默认处理器的线程池

4.注册默认的请求处理器

5.定时任务 scanNotActiveBroker 扫描剔除不活跃的broker

6.定时任务 每隔10分钟打印全部kv信息

controller.start() 启动

public void start() throws Exception {
    //启动远程服务器
    this.remotingServer.start();
}

1.调用remotingServer.start启动远程服务器,即NettyRemotingServer#start

remotingServer.start() 启动服务器

public void start() {
    this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
        nettyServerConfig.getServerWorkerThreads(),
        new ThreadFactory() {

            private AtomicInteger threadIndex = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "NettyServerCodecThread_" + this.threadIndex.incrementAndGet());
            }
        });

    ServerBootstrap childHandler =
        this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
            .channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 1024)
            .option(ChannelOption.SO_REUSEADDR, true)
            .option(ChannelOption.SO_KEEPALIVE, false)
            .childOption(ChannelOption.TCP_NODELAY, true)
            .childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSndBufSize())
            .childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketRcvBufSize())
            .localAddress(new InetSocketAddress(this.nettyServerConfig.getListenPort()))
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline()
                        .addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME,
                            new HandshakeHandler(TlsSystemConfig.tlsMode))
                        .addLast(defaultEventExecutorGroup,
                            new NettyEncoder(),
                            new NettyDecoder(),
                            new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
                            new NettyConnectManageHandler(),
                            new NettyServerHandler()
                        );
                }
            });

    if (nettyServerConfig.isServerPooledByteBufAllocatorEnable()) {
        childHandler.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
    }

    try {
        ChannelFuture sync = this.serverBootstrap.bind().sync();
        InetSocketAddress addr = (InetSocketAddress) sync.channel().localAddress();
        this.port = addr.getPort();
    } catch (InterruptedException e1) {
        throw new RuntimeException("this.serverBootstrap.bind().sync() InterruptedException", e1);
    }

    if (this.channelEventListener != null) {
        this.nettyEventExecutor.start();
    }

    this.timer.scheduleAtFixedRate(new TimerTask() {

        @Override
        public void run() {
            try {
                NettyRemotingServer.this.scanResponseTable();
            } catch (Throwable e) {
                log.error("scanResponseTable exception", e);
            }
        }
    }, 1000 * 3, 1000);
}

这里的代码就是netty构建服务器的代码,主要关注以下几点

  • options参数
    • ChannelOption.SO_BACKLOG 对应的是tcp/ip协议listen函数中的backlog参数,函数listen(int socketfd,int backlog)用来初始化服务端可连接队列,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小
    • ChannelOption.SO_REUSEADDR 一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用
    • ChannelOption.SO_KEEPALIVE 当设置为true的时候,TCP会实现监控连接是否有效,当连接处于空闲状态的时候,超过了2个小时,本地的TCP实现会发送一个数据包给远程的 socket,如果远程没有发回响应,TCP会持续尝试11分钟,知道响应为止,如果在12分钟的时候还没响应,TCP尝试关闭socket连接。
    • ChannelOption.TCP_NODELAY ChannelOption.TCP_NODELAY参数对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关,Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,虽然该方式有效提高网络的有效负载,但是却造成了延时,而该参数的作用就是禁止使用Nagle算法,使用于小数据即时传输,于TCP_NODELAY相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。
    • ChannelOption.SO_SNDBUF 发送缓冲区大小
    • ChannelOption.SO_RCVBUF 接收缓冲区大小
  • handler 处理器
    • NettyServerHandler 核心处理器 处理业务请求

NettyServerHandler

public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
    final RemotingCommand cmd = msg;
    if (cmd != null) {
        switch (cmd.getType()) {
            case REQUEST_COMMAND:
                processRequestCommand(ctx, cmd);
                break;
            case RESPONSE_COMMAND:
                processResponseCommand(ctx, cmd);
                break;
            default:
                break;
        }
    }
}
  1. processRequestCommand(ctx, cmd); 处理请求
  2. processResponseCommand(ctx, cmd); 处理响应
processRequestCommand
//根据cmd code获取对应的处理pair 如果没有 则使用默认处理器 defaultRequestProcessor
final Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
final Pair<NettyRequestProcessor, ExecutorService> pair = null == matched ? this.defaultRequestProcessor : matched;
//处理请求
final RemotingCommand response = pair.getObject1().processRequest(ctx, cmd);

1.根据cmd code获取对应的处理pair 如果没有 则使用默认处理器 defaultRequestProcessor

2.处理请求

defaultRequestProcessor 默认处理器的处理逻辑

public RemotingCommand processRequest(ChannelHandlerContext ctx,
    RemotingCommand request) throws RemotingCommandException {
    if (log.isDebugEnabled()) {
        log.debug("receive request, {} {} {}",
            request.getCode(),
            RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
            request);
    }

    switch (request.getCode()) {
        //创建或跟新kv配置
        case RequestCode.PUT_KV_CONFIG:
            return this.putKVConfig(ctx, request);
        //获取kv配置
        case RequestCode.GET_KV_CONFIG:
            return this.getKVConfig(ctx, request);
        //删除kv配置
        case RequestCode.DELETE_KV_CONFIG:
            return this.deleteKVConfig(ctx, request);
        //注册broker
        case RequestCode.REGISTER_BROKER:
            Version brokerVersion = MQVersion.value2Version(request.getVersion());
            if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
                return this.registerBrokerWithFilterServer(ctx, request);
            } else {
                //兼容低版本
                return this.registerBroker(ctx, request);
            }
        //注销broker
        case RequestCode.UNREGISTER_BROKER:
            return this.unregisterBroker(ctx, request);
        //获取指定TOPIC的路由信息
        case RequestCode.GET_ROUTEINTO_BY_TOPIC:
            return this.getRouteInfoByTopic(ctx, request);
        //获取broker集群信息
        case RequestCode.GET_BROKER_CLUSTER_INFO:
            return this.getBrokerClusterInfo(ctx, request);
        //删除一个Broker的写权限
        case RequestCode.WIPE_WRITE_PERM_OF_BROKER:
            return this.wipeWritePermOfBroker(ctx, request);
        //获取所有主题信息
        case RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER:
            return getAllTopicListFromNameserver(ctx, request);
        //删除主题信息
        case RequestCode.DELETE_TOPIC_IN_NAMESRV:
            return deleteTopicInNamesrv(ctx, request);
        //获取kv配置
        case RequestCode.GET_KVLIST_BY_NAMESPACE:
            return this.getKVListByNamespace(ctx, request);
        //获取指定集群所有的主题
        case RequestCode.GET_TOPICS_BY_CLUSTER:
            return this.getTopicsByCluster(ctx, request);
        //系统会将集群名称、broker名称作为默认topic创建。现在获取这类topic
        case RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_NS:
            return this.getSystemTopicListFromNs(ctx, request);
        case RequestCode.GET_UNIT_TOPIC_LIST:
            return this.getUnitTopicList(ctx, request);
        case RequestCode.GET_HAS_UNIT_SUB_TOPIC_LIST:
            return this.getHasUnitSubTopicList(ctx, request);
        case RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST:
            return this.getHasUnitSubUnUnitTopicList(ctx, request);
        //更新properties请求
        case RequestCode.UPDATE_NAMESRV_CONFIG:
            return this.updateConfig(ctx, request);
        //获取properties内容
        case RequestCode.GET_NAMESRV_CONFIG:
            return this.getConfig(ctx, request);
        default:
            break;
    }
    return null;
}

重点看一下REGISTER_BROKER 注册broker的逻辑 registerBrokerWithFilterServer

//注册broker
RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(
    requestHeader.getClusterName(),//集群名
    requestHeader.getBrokerAddr(),//broker地址
    requestHeader.getBrokerName(),//broker名字
    requestHeader.getBrokerId(),//brokerId
    requestHeader.getHaServerAddr(),//集群地址
    registerBrokerBody.getTopicConfigSerializeWrapper(),
    registerBrokerBody.getFilterServerList(),
    ctx.channel());

可以看到内部是调用RouteInfoManager来注册broker信息,注册的主要信息参数有

  1. 集群名
  2. broker地址
  3. broker名字
  4. brokerId 0表示主节点
  5. 集群地址

RouteInfoManager#registerBroker

public RegisterBrokerResult registerBroker(
    final String clusterName,//集群名
    final String brokerAddr,//broker地址
    final String brokerName,//broker名字
    final long brokerId,//brokerId
    final String haServerAddr,//集群地址
    final TopicConfigSerializeWrapper topicConfigWrapper,//主题配置信息
    final List<String> filterServerList,//过滤服务器
    final Channel channel) {
    RegisterBrokerResult result = new RegisterBrokerResult();
    try {
        try {
            //加写锁(读写锁 适合读多写少场景 读读不互斥)
            this.lock.writeLock().lockInterruptibly();
            //获取集群名为clusterName下的brokerNames
            Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
            if (null == brokerNames) {
                //不存在就创建一个
                brokerNames = new HashSet<String>();
                this.clusterAddrTable.put(clusterName, brokerNames);
            }
            //添加当前brokerName
            brokerNames.add(brokerName);

            boolean registerFirst = false;

            //获取brokerName对应的broker数据 brokerData内有主从broker地址信息 brokerId为0的表示主节点
            BrokerData brokerData = this.brokerAddrTable.get(brokerName);
            if (null == brokerData) {
                //第一次注册 添加brokerData信息
                registerFirst = true;
                brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());
                this.brokerAddrTable.put(brokerName, brokerData);
            }
            //跟新broker信息
            String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
            registerFirst = registerFirst || (null == oldAddr);

            if (null != topicConfigWrapper
                && MixAll.MASTER_ID == brokerId) {
                //判断borker主题配置信息有无改变 或者是否是第一次注册
                if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())
                    || registerFirst) {
                    ConcurrentMap<String, TopicConfig> tcTable =
                        topicConfigWrapper.getTopicConfigTable();
                    if (tcTable != null) {
                        //跟新topic配置信息
                        for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
                            this.createAndUpdateQueueData(brokerName, entry.getValue());
                        }
                    }
                }
            }
            //跟新broker的心跳存活信息
            BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr,
                new BrokerLiveInfo(
                    System.currentTimeMillis(),
                    topicConfigWrapper.getDataVersion(),
                    channel,
                    haServerAddr));
            if (null == prevBrokerLiveInfo) {
                log.info("new broker registered, {} HAServer: {}", brokerAddr, haServerAddr);
            }
            //跟新过滤服务器信息
            if (filterServerList != null) {
                if (filterServerList.isEmpty()) {
                    this.filterServerTable.remove(brokerAddr);
                } else {
                    this.filterServerTable.put(brokerAddr, filterServerList);
                }
            }
            //如果不是主节点 则找出主节点并将返回结果设置HaServerAddr地址和MasterAddr地址
            if (MixAll.MASTER_ID != brokerId) {
                String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);
                if (masterAddr != null) {
                    BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr);
                    if (brokerLiveInfo != null) {
                        result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());
                        result.setMasterAddr(masterAddr);
                    }
                }
            }
        } finally {
            this.lock.writeLock().unlock();
        }
    } catch (Exception e) {
        log.error("registerBroker Exception", e);
    }

    return result;
}

RouteInfoManager 路由信息管理器

关注一下路由信息管理里面的几个核心对象属性

1.HashMap<String/* topic */, List> topicQueueTable

2.HashMap<String/* brokerName */, BrokerData> brokerAddrTable;

3.HashMap<String/* clusterName /, Set<String/ brokerName */>> clusterAddrTable;

4.HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;

5.HashMap<String/* brokerAddr /, List/ Filter Server */> filterServerTable;

topicQueueTable 主题队列信息

topicQueueTable保存了主题在哪些 broker上注册,每个broker对应的读写队列数量以及读写权限

key为主题topic,value为List队列信息集合

QueueData属性如下

//broker名字
private String brokerName;

//一般来说读写队列数量一致,如果不一致就会出现很多问题。

//读队列数量
private int readQueueNums;
//写队列数量
private int writeQueueNums;

//6:同时支持读写 4:禁写 2:禁读
private int perm;

private int topicSynFlag;

1.brokerName broker名字

2.readQueueNums 读队列数量

3.writeQueueNums 写队列数量

4.perm 同时支持读写 4:禁写 2:禁读

5.topicSynFlag 尚不明确

brokerAddrTable broker地址信息

key为brokerName,value为BrokerData

brokerAddrTable保存了brokerName对应的主从节点地址信息

BrokerData属性如下:

private String cluster;
private String brokerName;
private HashMap<Long/* brokerId */, String/* broker address */> brokerAddrs;

1.cluster 所属集群

  1. brokerName broker名字
  2. brokerAddrs key为brokerId,value为broker的地址,brokerId=0表示主节点

clusterAddrTable 集群地址信息

clusterAddrTable保存了集群对应的brokerName集合

brokerLiveTable broker存活信息

brokerLiveTable保存了 broker的存活信息

key为brokerAddr broker地址 value为BrokerLiveInfo

BrokerLiveInfo属性如下:

private long lastUpdateTimestamp;
private DataVersion dataVersion;
private Channel channel;
private String haServerAddr;

1.lastUpdateTimestamp 最后一次跟新时间戳

2.dataVersion 数据版本

3.channel 通信通道

4.haServerAddr 集群地址

namesrv会定期的检测brokerLiveInfo,将2分钟没有跟新brokerLiveInfo的broker地址剔除

//扫描不活跃的broker
public void scanNotActiveBroker() {
    Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
    while (it.hasNext()) {
        Entry<String, BrokerLiveInfo> next = it.next();
        long last = next.getValue().getLastUpdateTimestamp();
        //关闭2分钟未发送心跳信息的broker
        if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
            //关闭netty channel
            RemotingUtil.closeChannel(next.getValue().getChannel());
            //剔除broker信息
            it.remove();
            log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME);
            this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
        }
    }
}

filterServerTable 过滤服务器地址

filterServerTable保存了broker地址对应的过滤服务器地址

key为brokerAddr broker地址,value为List/* Filter Server */> 过滤服务器地址

posted @ 2020-07-14 10:01  鹿慕叶  阅读(201)  评论(0编辑  收藏  举报