RocketMQ源码分析之Namesrv篇(一)

前言

Namesrv是RocketMQ最简单的一部分,骨头我们先挑最软的一根啃,本次引用的源码来于4.8.0版本,由于笔者水平有限欢迎大家批评指正。

名词介绍

NameSrv (Name Server)

NameSrv充当消息路由的提供者。生产者或消费者都能够通过NameSrv查找各个主题响应的BrokerIP列表。多个Namesrv实例组成集群,但互相独立,没有信息交换。

                             摘自RocketMQ官网

问题引出

从上面的官网对Namesrv的介绍,我们可以引出如下三个问题。

  • Namesrv是如何知道生产者与消费者状态信息的。
  • 生产者与消费者是如何利用Namesrv获取路由信息的。
  • 多个Namesrv实例组成集群互相独立没有信息交换是如何保证高可用的。

从源码分析中寻找问题答案

下面我们将从源码角度看看RocketMQ是如何实现的,打开Namesrv的源码包,我们发现NameSrv的代码量并不多。

RocketMQ源码分析之Namesrv篇(一)_第1张图片
(1) 我们先对如图所示的接个类进行一个大致的介绍。

  • org.apache.rocketmq.namesrv.kvconfig.KVConfigManager:提供将Namesrv配置加载到内存和修改配置的一些方法。

  • org.apache.rocketmq.namesrv.kvconfig.KVConfigSerializeWrapper:提供对KVConfigManager序列化的方法,序列化与反序列化方法继承自org.apache.rocketmq.remoting.protocol.RemotingSerializable

  • org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor:用来处理消费者或生产者(客户端)和Broker发送过来的请求。

  • org.apache.rocketmq.namesrv.routeinfo.BrokerHousekeepingService:监听Broker状态。
  • org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager:负责保存RocektMQ集群中消息的路由信息。

  • org.apache.rocketmq.namesrv.NamesrvController:Namesrv的总控制器。

  • org.apache.rocketmq.namesrv.NamesrvStartup:Namesrv启动的入口。


    (2) 下面我们从Namesrv启动的流程如下进行源码分析(省略一些非关键代码例如日志打印与异常捕获等等)。

// org.apache.rocketmq.namesrv.NamesrvStartup#main0

public static NamesrvController main0(String[] args) {
    try {
        // #①
        NamesrvController controller = createNamesrvController(args);
        // #②
        start(controller);
        // #③
        String tip = "The Name Server boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer();
        log.info(tip);
        System.out.printf("%s%n", tip);
        return controller;
    } catch (Throwable e) {
        e.printStackTrace();
        System.exit(-1);
    }
    return null;
}

:创建一个Namesrv总控制器。


:启动Namesrv总控制器。


:Namesrv启动成功时就会打印此日志。

// org.apache.rocketmq.namesrv.NamesrvStartup#start

      public static NamesrvController start(final NamesrvController controller) throws Exception {

        if (null == controller) {
            throw new IllegalArgumentException("NamesrvController is null");
        }
        //#①
        boolean initResult = controller.initialize();
        if (!initResult) {
        //#②
            controller.shutdown();
            System.exit(-3);
        }
        //#③
        Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                controller.shutdown();
                return null;
            }
        }));
        //#④
        controller.start();
        return controller;
    }

:初始化NamesrvController。


:初始化失败时需要关闭NamesrvController。


:注册JVM钩子函数,在JVM正常停机的前一刻关闭NamesrvController。


:正常初始化完之后启动NamesrvController。

// org.apache.rocketmq.namesrv.NamesrvStartup#initialize
  public boolean initialize() {

        // #①
        this.kvConfigManager.load();

        // todo 与远程通信有关的配置,后续介绍。
        this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
        // #②
        this.remotingExecutor =
      Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
        // #③
        this.registerProcessor();
        // #④
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                NamesrvController.this.routeInfoManager.scanNotActiveBroker();
            }
        }, 5, 10, TimeUnit.SECONDS);
        // #⑤
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                NamesrvController.this.kvConfigManager.printAllPeriodically();
            }
        }, 1, 10, TimeUnit.MINUTES);
        return true;
    }

:将Namesrv的配置加载到内存中。


:此线程池remotingExecutor专门用来处理客户端(生产者和消费者)Broker发送过来的请求DefaultRequestProcessor。


:将DefaultRequestProcessor与线程池remotingExecutor绑定起来。


:scheduledExecutorService定时任务执行线程池,每10s扫描检查一次当前存活的broker是否还存活。


