rocketmq学习笔记 ---- NameServer

博客从RocketMQ我们学到了什么之NameServer以邮局的功能作为类比,通俗易懂地介绍了RocketMQ中的NameServer在整个框架中的作用。而本篇文章,是以源码阅读笔记的形式,记录学习RocketMQ的过程。

 

启动流程

首先,NameServer的启动类为org.apache.rocketmq.namesrv.NamesrvStartup,方法的流程很简单:

1. 读取配置。从启动命令中读取配置文件,如果配置文件中有修改NamesrvConfig或NettyServerConfig设置的默认值,会将配置文件中的值覆盖NamesrvConfig或NettyServerConfig中的选项。NamesrvConfig、NettyServerConfig的配置项如下:

public class NamesrvConfig {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);

    private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));
    private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json";
    private String configStorePath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "namesrv.properties";
    private String productEnvName = "center";
    private boolean clusterTest = false;
    private boolean orderMessageEnable = false;
}
public class NettyServerConfig implements Cloneable {
    private int listenPort = 8888;  // 服务端监听端口
    // 执行ChannelHandler的方法(例如channelRead()等方法)的线程组
    private int serverWorkerThreads = 8;   
    // 执行请求回调方法的线程组线程数
    private int serverCallbackExecutorThreads = 0;
    // netty worker线程的数量(另外,Boss线程组的数量为1)
    private int serverSelectorThreads = 3; 

    // 单向请求、异步请求的最大并发量,超过默认数值,会阻塞
    private int serverOnewaySemaphoreValue = 256;
    private int serverAsyncSemaphoreValue = 64;

    // 监听客户端心跳的最大空闲时间,单位s
    private int serverChannelMaxIdleTimeSeconds = 120;  
    // netty发送/接收数据的缓冲区大小, 默认值65535,可通过设置系统属性修改
    private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
    private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
    // netty是否使用PooledByteBufAllocator也就是内存池,默认使用
    private boolean serverPooledByteBufAllocatorEnable = true;
    // netty使用EpollEventLoopGroup还是NioEventLoopGroup
    private boolean useEpollNativeSelector = false;
}

2. 创建NamesrvController。使用NamesrvConfig、NettyServerConfig创建实例。

final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);

3. 启动NamesrvController。代码如下:

    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的各个组件,如果失败,直接停止;然后,添加关闭钩子(所谓关闭钩子,是指进程在停止时执行的一个任务),在进程关闭时停止NameServer服务。最后,真正启动NamesrvController。

下面主要关心initialize()以及start()方法:

    public boolean initialize() {
        // KVConfigManager装载写入文件中的key和value
        this.kvConfigManager.load();
        // 启动netty服务器
        this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
        // NameServer接收到RemotingCommand请求后,在此线程组中执行请求,并返回结果
        this.remotingExecutor =
            Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
        // 注册NameServer的请求处理器DefaultRequestProcessor
        this.registerProcessor();
        // 设置定时任务,扫描不活跃的Broker,5秒后开始执行,以后每10秒扫描一次
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                NamesrvController.this.routeInfoManager.scanNotActiveBroker();
            }
        }, 5, 10, TimeUnit.SECONDS);
        // 1分钟后每隔10分钟,打印一次KVConfigManager的属性
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

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

        // 初始化TLS配置监听线程FileWatchService,当TLS证书或密码文件有修改时,通过HASH码校验重载配置
        if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
            // Register a listener to reload SslContext
            try {
                fileWatchService = new FileWatchService(
                    new String[] {
                        TlsSystemConfig.tlsServerCertPath,
                        TlsSystemConfig.tlsServerKeyPath,
                        TlsSystemConfig.tlsServerTrustCertPath
                    },
                    new FileWatchService.Listener() {
                        boolean certChanged, keyChanged = false;
                        @Override
                        public void onChanged(String path) {
                            if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) {
                                log.info("The trust certificate changed, reload the ssl context");
                                reloadServerSslContext();
                            }
                            if (path.equals(TlsSystemConfig.tlsServerCertPath)) {
                                certChanged = true;
                            }
                            if (path.equals(TlsSystemConfig.tlsServerKeyPath)) {
                                keyChanged = true;
                            }
                            if (certChanged && keyChanged) {
                                log.info("The certificate and private key changed, reload the ssl context");
                                certChanged = keyChanged = false;
                                reloadServerSslContext();
                            }
                        }
                        private void reloadServerSslContext() {
                            ((NettyRemotingServer) remotingServer).loadSslContext();
                        }
                    });
            } catch (Exception e) {
                log.warn("FileWatchService created error, can't load the certificate dynamically");
            }
        }

        return true;
    }
    public void start() throws Exception {
        // 启动Netty服务器,具体内容参考介绍NettyRemotingServer的文章
        this.remotingServer.start();
        // 启动监听TLS配置文件的线程
        if (this.fileWatchService != null) {
            this.fileWatchService.start();
        }
    }

