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执行该命令

相关推荐
凡人的AI工具箱6 小时前
AI教你学Python 第11天 : 局部变量与全局变量
开发语言·人工智能·后端·python
是店小二呀6 小时前
【C++】C++ STL探索:Priority Queue与仿函数的深入解析
开发语言·c++·后端
canonical_entropy6 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
我叫啥都行7 小时前
计算机基础知识复习9.7
运维·服务器·网络·笔记·后端
无名指的等待7128 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端
.生产的驴8 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
AskHarries9 小时前
Spring Boot利用dag加速Spring beans初始化
java·spring boot·后端
苹果酱05679 小时前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
掐指一算乀缺钱10 小时前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
计算机学姐12 小时前
基于python+django+vue的影视推荐系统
开发语言·vue.js·后端·python·mysql·django·intellij-idea