• RocketMQ(4.8.0)——Namesrv 服务


    Namesrv 服务

      Namesrv 在 RocketMQ 体系中,可以看做是一个 Topic 路由注册和管理、Broker注册和发现的角色。

    一、概述

      Namesrv 在 RocketMQ 体系中主要用于保存元数据、提高 Broker 的可用性。

      1.1 什么是 Namesrv

      在 RocketMQ 中如果有生产者、消费者加入或者掉线,Broker扩容或者掉线等各种异常场景,RocketMQ集群如何保证高可用呢?一个管理者或者协调者的角色应运而生。

      Namesrv 是专门针对 RocketMQ 开发的轻量级协调者,多个 Namesrv 节点可以组成一个 Namesrv 集群,帮助 RocketMQ 集群达到高可用。

      Namesrv 的主要功能是临时保存、管理 Topic 路由信息,各个Namesrv 节点是无状态的,即每两个 Namesrv 节点之间不通信,互相不知道彼此的存在。在 Broker、生产者消费者启动时,轮询全部配置的 Namesrv 节点,拉取路由信息。具体路由信息的代码路径:D: ocketmq-master amesrvsrcmainjavaorgapache ocketmq amesrv outeinfo 下各个 HashMap 保存的数据。

      1.2 Namesrv 核心数据结构和API

      Namesrv 中保存的数据被称为 Topic 路由信息,Topic 路由决定了 Topic 消息发送到哪些 Broker,消费者从哪些 Broker 消费消息。

      那么路由信息都包含哪些数据呢?路由数据结构的实现代码都在 D: ocketmq-master amesrvsrcmainjavaorgapache ocketmq amesrv outeinfoRouteInfoManager 类中,该类包含的数据结构如下:

    1 public class RouteInfoManager {
    2     private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
    3     private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
    4     private final ReadWriteLock lock = new ReentrantReadWriteLock();
    5     private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
    6     private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
    7     private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
    8     private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
    9     private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;

      BROKER_CHANNEL_EXPIRED_TIME:Broker 存活的时间周期,默认为120s。

    topicQueueTable:保存 Topic 和队列的信息,也叫真正的路由信息。一个 Topic 全部的 Queue 可能分部在不同的 Broker 中,也可能分部在同一个 Broker 中。

    brokerAddrTable:存储了 Broker 名字和 Broker 信息的对应信息。

    clusterAddrTable:集群和 Broker 的对应关系。

    brokerLiveTable:当前在线的 Broker 地址和 Broker 信息的对应关系。

    filterServerTable:过滤服务器信息。

     Namesrv 支持的全部 API 在 D: ocketmq-master amesrvsrcmainjavaorgapache ocketmq amesrvprocessorDefaultRequestProcessor.java,代码如下:

     1     @Override
     2     public RemotingCommand processRequest(ChannelHandlerContext ctx,
     3         RemotingCommand request) throws RemotingCommandException {
     4 
     5         if (ctx != null) {
     6             log.debug("receive request, {} {} {}",
     7                 request.getCode(),
     8                 RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
     9                 request);
    10         }
    11 
    12 
    13         switch (request.getCode()) {
    14             case RequestCode.PUT_KV_CONFIG:
    15                 return this.putKVConfig(ctx, request);
    16             case RequestCode.GET_KV_CONFIG:
    17                 return this.getKVConfig(ctx, request);
    18             case RequestCode.DELETE_KV_CONFIG:
    19                 return this.deleteKVConfig(ctx, request);
    20             case RequestCode.QUERY_DATA_VERSION:
    21                 return queryBrokerTopicConfig(ctx, request);
    22             case RequestCode.REGISTER_BROKER:            #Broker 注册自身信息到 Namesrv
    23                 Version brokerVersion = MQVersion.value2Version(request.getVersion());
    24                 if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
    25                     return this.registerBrokerWithFilterServer(ctx, request);
    26                 } else {
    27                     return this.registerBroker(ctx, request);
    28                 }
    29             case RequestCode.UNREGISTER_BROKER:          #Broker 取消注册自身信息到 Namesrv
    30                 return this.unregisterBroker(ctx, request);
    31             case RequestCode.GET_ROUTEINFO_BY_TOPIC:     #获取 Topic 路由信息
    32                 return this.getRouteInfoByTopic(ctx, request);
    33             case RequestCode.GET_BROKER_CLUSTER_INFO:
    34                 return this.getBrokerClusterInfo(ctx, request);
    35             case RequestCode.WIPE_WRITE_PERM_OF_BROKER:  #删除 Broker 的写权限
    36                 return this.wipeWritePermOfBroker(ctx, request);
    37             case RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER:      #获取全部 Topic 名字
    38                 return getAllTopicListFromNameserver(ctx, request);
    39 case RequestCode.DELETE_TOPIC_IN_NAMESRV: #删除 Topic 信息 40 return deleteTopicInNamesrv(ctx, request); 41 case RequestCode.GET_KVLIST_BY_NAMESPACE: 42 return this.getKVListByNamespace(ctx, request); 43 case RequestCode.GET_TOPICS_BY_CLUSTER: 44 return this.getTopicsByCluster(ctx, request); 45 case RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_NS: 46 return this.getSystemTopicListFromNs(ctx, request); 47 case RequestCode.GET_UNIT_TOPIC_LIST: 48 return this.getUnitTopicList(ctx, request); 49 case RequestCode.GET_HAS_UNIT_SUB_TOPIC_LIST: 50 return this.getHasUnitSubTopicList(ctx, request); 51 case RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST: 52 return this.getHasUnitSubUnUnitTopicList(ctx, request); 53 case RequestCode.UPDATE_NAMESRV_CONFIG: #更新 Namesrv 配置 54 return this.updateConfig(ctx, request); 55 case RequestCode.GET_NAMESRV_CONFIG: #获取 Namesrv 配置 56 return this.getConfig(ctx, request); 57 default: 58 break; 59 } 60 return null; 61 }

      1.3 Namesrv 和 Zookeeper

      曾几何时,RocketMQ 也采用了 Zookeeper 作为协调者,但是繁杂的运行机制和过多的依赖导致 RocketMQ 最终完全重新开发了一个零依赖、更简洁的 Namesrv 来替换 Zookeeper。事实证明,逻辑更简单、使用更简单、更轻量级的 Namesrv,效果更好。

    功能点 Zookeeper Namesrv
    角色 协调者 协调者
    配置保存 持久化到磁盘 保存内存
    是否支持选举
    数据一致性 强一致 弱一致,各个节点无状态,互不同通信,依靠心跳保持数据一致性
    是否高可用
    设计逻辑 支持Raft选举,逻辑复杂难懂,排查问题较难 CRUD,仅此而已

    二、Namesrv架构

      2.1 Namesrv 组件

            Broker:Broker 在启动时,将自己的元数据信息(包括 Broker 本身的元数据和该 Broker 种的 Topic 信息)上报 Namesrv,这部分信息也叫作 Topic 路由。

    Porduce:主要关注 Topic 路由。所谓 Topic 路由,表示这个 Topic 的消息可以通过路由知道消息流传到了哪些 Broker 中。如果有 Broker 宕机,Namesrv 会感知并告诉生产者,对生产者而言 Broker 是高可用的。

    Consumer:主要关注 Topic路由。消费者从 Namesrv 获取路由后才能知道存储订阅 Topic 消息的 Broker 地址,也才能到 Broker 拉取消费消息。

    通过 Namesrv 的协调,生产者、Broker、消费者三大组件有条不紊地配合完成整个消息的流转过程。那么 Namesrv 是如何架构的呢?各个组件的功能又是怎么样的呢?

      Namesrv 包含 4 个功能模块:Topic 路由管理模块、Remoting 通信模块、定时任务模块、KV管理模块。

      Topic 路由管理模块:Topic 路由决定 Topic 的分区数据会保存在哪些 Broker 上。这是 Namesrv 最核心的模块,Broker 启动时将自身信息注册到 Namesrv 中,方便生产者和消费者获取。生产者、消费者启动和间隔的心跳时间会获取 Topic 最新路由信息,以此发送或者接收消息。

      Remoting 通信模块:是基于 Netty 的一个网络通信封装,整个 RocketMQ 的公共模块在 RocketMQ 各个组件之间担任通信任务。该组件以 Request/Response 的方式通信,比如你想知道你使用的 RocketMQ 支持哪些功能,可以查看 D: ocketmq-mastercommonsrcmainjavaorgapache ocketmqcommonprotocolRequestCode.java,一个RequestCode 代表一种功能或者一个接口。

      定时任务模块:在 Namesrv 中定时任务并没有独立成一个模块,而是由 D: ocketmq-master amesrvsrcmainjavaorgapache ocketmq amesrvNamesrvController.java 中initialize()调用的几个定时任务组成的,其中各包括定时扫描宕机的 Broker、定时打印 KV配置、定时扫描超时请求。

      KV 管理模块:Namesrv 维护一个全局的 KV 配置模块,方便全局配置。

      2.2 Namesrv 启动流程


      第一步:脚本和启动参数配置。

        启动命令:nohup bin/mqnamesrv -c conf/namesrv.conf > /dev/null 2>&1。通过脚本配置启动基本参数,比如配置文件路径、JVM参数。调用 NameStartup.main()方法,解析命令行的参数,将处理好的参数转化为 Java 实例,传递给 NameController 实例。

      第二步:new 一个 NameController,加载命令行传递的配置参数,调用 controller.initialize() 方法初始化 NamesrvController。Namesrv 启动的主要初始化过程也在这个方法中,代码路径:  D: ocketmq-master amesrvsrcmainjavaorgapache ocketmq amesrvNamesrvController.java,代码如下:

     1     public boolean initialize() {
     2 
     3         this.kvConfigManager.load(); #加载KV配置,主要是从本地文件中加载KV配置到内存中。
     4 
     5         this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService); 
    #初始化 Netty 通信实例。RocketMQ 基于 Netty 实现了一个RPC服务端,即 NettyRemotingServer。通过参数 nettyServerConfig,会启动 9876 端口监听。 6 7 this.remotingExecutor = 8 Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_")); 9 10 this.registerProcessor(); 11 12 this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { 13 14 @Override 15 public void run() { 16 NamesrvController.this.routeInfoManager.scanNotActiveBroker();
    #Namesrv 主动监测 Broker 是否可用,如果不可用就剔除。生产者、消费者也能通过心跳发现被踢出的路由,从而感知 Broker 下线。 17 } 18 }, 5, 10, TimeUnit.SECONDS); 19 20 this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { 21 22 @Override 23 public void run() { 24 NamesrvController.this.kvConfigManager.printAllPeriodically();
    #Namesrv 定时打印配置信息到日志中。 25 } 26 }, 1, 10, TimeUnit.MINUTES); 27 28 if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) { 29 // Register a listener to reload SslContext 30 try { 31 fileWatchService = new FileWatchService( 32 new String[] { 33 TlsSystemConfig.tlsServerCertPath, 34 TlsSystemConfig.tlsServerKeyPath, 35 TlsSystemConfig.tlsServerTrustCertPath 36 }, 37 new FileWatchService.Listener() { 38 boolean certChanged, keyChanged = false; 39 @Override 40 public void onChanged(String path) { 41 if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) { 42 log.info("The trust certificate changed, reload the ssl context"); 43 reloadServerSslContext(); 44 } 45 if (path.equals(TlsSystemConfig.tlsServerCertPath)) { 46 certChanged = true; 47 } 48 if (path.equals(TlsSystemConfig.tlsServerKeyPath)) { 49 keyChanged = true; 50 } 51 if (certChanged && keyChanged) { 52 log.info("The certificate and private key changed, reload the ssl context"); 53 certChanged = keyChanged = false; 54 reloadServerSslContext(); 55 } 56 } 57 private void reloadServerSslContext() { 58 ((NettyRemotingServer) remotingServer).loadSslContext(); 59 } 60 }); 61 } catch (Exception e) { 62 log.warn("FileWatchService created error, can't load the certificate dynamically"); 63 } 64 } 65 66 return true; 67 }

       第三步:NamesrvController 在初始化后添加 JVM Hook。Hook 会调用 NamesrvController.shutdown() 方法来关闭整个 Namesrv 服务。

      第四步:调用 NamesrvController.start() 方法,启动整个 Namesrv。其实 start() 方法只启动了 Namesrv 接口处理线程池。

      至此,整个 Namesrv 启动完成。

      2.2 Namesrv 停止流程

      通常 Namesrv 的停止是通过关闭命令 mqshutdown namesrv 来实现的。这个命令通过调用 kill 命令 来关闭进程通知发给 JVM,JVM 调用关机 Hook 执行停止逻辑。代码路径:D: ocketmq-master amesrvsrcmainjavaorgapache ocketmq amesrvNamesrvStartup.java,代码如下:

    1         Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
    2             @Override
    3             public Void call() throws Exception {
    4                 controller.shutdown();
    5                 return null;
    6             }
    7         }));

      从代码中可以看到,JVM的关机 Hook 调用关闭了 controller,代码路径:D: ocketmq-master amesrvsrcmainjavaorgapache ocketmq amesrvNamesrvController.java,controller.shutdown()方法的代码实现如下:

    1     public void shutdown() {
    2         this.remotingServer.shutdown();            #关闭 Netty 服务端,主要是关闭 Netty 事件处理器、时间监听器等全部已经初始化组件。
    3         this.remotingExecutor.shutdown();          #关闭 Namesrv 接口处理线程池。
    4         this.scheduledExecutorService.shutdown();  #关闭全部已经启动的定时任务。
    5 
    6         if (this.fileWatchService != null) {
    7             this.fileWatchService.shutdown();
    8         }
    9     }
  • 相关阅读:
    【uni-app】预览图片(单/多张)
    【uni-app】全局数据globalData的设置、获取、修改
    C# DataGridView删除指定行
    设置DataGridview行的字体颜色
    设置DataGridView的单元格颜色
    JAVA设计模式总结之23种设计模式
    为什么要用k8s(优缺点)
    @RequestMapping中Get&Post 不写默认为都支持
    查询某条记录在mysql中的行数
    如何屏蔽博客园新出的标题栏?一行代码屏蔽博客园冒出的标题栏!
  • 原文地址:https://www.cnblogs.com/zuoyang/p/14440089.html
Copyright © 2020-2023  润新知