RocketMQ-lsy

399 阅读8分钟

消息队列

来源

FIFO的队列

优点

  • 同步转异步
  • 应用解耦

多个低错误率强耦合变成高错误率

  • 流量削峰

瞬时的海量请求,暂存分散

  • 消息分发

缺点

  • 运维成本
  • 开发成本

特点

历史

  1. Notify 推模型 解决事务消息
  2. MetaQ 拉模型,解决顺序消息和海量堆积问题
  3. RocketMQ 基于长轮询的拉取

术语概念

角色

发信 + 管理 + 暂存 + 收信

producer + namesrv + broker + consumer

image-20211218144851030

NameServer

不用Zookeeper的原因

不需要Master选举,不需要使用其大部分功能,不必引入重量级,降低维护成本

角色间交互

底层通信

基于Netty库完成RemotingServer + RemotingClient 的通信

自定义通信协议

image-20211218182107004

Broker

核心

消息存储&发送

磁盘概念

用户user态转换内核态 core,数据拷贝

  1. disk 加载数据 -> core
  2. core 拷贝数据 -> user (read)
  3. user 复制到网卡驱动的内核态 -> net-core
  4. net-core 网卡内核态内存复制到网卡传输 -> (write)

mmap 省去用户态内存复制,步骤2,zero copy

