RocketMQ学习笔记
文章目录
1. RocketMQ概述
特点:
1.能够保证严格的消息顺序
2.提供丰富的消息拉取模式
3.高效的订阅者水平扩展能力
4.实时的消息订阅机制
5.支持事务消息
6.亿级消息堆积能力
。普通消息
。顺序消息 ----能实现有序消费
。事务消息 ----可以解决分布式事务实现数据最终一致
消息发送方式:
。同步消息-----消息发送中进入同步等待状态,可以保证消息投递一定到达
。异步消息-----想要快速发送消息,又不想丢失的时候可以使用异步消息
producer.send(message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("消息发送成功");
System.out.println("sendResult="+sendResult);
}
@Override
public void onException(Throwable throwable) {
//跳转业务逻辑
System.out.println("发送异常");
}
});
。单向消息 -----只发送消息,不等待服务器响应,只发送请求不等待应答。
此方式发送消息的过程耗时非常 短,一般在微秒级别。
producer.sendOneway(message);
。批量发送消息
4大中间件:
。消息中间件
。缓存中间件
。文件存储中间件
。搜索中间件
2. RocketMQ下载安装
2.1 RocketMQ下载
一、下载
1、官网下载:
http://rocketmq.apache.org/
2、百度网盘下载:
下载地址 提取码:0g5a
3、RocketMQ控制台下载:
https://github.com/apache/rocketmq-externals/tree/master/
2.2 RocketMQ安装
1、前提条件:
已安装jdk1.8
2、将下载下的安装文件解压到本地磁盘
3、配置环境变量
变量名:ROCKETMQ_HOME
变量值:D:\01program_soft\01install_before\00903RocketMQ\rocketmq-all-4.6.0-bin-release
修改path,添加
4、启动
(1)启动NAMESERVER
使用cmd打开命令窗口,进入到rocketmq的bin目录下,执行:start mqnamesrv.cmd,
成功会弹出如下提示框,请勿关闭此框。
(2)启动brocker
同样在rocketmq的bin目录下,执行:start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true , 启动BROKER。成功后会弹出提示框,此框勿关闭。
如果弹出框提示‘错误: 找不到或无法加载主类 xxxxxx’。在bin下找到并打开runbroker.cmd,然后将‘%CLASSPATH%’加上英文双引号。保存并重新执行start语句。如下:
修改完毕后,再次启动即可出现成功弹框如下:
三、安装可视化插件
1、github下载:
2、解压到d:\mq_rocket文件夹下
3.命令行进入到D:\01program_soft\01install_before\00903RocketMQ目录下,
执行命令 java -jar rocketmq-console-ng-1.0.1.jar,回车
出现上面的界面,就说明控制台已经启动成功。
2.3 RocketMQ控制台
控制台地址:http://localhost:17890
用户名: 不用
密 码:不用
3. RocketMQ角色
部署模型:
broker:
。broker面向producer和consumer接收和发送消息
。面向nameServer提交自己的信息
。是消息中间件的消息存储、转发服务器
。每个broker节点,在启动时,都会遍历NameServer列表,与每个NameServer建立连接,注册自己的信息之后定时上报
NameServer:注册中心 == zookeeper
。服务注册
。服务发现
。路由管理
nameServer是服务发现者,集群中各个角色(producer,broker,consumer)都需要定时向nameServer上报自己的状态,以便互相发现彼此,超时不上报的话,nameServer会把它从列表中删除
。为什么不用zookeeper? ----rockermq希望为了提高性能,CAP定理,客户端负载均衡
producer:生产者 -----> master 主: 消息写入
slave 从: 发消息 ------> customer:消费者
producer:
。消息的生产者
。通过集群中的其中一个节点建立长连接,获得topic的路由信息,包括topic下面有哪些queue,这些queue分
部在哪些broker上
consumer:
。消息的消费者
。通过NameServer集群获得topic的路由信息,连接到对应的broker上消费消息
注意:由于master和slave都可以读取消息,因此consumer会与master和slave都建立连接
topic和queue的关系:
4.点对点模式
4.1 生产者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
/**
* 说明: 点对点模式 生产者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class Producer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
//1.创建DefaultMQProducer
DefaultMQProducer producer = new DefaultMQProducer("order_demo_producer_group11");
//2.设置Namesrv地址 注册中心
producer.setNamesrvAddr("127.0.0.1:9876");
//3.开启DefaultMQProducer
producer.start();
/**
* 4.创建消息message
* 参数1 topic 主题
* 参数2 tags 标记 用于过滤
* 参数3 keys 消息的唯一值
* 参数4 body 消息
*/
Message message = new Message("test1","hello
rocketmq".getBytes(RemotingHelper.DEFAULT_CHARSET));
//5.同步发送消息 同步:发送--等待回复 --brocker返回的消息
SendResult result = producer.send(message);
System.out.println(result);
//6.关闭DefaultMQProducer
producer.shutdown();
}
}
4.2 消费者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* 说明:点对点模式 消费者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class Consumer {
public static void main(String[] args) throws MQClientException {
//1.创建DefaultMQProducer
DefaultMQPushConsumer consumer = new
DefaultMQPushConsumer("order_demo_producer_group11");
//2.设置namesrv地址
consumer.setNamesrvAddr("127.0.0.1:9876");
/**
* 3.设置subscribe,这里是要读取的主题信息
* 参数1 topic 消费的主题
* 参数2 过滤器 *:不过滤
*/
consumer.subscribe("test1","*");
//设置消息拉取上限
consumer.setConsumeMessageBatchMaxSize(2);
//4.创建消息监听 messageListener
consumer.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//循环消息
for(MessageExt messageExt:list){
//获取主题
System.out.println("consumer主题:"+messageExt.getTopic());
//5.获取消息信息
System.out.println("consumer信息内容:"+new String(messageExt.getBody()));
}
//6.返回消息读取状态
//默认情况下,这条消息只会被一个消费者消费 点对点模式
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //接收状态确认ack
}
});
//开启consumer
consumer.start();
}
}
5. 批量消息发送
可以多条消息打包一起发送,减少网络传输次数提高效率。
。批量消息要求必须具有同一topic,相同消息配置
。不支持延时消息
。建议一个批量消息最好不超过1MB大小
。如果不确定是否超过限制,可以手动计算大小分批发送
5.1 生产者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
* 说明: 批量消息发送 生产者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class BatchProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
//1.创建DefaultMQProducer
DefaultMQProducer producer = new DefaultMQProducer("order_demo_producer_group11");
//2.设置Namesrv地址 注册中心
producer.setNamesrvAddr("127.0.0.1:9876");
//3.开启DefaultMQProducer
producer.start();
/**
* 4.创建消息message
* 参数1 topic 主题
* 参数2 tags 标记 用于过滤
* 参数3 keys 消息的唯一值
* 参数4 body 消息
*/
Message message1 = new Message("test2","hello
rocketmq1".getBytes(RemotingHelper.DEFAULT_CHARSET));
Message message2 = new Message("test2","hello
rocketmq2".getBytes(RemotingHelper.DEFAULT_CHARSET));
Message message3 = new Message("test2","hello
rocketmq3".getBytes(RemotingHelper.DEFAULT_CHARSET));
List<Message> list = new ArrayList<Message>();
list.add(message1);
list.add(message2);
list.add(message3);
//5.批量同步发送消息 同步:发送--等待回复 --brocker返回的消息
SendResult result = producer.send(list);
System.out.println(result);
//6.关闭DefaultMQProducer
producer.shutdown();
}
}
5.2 消费者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* 说明:点对点模式 消费者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class Consumer {
public static void main(String[] args) throws MQClientException {
//1.创建DefaultMQProducer
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order_demo_producer_group11");
//2.设置namesrv地址
consumer.setNamesrvAddr("127.0.0.1:9876");
/**
* 3.设置subscribe,这里是要读取的主题信息
* 参数1 topic 消费的主题
* 参数2 过滤器 *:不过滤
*/
consumer.subscribe("test1","*");
//设置消息拉取上限
consumer.setConsumeMessageBatchMaxSize(2);
//4.创建消息监听 messageListener
consumer.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list,
ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//循环消息
for(MessageExt messageExt:list){
//获取主题
System.out.println("consumer主题:"+messageExt.getTopic());
//5.获取消息信息
System.out.println("consumer信息内容:"+new String(messageExt.getBody()));
}
//6.返回消息读取状态
//默认情况下,这条消息只会被一个消费者消费 点对点模式
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //接收状态确认ack
}
});
//开启consumer
consumer.start();
}
}
6.异步处理
异步可靠消息
不会阻塞,等待broker的确认
采用事件监听方式接受broker返回的确认
6.1 生产者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
/**
* 说明: 异步处理 生产者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class AsynProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
//1.创建DefaultMQProducer
DefaultMQProducer producer = new DefaultMQProducer("order_demo_producer_group11");
//2.设置Namesrv地址 注册中心
producer.setNamesrvAddr("127.0.0.1:9876");
//3.开启DefaultMQProducer
producer.start();
/**
* 4.创建消息message
* 参数1 topic 主题
* 参数2 tags 标记 用于过滤
* 参数3 keys 消息的唯一值
* 参数4 body 消息
*/
Message message = new Message("test1","hello
rocketmq".getBytes(RemotingHelper.DEFAULT_CHARSET));
//5.异步可靠消息
// 不会阻塞,等待broker的确认
// 采用事件监听方式接受 broker返回的确认
producer.send(message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("消息发送成功");
System.out.println("sendResult="+sendResult);
}
@Override
public void onException(Throwable throwable) {
//跳转业务逻辑
System.out.println("发送异常");
}
});
}
}
6.2 消费者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* 说明:异步消息 消费者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class Consumer {
public static void main(String[] args) throws MQClientException {
//1.创建DefaultMQProducer
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order_demo_producer_group11");
//2.设置namesrv地址
consumer.setNamesrvAddr("127.0.0.1:9876");
/**
* 3.设置subscribe,这里是要读取的主题信息
* 参数1 topic 消费的主题
* 参数2 过滤器 *:不过滤
*/
consumer.subscribe("test1","*");
//设置消息拉取上限
consumer.setConsumeMessageBatchMaxSize(2);
//4.创建消息监听 messageListener
consumer.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//循环消息
for(MessageExt messageExt:list){
//获取主题
System.out.println("consumer主题:"+messageExt.getTopic());
//5.获取消息信息
System.out.println("consumer信息内容:"+new String(messageExt.getBody()));
}
//6.返回消息读取状态
//默认情况下,这条消息只会被一个消费者消费
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //接收状态确认ack
}
});
//开启consumer
consumer.start();
}
}
7.群组和广播模式
consumer.setMessageModel(MessageMode.BROADCASTING); //设置广播模式
consumer.setMessageModel(MessageMode.CLUSTERING); //集群
//集群 --> 一组 consumer
//广播 所有 consumer
tag ----- 是用来过滤消息 消息分组
key ----- 消息的唯一标识
可以使用tag来过滤消费
在producer中使用Tag:
Message msg = new Message(“TopicTest”,“TagA”,("hello
RocketMQ").getBytes(RemotingHelper.DEFAULT_CHARSET));
在consumer中订阅Tag:
consumer.subscribe(“tipicTest”,“TagA|TagB”); //代表订阅Topic下的所有消息
8.sql过滤
broker.conf文件增加属性:
EnablePropertyFilter =true
8.1 生产者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
/**
* 说明: sql过滤 生产者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class SQLProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
//1.创建DefaultMQProducer
DefaultMQProducer producer = new DefaultMQProducer("sql_producer_group11");
//2.设置Namesrv地址 注册中心
producer.setNamesrvAddr("127.0.0.1:9876");
//3.开启DefaultMQProducer
producer.start();
/**
* 4.创建消息message
* 参数1 topic 主题
* 参数2 tags 标记 用于过滤
* 参数3 keys 消息的唯一值
* 参数4 body 消息
*/
for(int i=0;i<100;i++){
Message message = new Message("sql",("hello
rocketmq"+i).getBytes(RemotingHelper.DEFAULT_CHARSET));
message.putUserProperty("age",String.valueOf(i));
//5.同步发送消息 同步:发送--等待回复 --brocker返回的消息
SendResult result = producer.send(message);
System.out.println(result);
}
//6.关闭DefaultMQProducer
producer.shutdown();
}
}
8.2 消费者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* 说明:sql过滤 消费者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class SQLConsumer {
public static void main(String[] args) throws MQClientException {
//1.创建DefaultMQProducer
DefaultMQPushConsumer consumer = new
DefaultMQPushConsumer("sql_producer_group11");
//2.设置namesrv地址
consumer.setNamesrvAddr("127.0.0.1:9876");
MessageSelector messageSelector = MessageSelector.bySql("age >= 18 and age <= 28");
/**
* 3.设置subscribe,这里是要读取的主题信息
* 参数1 topic 消费的主题
* 参数2 过滤器 *:不过滤
*/
consumer.subscribe("sql",messageSelector);
//设置消息拉取上限
consumer.setConsumeMessageBatchMaxSize(2);
//4.创建消息监听 messageListener
consumer.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list,
ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//循环消息
for(MessageExt messageExt:list){
//获取主题
System.out.println("consumer主题:"+messageExt.getTopic());
//5.获取消息信息
System.out.println("consumer信息内容:"+new
String(messageExt.getBody()));
}
//6.返回消息读取状态
//默认情况下,这条消息只会被一个消费者消费 点对点模式
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //接收状态确认ack
}
});
//开启consumer
consumer.start();
}
}
9.RocketMQ事务消息
2pc: 2次提交 第1次提交时,不会立即生效,立即消费
RocketMQ事务消息用的就是2pc这种方式
tcc: try(锁住)–comfirm(确认)–cancel(取消)
producer----开启事务 ----发送消息(half message) ----broker:打标记—HFM发送确认
分布式系统中的事务可以使用2pc、TCC(try,confirm,cancle)来解决分布式系统中的消息原子性
RocketMQ4.3+提供分布式事务功能,通过RocketMQ事务消息能达到分布式事务的最终一致
RockMQ实现方式:
Half Message:预处理消息,当broker收到此消息后,会存储到RMQ_SYS_RANS_HALF_TOPIC的消息消费队列中
检查事务状态:broker会开启一个定时任务,消息RMQ_SYS_TRANS_HALF_TOPIC队列中的消息,每次执行任务会想向消息发送者确认执行状态(提交、回滚、未知),如果是未知,等待下一次回调。
超时:如果超过回调次数,默认回滚消息
TransactionListener的两个方法:
executeLocalTransaction:半消息发送成功触发此方法来执行本地事务
checkLocalTransaction: brocker将发送检查消息来检查事务状态,并将调用此方法来获取本地事务状态
本地事务执行状态:
执行成功,确认提交:LocalTransactionState.COMMIT_MESSAGE
回滚消息,broker端会删除版消息:LocalTransactionState.ROLLBACK_MESSAGE
暂时为未知状态,等待broker回调:LocalTransactionState.UNKNOW
问题:producer.send()放入事务里,还是事务外?
9.1 生产者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
/**
* 说明: 事务消息 生产者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class TransactionProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
//1.创建DefaultMQProducer
TransactionMQProducer producer = new
TransactionMQProducer("transaction_producer_group11");
//2.设置Namesrv地址 注册中心
producer.setNamesrvAddr("127.0.0.1:9876");
//回调
producer.setTransactionListener(new TransactionListener() {
//事务操作---几个方法 是原子性操作
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
//执行本地业务
System.out.println("===executeLocalTransaction====");
System.out.println("msg:="+new String(message.getBody()));
System.out.println("msg:="+message.getTransactionId()); //事务id
//----原子性操作--------
try{
//a(); 提交注册信息
//b(); 写入数据库
//c(); 新用户 发短信
}catch (Exception e){
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.COMMIT_MESSAGE;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
//broker端 回调,检查事务
System.out.println("===checkLocalTransaction====");
System.out.println("msg:="+new String(messageExt.getBody()));
System.out.println("msg:="+messageExt.getTransactionId()); //事务id
//根据那条message自己写逻辑检查
//事务执行成功
return LocalTransactionState.COMMIT_MESSAGE;
//等会儿
//return LocalTransactionState.UNKNOW;
//回滚
//return LocalTransactionState.ROLLBACK_MESSAGE;
}
});
//3.开启DefaultMQProducer
producer.start();
/**
* 4.创建消息message
* 参数1 topic 主题
* 参数2 tags 标记 用于过滤
* 参数3 keys 消息的唯一值
* 参数4 body 消息
*/
Message message = new Message("transaction","hello
transaction".getBytes(RemotingHelper.DEFAULT_CHARSET));
//5.同步事务消息
SendResult result = producer.sendMessageInTransaction(message,null);
System.out.println(result);
//6.关闭DefaultMQProducer
//producer.shutdown();
}
}
9.2 消费者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* 说明:事务消息 消费者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class TransactionConsumer {
public static void main(String[] args) throws MQClientException {
//1.创建DefaultMQProducer
DefaultMQPushConsumer consumer = new
DefaultMQPushConsumer("order_demo_producer_group11");
//2.设置namesrv地址
consumer.setNamesrvAddr("127.0.0.1:9876");
/**
* 3.设置subscribe,这里是要读取的主题信息
* 参数1 topic 消费的主题
* 参数2 过滤器 *:不过滤
*/
consumer.subscribe("test1","*");
//设置消息拉取上限
consumer.setConsumeMessageBatchMaxSize(2);
//4.创建消息监听 messageListener
consumer.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list,
ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//循环消息
for(MessageExt messageExt:list){
//获取主题
System.out.println("consumer主题:"+messageExt.getTopic());
//5.获取消息信息
System.out.println("consumer信息内容:"+new String(messageExt.getBody()));
}
//6.返回消息读取状态
//默认情况下,这条消息只会被一个消费者消费 点对点模式
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //接收状态确认ack
}
});
//开启consumer
consumer.start();
}
}
10.重试机制
producer
默认超时时间 private int sendMsgTimeOut = 30000;
同步发送时,重试次数:默认是2
producer.setRetryTimesWhenSendFaild(3);
异步发送时,重试次数: 默认是2
producer.setRetryTimeWhenSendAsyncFailed();
Consumer
消费超时 单位分钟
consumer.setConsumerTimeOut();
发送ack,消费失败
RECONSUMER_LATER
broker投递
只有在消息模式为MessageModelCLUSTERING集群模式时,broker才会自动进行重试,广播消息不重试,重投使用messageDelayLevel
默认值:
messageDelayLevel: 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
11.重复消费
11.1 消息丢失
SendResult
producer在发送同步/异步可靠消息后,会接收到SendResult,表示消息发送成功。
SendResult其中属性sendStatus表示了broker是否真正完成了消息存储
当sendStatus=‘ok’的时候,应该重新发送消息,避免丢失
11.2 消息重复消费
影响消息正常发送和消费的重要原因是网络的不确定性。
引起重复消费的原因:
ACK
正常情况下在consumer真正消费完消息后应该发送ack,通知broker该消息正常消费,从queue中剔除,
当ack因为网络原因无法发送到broker,broker会认为此条消息没有被消费,此后会被开启消息重投机制把消息再次投递到consumer
group
在SLUSTERING模式下,消息在broker中会保证相同group的consumer消费一次(每个group的一个consumer),但是针对不同group的consumer会推送多次
解决方案:
数据库表
处理消息前,使用消息主键在表中带有约束的字段insert
Map
单机时可以使用map concurrentHashMap -->putIfAbsent
Redis
使用主键或set操作 比如用messageId作为主键
12.顺序消费
1.同一个topic(一个topic包含多个queue:数组 先进先出)
2.同一个queue
3.发消息的时候一个线程去发送消息
。消费的时候一个线程 消费一个queue里的消息
。多个queue只能保证单个queue里的顺序
12.1 生产者
选择具体的queue:
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
import java.util.List;
/**
* 说明: 指定queue 生产者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class QueueProducer {
public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
//1.创建DefaultMQProducer
DefaultMQProducer producer = new DefaultMQProducer("queue_producer_group11");
//2.设置Namesrv地址 注册中心
producer.setNamesrvAddr("127.0.0.1:9876");
//3.开启DefaultMQProducer
producer.start();
/**
* 4.创建消息message
* 参数1 topic 主题
* 参数2 tags 标记 用于过滤
* 参数3 keys 消息的唯一值
* 参数4 body 消息
*/
for(int i=0;i<20;i++){
Message message = new Message("queue",("hello rocketmq"+i).getBytes(RemotingHelper.DEFAULT_CHARSET));
//5.发送消息 queue选择器 向topic哪个queue去写消息
SendResult result = producer.send(message, new MessageQueueSelector() {
//手动选择一个queue
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
MessageQueue queue = list.get((Integer) o);
//选好的queue
return queue;
}
//arg:第几个queue timeout:超时时间
},0,20000);
}
for(int i=0;i<20;i++){
Message message = new Message("queue",("hello
rocketmq"+i).getBytes(RemotingHelper.DEFAULT_CHARSET));
//5.发送消息 queue选择器 向topic哪个queue去写消息
SendResult result = producer.send(message, new MessageQueueSelector() {
//手动选择一个queue
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
MessageQueue queue = list.get((Integer) o);
//选好的queue
return queue;
}
//arg:第几个queue timeout:超时时间
},1,20000);
}
//6.关闭DefaultMQProducer
producer.shutdown();
}
}
12.2 消费者
package com.tangguanlin.rocketmq;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* 说明:rocketmq 顺序消费者
* 作者:汤观林
* 日期:2021年10月07日 10时
*/
public class QueueConsumer {
public static void main(String[] args) throws MQClientException {
//1.创建DefaultMQProducer
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("queue_producer_group11");
//2.设置namesrv地址
consumer.setNamesrvAddr("127.0.0.1:9876");
/**
* 3.设置subscribe,这里是要读取的主题信息
* 参数1 topic 消费的主题
* 参数2 过滤规则
*/
consumer.subscribe("queue","*");
//开启最大线程数 最好用一个线程去消费,保持顺序消费
consumer.setConsumeMessageBatchMaxSize(2);
//4.创建消息监听 messageListener
consumer.setMessageListener(new MessageListenerOrderly() {
//单个queue是单个线程
//多个queue是多个线程
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list,
ConsumeOrderlyContext consumeOrderlyContext) {
//读取消息
for(MessageExt messageExt:list){
//获取主题
String topic = messageExt.getTopic();
//获取标签
String tags = messageExt.getTags();
String result = new String(messageExt.getBody());
System.out.println("order消息的主题="+topic +" order消息的内
容="+result+" 线程名称"+
Thread.currentThread().getName()+"
queueId="+messageExt.getQueueId());
}
return ConsumeOrderlyStatus.SUCCESS;
}
}
);
//开启consumer
consumer.start();
}
}
13.长轮询机制
1.offset 消息的数组下标
queue.getOffset(); //队列的下标
offset的存储与加载:
rocketMQ的broker端中,offset的是以json的形式持久化到磁盘文件中,文件路径为
${user.home}/store/config/consumerOffset.json
2.长轮询机制
2.1 轮询: http协议
没发起一次请求,都返回数据,没有数据就返回空数据 消耗大,特别影响性能
2.2 长连接:
客户端和服务器一直保持连接,当客户端多了之后,服务器压力很大,很影响服务器性能,
而且,消息会不停的给某一个客户端,导致该客户端消化不了,不能按需消费,主动权在服务器打算
2.3 长轮询机制:
客户端发起请求,服务器端有消息就返回,没有消息就挂起,等有消息了后返回给客户端,
主动权在客户端,客户端按需消费,消费完一个消息,再去跟服务器端要一个消息。这个机制就很美好。
RocketMQ就是采用的长轮询机制的。
14.SpringBoot整合RocketMQ
(略)
15.RocketMQ的集群(略)
容="+result+" 线程名称"+
Thread.currentThread().getName()+"
queueId="+messageExt.getQueueId());
}
return ConsumeOrderlyStatus.SUCCESS;
}
}
);
//开启consumer
consumer.start();
}
}
# 13.长轮询机制
1.offset 消息的数组下标
[外链图片转存中...(img-ct9gCXKh-1634466791553)]
queue.getOffset(); //队列的下标
offset的存储与加载:
rocketMQ的broker端中,offset的是以json的形式持久化到磁盘文件中,文件路径为
${user.home}/store/config/consumerOffset.json
[外链图片转存中...(img-SjthNjkT-1634466791554)]
[外链图片转存中...(img-g1H6tn2u-1634466791555)]
2.长轮询机制
[外链图片转存中...(img-0AVk3Lud-1634466791557)]
[外链图片转存中...(img-LVlVgEEa-1634466791558)]
2.1 轮询: http协议
没发起一次请求,都返回数据,没有数据就返回空数据 消耗大,特别影响性能
2.2 长连接:
客户端和服务器一直保持连接,当客户端多了之后,服务器压力很大,很影响服务器性能,
而且,消息会不停的给某一个客户端,导致该客户端消化不了,不能按需消费,主动权在服务器打算
2.3 长轮询机制:
客户端发起请求,服务器端有消息就返回,没有消息就挂起,等有消息了后返回给客户端,
主动权在客户端,客户端按需消费,消费完一个消息,再去跟服务器端要一个消息。这个机制就很美好。
RocketMQ就是采用的长轮询机制的。
# 14.SpringBoot整合RocketMQ
(略)
# 15.RocketMQ的集群(略)