RocketMQ broker停写功能源码分析

这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党

背景

在我们要平滑升级broker的时候,无损升级的最佳实践应该是

  1. 新broker启动
  2. 旧broker停写
  3. 旧broker消息消费完成后下线(包括延时消息)

所以我们本次就是来分析如何完成broker的停写

源码入口

其实通过查看源码,我们发现有两种方式可以停写broker

  1. 通过mqadmin运维工具命令行的方式

RocketMQ broker停写功能源码分析_第1张图片

  1. 通过MQAdminExt管理工具类

RocketMQ broker停写功能源码分析_第2张图片

两种方式没有区别,都是调用DefaultMQAdminExtwipeWritePermOfBroker方法

RocketMQ broker停写功能源码分析_第3张图片

源码分析

client

我们这里直接进去到最底层的实现类代码

public int wipeWritePermOfBroker(final String namesrvAddr, String brokerName,
        final long timeoutMillis) throws RemotingCommandException,
        RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQClientException {
        WipeWritePermOfBrokerRequestHeader requestHeader = new WipeWritePermOfBrokerRequestHeader();
        requestHeader.setBrokerName(brokerName);

        RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.WIPE_WRITE_PERM_OF_BROKER, requestHeader);
        RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMillis);
        assert response != null;
        switch (response.getCode()) {
            case ResponseCode.SUCCESS: {
                WipeWritePermOfBrokerResponseHeader responseHeader =
                    (WipeWritePermOfBrokerResponseHeader) response.decodeCommandCustomHeader(WipeWritePermOfBrokerResponseHeader.class);
                return responseHeader.getWipeTopicCount();
            }
            default:
                break;
        }

        throw new MQClientException(response.getCode(), response.getRemark());
    }

客户端的代码我们没什么好看的,我们还是通过请求状态码RequestCode.WIPE_WRITE_PERM_OF_BROKER查看实际的Nameserver的处理逻辑

NameServer

RocketMQ broker停写功能源码分析_第4张图片

  • DefaultRequestProcessor的方法 wipeWritePermOfBroker
    RocketMQ broker停写功能源码分析_第5张图片

可以看到实际的代码逻辑在这一行

int wipeTopicCnt = this.namesrvController.getRouteInfoManager().wipeWritePermOfBrokerByLock(requestHeader.getBrokerName());

我们进入到这个方法看看

RocketMQ broker停写功能源码分析_第6张图片

可以看到实际的逻辑还在更下层,我们再进去看看

private int operateWritePermOfBroker(final String brokerName, final int requestCode) {
        int topicCnt = 0;

        for (Entry<String, Map<String, QueueData>> entry : this.topicQueueTable.entrySet()) {
            Map<String, QueueData> qdMap = entry.getValue();

            final QueueData qd = qdMap.get(brokerName);
            if (qd == null) {
                continue;
            }
            int perm = qd.getPerm();
            switch (requestCode) {
                case RequestCode.WIPE_WRITE_PERM_OF_BROKER:
                    perm &= ~PermName.PERM_WRITE;
                    break;
                case RequestCode.ADD_WRITE_PERM_OF_BROKER:
                    perm = PermName.PERM_READ | PermName.PERM_WRITE;
                    break;
            }
            qd.setPerm(perm);
            topicCnt++;
        }
        return topicCnt;
    }

这里的topicQueueTable我们可以看看他实际的数据结构

RocketMQ broker停写功能源码分析_第7张图片

可以看到这里他的perm7

如果我们去broker的 config的topics.json查看相关的配置信息

可以看到刚好可以对得上

RocketMQ broker停写功能源码分析_第8张图片

这里我们解释一下perm的权限问题

RocketMQ broker停写功能源码分析_第9张图片

  • PERM_PRIORITY = 0x1 << INDEX_PERM_PRIORITY 8 优先级队列
  • PERM_READ = 0x1 << INDEX_PERM_READ 4 读权限
  • PERM_WRITE = 0x1 << INDEX_PERM_WRITE 2 写权限
  • PERM_INHERIT = 0x1 << INDEX_PERM_INHERIT 1 继承权限

0x1表示16进制的1,0x1 << 3 表示将二进制数 0001 向左移动 3 位,结果为 1000,对应十进制数 8。其他的类似

这里最基本的权限只有这三个,也就是读、写、继承。但是是否拥有读写权限是通过如下几个方法进行位运算

public static boolean isWriteable(final int perm) {
        return (perm & PERM_WRITE) == PERM_WRITE;
    }

    public static boolean isInherited(final int perm) {
        return (perm & PERM_INHERIT) == PERM_INHERIT;
    }

    public static boolean isValid(final String perm) {
        return isValid(Integer.parseInt(perm));
    }

然后我们继续源码分析
这里对请求状态码做了区分,分析让queue权限变成可写还是可读
我们先看看perm &= ~PermName.PERM_WRITE;

我们原先的权限是7(可继承、读、写),经过运算就变成5(可继承、可读)了

这里我们是执行停写,如果要恢复写入,就执行PermName.PERM_READ | PermName.PERM_WRITE;,变为6(可读、可写、不可继承)

所以可以看到这里是先更新了NameServer的路由信息

值得注意的是这里有一个问题,就是实际这里并不会去更新brokertopics.json配置信息,只是更新了NameServer的内存里面的topicQueueTable,所以也就是如果broker重启或者NameServer重启还是会恢复写入。
这里为什么不去更新brokertopics.json,个人觉得可能是一个bug或者是为了逻辑的简单

消费者更新Topic路由信息

至于消费者是如何更新获取到NameServer更新的topicQueueTable,我们可以去随便看看消费者的代码

我们可以看到消费者在启动的时候就启动了一个定时任务
MQClientInstance.this.updateTopicRouteInfoFromNameServer();

RocketMQ broker停写功能源码分析_第10张图片

这里定时会去Nameserver拉取最新的Topic路由信息,所以我们更新Nameserver的路由信息后这里就会拉去到。更新时间我们可以看看

RocketMQ broker停写功能源码分析_第11张图片

默认30s,也就是说我们在停写命令发出后,最长30s内还是会有消息写入

值得注意的是NameServer之间并不会互相通信,所以如果我们是集群模还需要循环遍历所有NameServer执行该命令

你可能感兴趣的:(消息中间件,#,RocketMQ,rocketmq,mysql,数据库)