存储结构(定长

顺序写,随机读

术语名词

Topic 消息的1级分类 (消息必须指定)

Tag 消息的2级分类

Properties 消息的多级分类

Message Queue (提高并行

Consumer Queue (增加吞吐

Producer Group ?

Consumer Group?

namespace

consumer的 GroupName = consumer group 与MessageModel配合

MessageModel :

  • Broadcasting 每个都全部得到,订阅
  • Clustering 分摊部分,负载均衡

原理架构

消息消费方式

Push模型

Server接受消息主动推送给client

优:实时性高

缺:server(broker)工作量增加,性能降低;client处理能力不等,不一定及时处理

Pull模型

client主动从server拉取消息

优:client可依据自身处理能力拉取

缺:循环间隔不好设定,太长不及时,太短则浪费

longpull 长轮询方式

两者优点(本质pull

Broker端hold住client的请求(3*5s),这段时间有消息就利用该连接 (pull缺点)

Consumer主动消费,broker消息积压也不全部推送(push缺点)

流量控制

消息处理的逻辑在线程池中,不好监控与控制

使用 ProcessQueue 快照类 《= MessageQueue

TreeMap (offset,消息内容)+ 读写锁

可以辅助实现顺序消息

存储队列位置信息

解决

  • 重复消费某条消息
  • 跳过一段时间的消息

安装部署

目录结构

LICENSE
NOTICE
README.md
benchmark/
bin/
conf/

lib/

单机版

测试应用

  1. 下载源代码
unzip  rocketmq-all-4.4.0-bin-release.zip
cd rocketmq-all-4.4.0-bin-release

  1. 修改启动脚本的参数

runbroker.sh + runserver.sh + tools.sh

默认值过大(生产适用)

cd ./bin
vim ./runbroker.sh

# 将 JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g" 调小
# 改成下列
JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m"

vim ./runserver.sh
# JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"

vim ./rools.sh
# JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -XX:PermSize=128m -XX:MaxPermSize=128m"

JAVA_OPT="${JAVA_OPT} -server -Xms128m -Xmx256m -Xmn64m -XX:PermSize=128m -XX:MaxPermSize=128m"
  1. 启动

先启动nameServer 再启动borker

nameserver 默认端口 9876

sh ./mqnamesrv &
# 成功
OpenJDK 64-Bit Server VM warning: Using the DefNew young collector with the CMS collector is deprecated and will likely be removed in a future release
OpenJDK 64-Bit Server VM warning: UseCMSCompactAtFullCollection is deprecated and will likely be removed in a future release.
OpenJDK 64-Bit Server VM warning: MaxNewSize (524288k) is equal to or greater than the entire heap (524288k).  A new max generation size of 524224k will be used.
The Name Server boot success. serializeType=JSON

sh ./mqbroker -n localhost:9876 autoCreateTopicEnable=true -c ../conf/broker.conf &

OpenJDK 64-Bit Server VM warning: If the number of processors is expected to increase from one, then you should configure the number of parallel GC threads appropriately using -XX:ParallelGCThreads=N
  1. 检查启动

检查进程

jps
BrokerStartup
NamesrvStartup
  1. 测试使用

使用 tools.sh 进行消息收发测试

sh tools.sh org.apache.rocketmq.example.quickstart.Producer
sh tools.sh org.apache.rocketmq.example.quickstart.Consumer

# 启动Producer 报错,需设置环境变量 NAMESRC_ADDR
export NAMESRV_ADDR=localhost:9876
  1. 关闭

先关闭broker,再关闭namesrv

sh ./bin/mqshutdown broker
sh ./bin/mqshutdown namesrv

管理指令

./bin/mqadmin

  • 创建/修改/删除Topic

updateTopic

deleteTopic

  • 创建/修改/删除 订阅组 SubGroup

updateSubGroup

deleteSubGroup

  • 更新Broker信息(可动态修改的

updateBrokerConfig

  • 更新Topic 读写权限

updateTopicPerm

  • 查询Topic信息 (类似namesrv提供的路由信息)

路由信息

TopicRoute

列表信息

TopicList

统计信息

TopicStats

  • 依据时间\ID查询消息

pritMsg

queryMsgById()

  • 查看集群消息

clusterList

存储

在启动用户主目录下

/{root}/logs/rocketmqlogs

-rw-r--r--. 1 root root   2532 Dec  8 14:13 broker_default.log
-rw-r--r--. 1 root root  29060 Dec  8 14:18 broker.log
-rw-r--r--. 1 root root      0 Dec  8 14:09 commercial.log
-rw-r--r--. 1 root root      0 Dec  8 14:09 filter.log
-rw-r--r--. 1 root root      0 Dec  8 14:09 lock.log
-rw-r--r--. 1 root root    452 Dec  8 14:06 namesrv_default.log
-rw-r--r--. 1 root root   9108 Dec  8 14:17 namesrv.log
-rw-r--r--. 1 root root      0 Dec  8 14:09 protection.log
-rw-r--r--. 1 root root   6923 Dec  8 14:13 remoting.log
-rw-r--r--. 1 root root   9823 Dec  8 14:18 rocketmq_client.log
-rw-r--r--. 1 root root      0 Dec  8 14:09 stats.log
-rw-r--r--. 1 root root      0 Dec  8 14:09 storeerror.log
-rw-r--r--. 1 root root  22376 Dec  8 14:18 store.log
-rw-r--r--. 1 root root  13797 Dec  8 14:18 transaction.log
-rw-r--r--. 1 root root 151200 Dec  8 14:18 watermark.log

/{root}/store
-rw-r--r--. 1 root root    0 Dec  8 14:13 abort
-rw-r--r--. 1 root root 4096 Dec  8 14:19 checkpoint
drwxr-xr-x. 2 root root  193 Dec  8 14:19 config
-rw-r--r--. 1 root root    4 Dec  8 14:13 lock

异常处理

org.apache.rocketmq.client.exception.MQClientException: No name server address, please set it.


#调用超时,连接问题?
Exception in thread "main" org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException: sendDefaultImpl call timeout
  • 执行admin查看信息都报错
OpenJDK 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0
OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
org.apache.rocketmq.tools.command.SubCommandException: ClusterListSubCommand command failed
        at org.apache.rocketmq.tools.command.cluster.ClusterListSubCommand.execute(ClusterListSubCommand.java:93)
        at org.apache.rocketmq.tools.command.MQAdminStartup.main0(MQAdminStartup.java:135)
        at org.apache.rocketmq.tools.command.MQAdminStartup.main(MQAdminStartup.java:86)
Caused by: org.apache.rocketmq.acl.common.AclException: [10015:signature-failed] unable to calculate a request signature. error=[10015:signature-failed] unable to calculate a request signature. error=Algorithm HmacSHA1 not available
        at org.apache.rocketmq.acl.common.AclSigner.signAndBase64Encode(AclSigner.java:84)

配置优化

安装包具备示例

conf/
2m-2s-sync #2主2从同步示例
2m-2s-async #2主2从异步示例
2m-noslave #2主无从

配置说明

无法热启动(java通病

# brokerip 注意顺序,其他ip的干扰,例如docker
brokerIP1=192.168.85.132
# namesrv 的地址,多个用分号分开
namesrvAddr=
#集群名称
brokerClusterName=
#broker名称
brokerName=
#broker监听端口
listenPort=
# 主从节点判断标识,主1,从大于0
brokerId=1
#三种 SYNC_MASTER,ASYNC_MASTER,SLAVE
# 主从同步机制,主从异步机制,
brokerRole=SLAVE
#刷盘策略 SYNC_FLUSH,ASYNC_FLUSH
flushDiskType

#几点删除动作 04=凌晨3点
deleteWhen=04
#消息保存时长 单位小时
fileReservedTime=48
#存储消息的根目录
storePathRootDir

配置文件 conf/broker.conf

日志自定义输出

结合应用

生产者类型

发送消息步骤

  1. 设置Producer
    • groupName
    • instanceName
    • retryTimesWhenFailed
    • namesrv
  2. 组装消息并发送

SendStatus 返回状态

  • FLUSH_DISK_TIMEOUT

未在规定时间内刷盘,需设置SYNC_FLUSH

  • FLUSH_SLAVE_TIMEOUT

未在规定时间完成主从同步,需设置SYNC_MASTER

  • SLAVE_NOT_AVAILABLE

从节点不可用,,需设置SYNC_MASTER

  • SNED_OK

发送成功,需结合具体配置(刷盘,主从)来看

发送方式

  • 同步发送
SendResult sendResult = producer.send(msg);
  • 异步发送
 producer.send(msg, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.println("message "+ finalI +"send success" + sendResult.getSendStatus());
                }

                @Override
                public void onException(Throwable throwable) {
                    throwable.printStackTrace();
                }
            });
  • 延迟发送

原理,将消息暂时放入到内置定时队列中,定时器到了放置到原队列中

注意:设置延迟等级(对应延迟时间长度

msg.setDelayTimeLevel(3);
  • 事务消息发送

Transaction : A +10 同时 B-10

事务内要么A,B全部成功,要么A,B全部失败

采用两阶段提交,先做A+10消息,检查操作是否成功,再决定B消息是commit还是rollback

  • 具体流程 P52

  • 自定义发送规则

Topic 轮流向 多个MessageQueue发送,consumer依据负载均衡策略到分配的Message Queue消费

自定义用于将消息发送到指定Message Queue,使用MessageQueueSelector

public class OrderMessageQueueSelector implements MessageQueueSelector{

        @Override
        public MessageQueue select(List<MessageQueue> list, Message message, Object orderKey) {
            int id = Integer.parseInt((String) orderKey);
            int idMainIndex = id/100;
            int size = list.size();
            int index = idMainIndex%size;
            return list.get(index);
        }
    }

消费者类型

DefaultMQPushConsumer (系统控制读取)

自动调用处理函数进行处理,自动保存offset,新加如自动负载

长轮询方式,实时性高,CS端的配合

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("lsy_consumer_group");
        consumer.setNamesrvAddr("192.168.85.132:9876");
        //set offset
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.setMessageModel(MessageModel.BROADCASTING);
        consumer.subscribe("lsy_topic","*");

        //MessageListenerConcurrently
        //MessageListenerOrderly
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                System.out.println(Thread.currentThread().getName() + " Receive New Message" );
                list.forEach(i->{
                    String result = new String(i.getBody());
                    System.out.println(result);
                });
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();

DefaultMQPullConsumer (可控)

需要自己记住offset,自身消费能力,CPU一直轮询

结束记得shutdown,释放资源

public static void pullConsumer() throws Exception{
        DefaultMQPullConsumer consumer =new DefaultMQPullConsumer("lsy_consumer_group");
        consumer.setNamesrvAddr("192.168.85.132:9876");
        consumer.start();
        Set<MessageQueue> msgSet = consumer.fetchSubscribeMessageQueues("lsy_topic");
        for (MessageQueue messageQueue : msgSet) {
            long offset = consumer.fetchConsumeOffset(messageQueue, true);
            System.out.println("offset"+offset + "messageQueue");
            SINGLE_MQ:
            while (true){
                try{
                    PullResult pullResult = consumer.pullBlockIfNotFound(messageQueue,null,getMessageQueueOffset(messageQueue),32);
                    System.out.println(pullResult);
                    putMessageQueueOffset(messageQueue,pullResult.getNextBeginOffset());
                    switch (pullResult.getPullStatus()){
                        case FOUND:
                            List<MessageExt> msgFoundList = pullResult.getMsgFoundList();
                            msgFoundList.forEach(i->{
                                System.out.println(new String(i.getBody()));
                            });
                            break ;
                        case NO_MATCHED_MSG:
                            break;
                        case NO_NEW_MSG:
                            break SINGLE_MQ;
                        case OFFSET_ILLEGAL:
                            break ;
                        default:
                            break ;
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            consumer.shutdown();
        }
    }

    public static long getMessageQueueOffset(MessageQueue messageQueue){
        return OFFSET_TABLE.get(messageQueue)!=null?OFFSET_TABLE.get(messageQueue):0;
    }

    public static void putMessageQueueOffset(MessageQueue messageQueue,long offset){
        OFFSET_TABLE.put(messageQueue,offset);
    }

无法连接namesrv时不会报错,只是warning

原因,集群中允许部分失败,整体仍可用

显式发现配置信息错误或服务不可用需配置

consumer.fetchSubscribeMessageQueues("topicName")

连接超时,检查防火墙关闭,可ping通

#连接超时
Caused by: org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException: sendDefaultImpl call timeout

解决,broker启动时绑定ip不对, 127.17.0.1 是为docker的ip,10911应该是端口

The broker[broker-a, 172.17.0.1:10911] boot success. serializeType=JSON and name server is localhost:9876

docker 网卡干扰,broker启动时ip绑定错误了

broker启动时
The broker[broker-a, 172.17.0.1:10911] boot success. serializeType=JSON and name server is 192.168.85.132:9876

在第一行配置
brokerIP1=192.168.85.132

org.apache.rocketmq.client.exception.MQClientException: Send [3] times, still failed, cost [6521]ms, Topic: lsy_topic, BrokersSent: [192.168.85.132, 192.168.85.132, 192.168.85.132]
Caused by: org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to <172.17.0.1:10911> failed

broker挂掉

MQClientException: No route info of this topic