到此为止,NameServer的启动流程就结束了。相对而言,NameServer组件较少,功能比较简单,就像官网上说的NameServer是一种无状态的服务,它仅仅记录所有的Broker的注册地址,topic的分布情况等。暂时对KVConfigManager的功能未知,看代码也是作为一种集中式的存储key-value,像是一个简单的配置中心。

目前为止,RouteInfoManager组件没有介绍到,这就是NameServer用来存储Broker的注册地址和topic的位置的组件。

之前在NettyRemotingServer的文章中介绍到,所有的请求封装为RemotingCommand后,交给对应的NettyRequestProcessor处理。在NameServer中,设置了一个DefaultRequestProcessor,在其processRequest()方法中有请求的处理方法,其中注册Broker请求如下:

        switch (request.getCode()) {
            // 其他请求略过
            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);
                }
            case RequestCode.UNREGISTER_BROKER:
                return this.unregisterBroker(ctx, request);
            case RequestCode.GET_ROUTEINTO_BY_TOPIC:
                return this.getRouteInfoByTopic(ctx, request);
            case RequestCode.GET_BROKER_CLUSTER_INFO:
                return this.getBrokerClusterInfo(ctx, request);
            case RequestCode.WIPE_WRITE_PERM_OF_BROKER:
                return this.wipeWritePermOfBroker(ctx, request);
            // 其他请求略过...
            default:
                break;
        }

看看Broker注册的代码:

    public RemotingCommand registerBrokerWithFilterServer(ChannelHandlerContext ctx, RemotingCommand request)
        throws RemotingCommandException {
        // 创建response对象
        final RemotingCommand response = RemotingCommand.createResponseCommand(RegisterBrokerResponseHeader.class);
        // 从RemotingCommand中解析出Broker注册参数RegisterBrokerResponseHeader 
        final RegisterBrokerResponseHeader responseHeader = (RegisterBrokerResponseHeader) response.readCustomHeader();
        final RegisterBrokerRequestHeader requestHeader =
            (RegisterBrokerRequestHeader) request.decodeCommandCustomHeader(RegisterBrokerRequestHeader.class);

        if (!checksum(ctx, request, requestHeader)) {
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark("crc32 not match");
            return response;
        }

        RegisterBrokerBody registerBrokerBody = new RegisterBrokerBody();

        if (request.getBody() != null) {
            try {
                registerBrokerBody = RegisterBrokerBody.decode(request.getBody(), requestHeader.isCompressed());
            } catch (Exception e) {
                throw new RemotingCommandException("Failed to decode RegisterBrokerBody", e);
            }
        } else {
            registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setCounter(new AtomicLong(0));
            registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setTimestamp(0);
        }

        // 注册Broker
        RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(
            requestHeader.getClusterName(),
            requestHeader.getBrokerAddr(),
            requestHeader.getBrokerName(),
            requestHeader.getBrokerId(),
            requestHeader.getHaServerAddr(),
            registerBrokerBody.getTopicConfigSerializeWrapper(),
            registerBrokerBody.getFilterServerList(),
            ctx.channel());

        responseHeader.setHaServerAddr(result.getHaServerAddr());
        responseHeader.setMasterAddr(result.getMasterAddr());

        byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG);
        response.setBody(jsonValue);

        response.setCode(ResponseCode.SUCCESS);
        response.setRemark(null);
        return response;
    }

继续关注this.namesrvController.getRouteInfoManager().registerBroker()方法:

    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;
                // 注册Broker
                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);
                }
                String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
                registerFirst = registerFirst || (null == oldAddr);

                if (null != topicConfigWrapper
                    && MixAll.MASTER_ID == brokerId) {
                    // 检查Version,如果version不一致,说明有更新。第一次注册也要更新queueData
                    if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())
                        || registerFirst) {
                        // 获取Broker当前的topic配置
                        ConcurrentMap<String, TopicConfig> tcTable =
                            topicConfigWrapper.getTopicConfigTable();
                        if (tcTable != null) {
                            for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
                                // 在NameServer上,更新Broker的topic信息
                                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);
                }
                // 更新服务器过滤列表,其功能暂时未知,需结合broker了解其功能
                if (filterServerList != null) {
                    if (filterServerList.isEmpty()) {
                        this.filterServerTable.remove(brokerAddr);
                    } else {
                        this.filterServerTable.put(brokerAddr, filterServerList);
                    }
                }
                // 根据master的属性设置haServer
                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;
    }

 

