消息队列
来源
FIFO的队列
优点
- 同步转异步
- 应用解耦
多个低错误率强耦合变成高错误率
- 流量削峰
瞬时的海量请求,暂存分散
- 消息分发
缺点
- 运维成本
- 开发成本
特点
历史
- Notify 推模型 解决事务消息
- MetaQ 拉模型,解决顺序消息和海量堆积问题
- RocketMQ 基于长轮询的拉取
术语概念
角色
发信 + 管理 + 暂存 + 收信
producer + namesrv + broker + consumer
NameServer
不用Zookeeper的原因
不需要Master选举,不需要使用其大部分功能,不必引入重量级,降低维护成本
角色间交互
底层通信
基于Netty库完成RemotingServer + RemotingClient 的通信
自定义通信协议
Broker
核心
消息存储&发送
磁盘概念
用户user态转换内核态 core,数据拷贝
- disk 加载数据 -> core
- core 拷贝数据 -> user (read)
- user 复制到网卡驱动的内核态 -> net-core
- 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/
单机版
测试应用
- 下载源代码
unzip rocketmq-all-4.4.0-bin-release.zip
cd rocketmq-all-4.4.0-bin-release
- 修改启动脚本的参数
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"
- 启动
先启动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
- 检查启动
检查进程
jps
BrokerStartup
NamesrvStartup
- 测试使用
使用 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
- 关闭
先关闭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
日志自定义输出
结合应用
生产者类型
发送消息步骤
- 设置Producer
- groupName
- instanceName
- retryTimesWhenFailed
- namesrv
- 组装消息并发送
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