RocketMQ broker停写功能源码分析

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

背景

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

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

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

源码入口

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

  1. 通过mqadmin运维工具命令行的方式
  1. 通过MQAdminExt管理工具类

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

源码分析

client

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

java 复制代码
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

  • DefaultRequestProcessor的方法 wipeWritePermOfBroker

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

java 复制代码
int wipeTopicCnt = this.namesrvController.getRouteInfoManager().wipeWritePermOfBrokerByLock(requestHeader.getBrokerName());

我们进入到这个方法看看

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

java 复制代码
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我们可以看看他实际的数据结构

可以看到这里他的perm7

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

可以看到刚好可以对得上

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

  • 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。其他的类似

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

java 复制代码
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();

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

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

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

相关推荐
代码之光_198028 分钟前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
ajsbxi34 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
颜淡慕潇1 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
尘浮生2 小时前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
尚学教辅学习资料2 小时前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
monkey_meng4 小时前
【Rust中的迭代器】
开发语言·后端·rust
余衫马4 小时前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng4 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
paopaokaka_luck8 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
码农小旋风10 小时前
详解K8S--声明式API
后端