#更文挑战#RocketMQ源码分析-NameServer解析一

117 阅读5分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

开篇先上一张RocketMQ官方的经典架构图,如下图所示: image.png Rocketmq是开源的消息中间件,主要是四部分由NameServer,Producer,Broker,Consumer构成 1)NameServer:类似于注册中心,负责Topic和路由信息的管理 2)Producer:生产者,消息的发送端 3)Broker:消息存储,以及消息转发 4)Consumer:消费者,消费消息 NameServer:是一个近乎无状态的节点,由上面的架构图可知,每台NameServer相互独立,Broker与每台NameServer保持长连接通信。还是老规矩,怎么找NameServer的入口,就看他的启动脚本就完事了NameServer的启动脚本位于源码rocketmq/distribution/bin/mqnamesrv里由脚本可知org.apache.rocketmq.namesrv.NamesrvStartup是启动类,一起看下源码,以下源码解析均为重要方法选择性解析,全面解析的话时间不是很充足。

public static void main(String[] args) {
    main0(args);
}
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;
}

1)启动类可以看出NamesrvController是整个NameServer的核心控制

看下NamesrvController的类图

image.png

//代码
// namerServer 配置信息
private final NamesrvConfig namesrvConfig;
// netty配置信息
private final NettyServerConfig nettyServerConfig;
// 调度任务
private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(    "NSScheduledThread"));
// 读取或变更NameServer的属性 加载NamerServer的属性到内存   
private final KVConfigManager kvConfigManager; 
// 记录Broker,Topic信息以及关联
private final RouteInfoManager routeInfoManager;
// netty server端
private RemotingServer remotingServer;
// 处理netty事件接口(连接,关闭,异常,心跳事件)
private BrokerHousekeepingService brokerHousekeepingService;
// 业务线程池private ExecutorService remotingExecutor;全局配置类 数据版本号,存储base路径等等
private Configuration configuration;      
// 监听证书文件是否有有变化
private FileWatchService fileWatchService;

2)NamesrvController属性

NamesrvConfig:主要是NameServer的一些配置kvConfigPath,rocketmqHome等

如下NettyServerConfig(netty相关配置)源码

public class NettyServerConfig implements Cloneable {
    //Server端端口
    private int listenPort = 8888;
    //业务线程池个数
    private int serverWorkerThreads = 8;
    //默认业务线程池,采用fixed类型
    private int serverCallbackExecutorThreads = 0;
    //Netty I/O线程数处理Selector读写事件线程数
    private int serverSelectorThreads = 3;
    //用于限流
    private int serverOnewaySemaphoreValue = 256;
    //异步调用限流
    private int serverAsyncSemaphoreValue = 64;
    //channel的空闲时间 idleHandler实现
    private int serverChannelMaxIdleTimeSeconds = 120;
    //socket 发送缓冲区大小 默认 65535
    private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
    //socket 接收缓冲区大小      65535
    private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
    //是否使用PooledByteBufd
    private boolean serverPooledByteBufAllocatorEnable = true;
}

3)scheduledExecutorService,定时任务线程池的配置详情

public class NettyServerConfig implements Cloneable {
   //Server端端口
   private int listenPort = 8888;
   //业务线程池个数
   private int serverWorkerThreads = 8;
   //默认业务线程池,采用fixed类型
   private int serverCallbackExecutorThreads = 0;
   //Netty I/O线程数处理Selector读写事件线程数
   private int serverSelectorThreads = 3;
   //用于限流
   private int serverOnewaySemaphoreValue = 256;
   //异步调用限流
   private int serverAsyncSemaphoreValue = 64;
   //channel的空闲时间 idleHandler实现
   private int serverChannelMaxIdleTimeSeconds = 120;
   //socket 发送缓冲区大小 默认 65535
   private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
   //socket 接收缓冲区大小      65535
   private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
   //是否使用PooledByteBufd
   private boolean serverPooledByteBufAllocatorEnable = true;
}