:定时任务执行线程池,每10s打印一次Namesrv的配置。

至此Namesrv的启动流程我们已经全部分析完。

核心代码分析

// org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor#processRequest
// Namesrv对外暴露的接口(处理客户端(消费者和生产者)和Broker发送的请求)
// 此方法代码比较简单这里不进行详细分析了
 public RemotingCommand processRequest(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException
       
// org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager

    // #① Namesrv默认在两分钟内没有收到Broker的信息,则任务Broker已宕机
    private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
    // #② 读写锁
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    // #③ 主题与队列映射
    private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
    // #④ Broker名称与Broker映射,一个主从Broker对应的Broker名称是相同的
    private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
    // #⑤ RocketMQ集群名称与Broker的映射,每个主从Broker都有一个Broker名称(同一个主从Broker内只有BrokerId不一样 id=0 的是主节点,id≠0 的是从节点)
    private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
    // #⑥ 当前存活的Broker信息,
    private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
    // #⑦ 消息过滤
    private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;

:这里需要详细说明一下,Namesrv每10s扫描一次所有的Broker(通过上面所说的scheduledExecutorService定时任务线程池进行扫描)根据心跳包lastUpdateTimestamp的时间当前系统时间的差值是否超过BROKER_CHANNEL_EXPIRED_TIME来判断Broker是否存活,从这一点我们也可以看出此数据并非实时的,从而客户端无法实时感知Broker的状态。

// org.apache.rocketmq.namesrv.routeinfo.BrokerLiveInfo
class BrokerLiveInfo {
    private long lastUpdateTimestamp;
    // # ① 利用版本号解决ABC问题,如果不懂ABA问题的可以百度一下。
    private DataVersion dataVersion;
    private Channel channel;
    private String haServerAddr;
    // ...
    }
  public RegisterBrokerResult registerBroker(
        final String clusterName,final String brokerAddr,
        final String brokerName,final long brokerId,
        final String haServerAddr,final TopicConfigSerializeWrapper topicConfigWrapper,
        final List<String> filterServerList,final Channel channel) {
        RegisterBrokerResult result = new RegisterBrokerResult();
        try {
            try {
              //#① 由于下列操作的容器都是非线程安全的,因此需要通过写锁来避免线程安全问题
                this.lock.writeLock().lockInterruptibly();
                Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
                if (null == brokerNames) {
                    brokerNames = new HashSet<String>();
                    this.clusterAddrTable.put(clusterName, brokerNames);
                }
                brokerNames.add(brokerName);
                boolean registerFirst = false;
                BrokerData brokerData = this.brokerAddrTable.get(brokerName);
                if (null == brokerData) {
                    registerFirst = true;
                    brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());
                    this.brokerAddrTable.put(brokerName, brokerData);
                }
                Map<Long, String> brokerAddrsMap = brokerData.getBrokerAddrs();
                //Switch slave to master: first remove <1, IP:PORT> in namesrv, then add <0, IP:PORT>
                //The same IP:PORT must only have one record in brokerAddrTable
                Iterator<Entry<Long, String>> it = brokerAddrsMap.entrySet().iterator();
                while (it.hasNext()) {
                    Entry<Long, String> item = it.next();
                    if (null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey()) {
                        it.remove();
                    }
                }
                String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
                registerFirst = registerFirst || (null == oldAddr);

                if (null != topicConfigWrapper
                    && MixAll.MASTER_ID == brokerId) {
                    if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())
                        || registerFirst) {
                        ConcurrentMap<String, TopicConfig> tcTable =
                            topicConfigWrapper.getTopicConfigTable();
                        if (tcTable != null) {
                            for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
                                this.createAndUpdateQueueData(brokerName, entry.getValue());
                            }
                        }
                    }
                }
                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);
                    }
                }
                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;
    }

上面的代码比较好懂就是去更新上面所说的几个容器,第一次更新时new一个非第一次更新时直接添加。

总结

从上面的源码我们可以找到一开始我们抛出的三个问题的答案。


:Namesrv通过定时扫描Broker主动上报心跳信息来判断Broker是否存活。


:通过org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor#processRequest方法获取org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager类维护的路由信息。


:通过源码分析我们没有看到Namesrv模块没有提供任何Namesrv之间通信的接口,而且每个Namesrv都维护一套完整的Broker集群、客户端集群(生产者和消费者)信息,因此只要RocketMQ集群充还存在一个可用的Namesrv那么集群就能继续正常工作。

你可能感兴趣的:(rocketmq,java,kafka,消息队列,架构,源码)