NameServer中的线程模型:

Netty的线程模型也就是多线程Reactor模型,使用拥有少量线程的、独立的Acceptor线程池专门处理NIO的accept事件,生成channel。使用线程组处理消息的拆包、解码、业务处理和返回消息等。

RocketMQ中使用Netty作为网络通讯工具,它使用的线程模型与Netty一般使用的线程模型略有不同,模型如图所示:

上图画出了客户端发起连接请求,到发送Request请求,再到返回response结果以及执行回调的过程。其步骤如下:

  1. 客户端连接服务器。当客户端连接到服务器时,Acceptor中select处理一个Nio的ACCEPT事件,生成一个channel。这一步的线程组专门处理网络连接。
  2. Netty将该channel依次绑定到Selector线程组的EventLoop上(比如假设Selector线程组线程数量为8,那么连接到的第8 * n + 1 ~  8 * n + 8个channel分别绑定到线程1 ~ 线程8上)。当客户端发送一个请求时,服务端的Selector在执行select时会接收到READ事件,Selector读取准别好的字节封装到ByteBuf中,然后发送到ChannelPipeline中。这一步的线程组专门处理网络IO。
  3. 在RocketMQ的NettyRemotingServer启动服务器过程中,添加ChannelHandler时指定了使用defaultEventExecutorGroup作为消息在ChannelPipeline中传递的线程组,也就是调用每个ChannelHandler的channelRead()方法都在在线程组中执行。那么,RocketMQ的消息的拆包、解码是在该线程组中进行处理的。
  4. 在第3步中,NettyDecoder将传入的ByteBuf解码为RemotingCommand,这是远程请求统一使用的数据结构。然后在NettyServerHandler处理channelRead()时,将对远程请求的处理封装成一个RequestTask,这是一个实现Runnable接口的请求处理任务,最后投入remotingExecutor中执行。
  5. 在RequestTask请求任务执行完成后,会获得一个执行相应结果,通过ctx.writeAndFlush(response)方法,将返回结果刷新会客户端。4和5两步主要在remotingExecutor中处理远程请求。
  6. 假如客户端发起的RemotingCommand请求是设置了回调的异步请求,客户端接收到服务器端返回的消息后,根据请求id找到相应的回调方法,并在publicExecutor中执行回调。这一步客户端在publicExecutor中执行消息响应的回调。

综上,可以看出RocketMQ线程模型一个很重要的想法:针对不同的用途设置专用的线程组,这样可以根据业务需求精确的调整用于每个部分的线程组的数量。

 

总结:

首先还是来看RocketMQ的架构:

1.每个Broker与NameServer都有一个连接,而NameServer与NameServer之间没有连接,从代码上来说也没有数据的同步,说明NameServer和Zookeeper不一样,不保证数据的一致性,是AP的。

2.从Broker的注册代码来看,一个BrokerName可以包含一个Matser Broker和多个Slave Broker,其中Master的id为0,slave的id为1,2,3...

3.Broker心跳超时时,会关闭Channel。从NameServer的定时任务来看,RocketMQ每隔10秒钟扫描一次Broker检测最后一次发消息的时间,如果超过两分钟,则会关闭channel。说明RocketMQ的默认失效时间为2分钟。

4.当Broker失效后,NameServer只能在2分钟后将其从Broker存活列表中删除,在这段时间中,发送消息时通过失败选择下一个Broker重试的方法,规避NameServer保证数据一致性的复杂设计。

5.RocketMQ针对不同的用途设置专用的线程组,可以根据业务需求精确的调整用于每个部分的线程组的数量。

 

疑惑点:

1.KVConfigManager是一个包含命名空间的KV配置存储管理组件,他在RocketMQ中起到什么样的作用?

2.暂时不理解注册Broker时,传入的参数filterServerList的含义。

3.在注册Broker的代码的后面,有这样一行代码:

result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());

其中brokerLiveInfo是master,说明如果broker不是Master,那么这里返回的result中设置的负载均衡的server地址是Master传过来的地址,但是往brokerLiveTable中添加的又是请求传过来的haServer,就是说两者有可能不一样,不知道这里有什么区别。另外,当同属于brokerName为broker-a的一个slave broker先于master启动,那么这里result的haServer又是空的,不知道这种情况是否有影响,或者RocketMQ是否有master必定先于slave启动的要求?

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值