4)调度器主要执行两个任务,10s一次检测channel是否最后一个的活跃事件,是否大于配置的超时时间,看下任务代码,其实就是类似于心跳检测,在Broker也有调度线程10s一次调用BrokerController.registerBrokerAll()方法实现路由注册等发送QUERY_DATA_VERSION事件去更新LastUpdateTimestamp字段实现心跳检测的功能,源码如下:

//扫描不活跃的的broker 信息
public void scanNotActiveBroker() {
   //获取所有live的brokerinfo
   Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
   while (it.hasNext()) {
       Entry<String, BrokerLiveInfo> next = it.next();
       //获取最新的一次updateTime时间
       long last = next.getValue().getLastUpdateTimestamp();
       //判断是否超时
       if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
           //超时的话,关闭broker的channel
           RemotingUtil.closeChannel(next.getValue().getChannel());
           it.remove();
           log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME);
           //移除BrokerLiveInfo,以及对应的Topic信息
           this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
       }
   }
}

5)RouteInfoManager(NameServer存储的核心信息)

image.png

//以下为具体属性
//NameServer 与broker的空闲时间 查过两分钟没有收到心跳 关闭该链接
private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
//读写锁
private final ReadWriteLock lock = new ReentrantReadWriteLock();
//topic 与队列的关系  以及topic 分布在哪些Broker上 每个broker存在该topic的个数
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
// 所有的broker信息 brokerName 为key
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
//broker集群信息,每个集群包含哪些Broker,key为集群名称
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
// 当前存活的Broker,该信息不是实时的,NameServer每10S扫描一次所有的broker,根据心跳包的时间得知broker的状态
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
//过滤的broker
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;

6)核心属性实体(QueueData队列属性)

//写队列:producer可以发送的queue的个数
//读队列:consumer可以拉取的队列的个数
public class QueueData implements Comparable<QueueData> { 
//borker name  
private String brokerName;  
//写队列的个数  
private int readQueueNums; 
//读队列的个数  
private int writeQueueNums; 
//权限
private int perm;       
//同步复制 还是异步复制   
private int topicSynFlag;  
}

7)BrokerData(Broker属性)

public class BrokerData implements Comparable<BrokerData> {
    //集群名称,节点信息,分布在哪个节点信息
    private String cluster;
    //brokerName
    private String brokerName;
    //broker 对应的IP:Port,brokerId=0表示Master,大于0表示Slave。
    private HashMap<Long/* brokerId */, String/* broker address */> brokerAddrs;
    //随机数,随机获取一个broker的地址
    private final Random random = new Random();
}

8)BrokerLiveInfo(活跃的Broker)

class BrokerLiveInfo {
    //时间戳用于判断Broker是否存活
    private long lastUpdateTimestamp;
    private DataVersion dataVersion;
    //socket通信
    private Channel channel;
    //ha 地址
    private String haServerAddr;
}

9)TopicConfig(Topic配置信息)

public class TopicConfig {
    private static final String SEPARATOR = " ";
    //默认读队列数量
    public static int defaultReadQueueNums = 16;
    // 默认写队列数量
    public static int defaultWriteQueueNums = 16;
    //topic 主题名称
    private String topicName;
    //readQueue数量
    private int readQueueNums = defaultReadQueueNums;
    //write Queue数量
    private int writeQueueNums = defaultWriteQueueNums;
    //读取权限
    private int perm = PermName.PERM_READ | PermName.PERM_WRITE;
    //主题过滤方式
    private TopicFilterType topicFilterType = TopicFilterType.SINGLE_TAG;
    //系统标志,比如事务,延时等
    private int topicSysFlag = 0;
    //是否顺序消费 
    private boolean order = false;
}

10)TopicRouteData(Topic路由信息)

public class TopicRouteData extends RemotingSerializable {
   //是否顺序消息
   private String orderTopicConf;
   //topic分布在哪些Broker上,以及读写队列的个数
   private List<QueueData> queueDatas;
   // topic分布的Broker信息
   private List<BrokerData> brokerDatas;
   //需要过滤的address
   private HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
}
下一章节继续探索NameServer的源码,感兴趣的小伙伴可以一起讨论,谢谢!