RocketMQ 作为一款纯java、分布式、队列模型的开源消息中间件,支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。
核心概念
主要由四大部分组成:
1. Producer:生产者,可以集群部署。它会先和 NameServer 集群中的随机一台建立长连接,得知当前要发送的 Topic 存在哪台 Broke Master上,然后再与其建立长连接,支持多种负载平衡模式发送消息。
2. Consumer:消费者,可以集群部署。它也会先和 NameServer 集群中的随机一台建立长连接,得知当前要消息的 Topic 存在哪台 Broker Master、Slave上,然后它们建立长连接,支持集群消费和广播消费消息。
3. Broker:主要负责消息的存储、查询消费,支持主从部署,一个 Master 可以对应多个 Slave,Master 支持读写,Slave 只支持读。Broker 会向集群中的每一台 NameServer 注册自己的路由信息。
4. NameServer:是一个很简单的 Topic 路由注册中心,支持 Broker 的动态注册和发现,保存 Topic 和 Borker 之间的关系。通常也是集群部署,但是各 NameServer 之间不会互相通信, 各 NameServer 都有完整的路由信息,即无状态。
SpringBoot 集成 RocketMQ
SpringBoot对RocketMQ 进行了封装处理,使用比较简单。
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
applocation.yml
rocketmq:
# MQ地址,如果是集群,则使用;分开
# name-server: ip:端口;ip:端口
name-server: ip:端口
# 消费者
# 当使用阿里云的云Rocketmq,需要配置access-key、secret-key;
# 也可以是云Rocketmq的账号密码 access-key 对应rmq的账号,secret-key 对应rmq的密码;
consumer:
access-key: 111
secret-key: 222
# 生产者
producer:
access-key: 111
secret-key: 222
# 组
group: group-test
# 消息发送超时时间 默认3000
send-message-timeout: 4000
#消息达到4096字节的时候,消息就会被压缩。默认 4096
compress-message-body-threshold: 4096
#最大的消息限制,默认为128K
max-message-size: 4194304
#同步消息发送失败重试次数 默认2
retry-times-when-send-failed: 3
#在内部发送失败时是否重试其他代理,这个参数在有多个broker时才生效
retry-next-server: true
#异步消息发送失败重试的次数 默认2
retry-times-when-send-async-failed: 3
关键类
1. RocketMQTemplate:提供了各种操作MQ的方法。
a. 普通消息
// destination:主题 topic 如果有tag,"topic:tag"
// payload:发送消息内容
void convertAndSend(String destination, Object payload);
除了普通消息只能发送 Object 类型的消息外,其他的还可以发送Message类型的消息;
b. 同步消息
// Object 类型消息
// timeout:消息超时时间,可有可无
// SendResult.getMsgID():发送的消息ID,可以在RocketMQ 中通过ID 查询到该消息
SendResult syncSend(String destination, Object payload, long timeout);
// 发送 Message 类型消息
SendResult syncSend(String destination, Message<?> message, long timeout);
// 延迟消息 只能 Message 类型
// delayLevel:延迟级别,开源版只有固定的延迟级别,不支持任意时间延迟
// 1-18 个级别:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
SendResult syncSend(String destination, Message<?> message, long timeout, int delayLevel);
// 同步顺序消息 Object 除了 timeout 可有可无,其他参数必须有
// hashKey:使用此参数选择队列。 例如:orderId,productId
SendResult syncSendOrderly(String destination, Object payload, String hashKey, long timeout);
SendResult syncSendOrderly(String destination, Message<?> message, String hashKey, long timeout);
c. 异步消息
// SendCallback:有两个方法 1. onSuccess():发送成功 2. onException:发送失败
void asyncSend(String destination, Object payload, SendCallback sendCallback, long timeout);
void asyncSend(String destination, Message<?> message, SendCallback sendCallback, long timeout);
void asyncSend(String destination, Message<?> message, SendCallback sendCallback, long timeout, int delayLevel);
// 异步顺序消息
void asyncSendOrderly(String destination, Object payload, String hashKey, SendCallbacksendCallback, long timeout);
void asyncSendOrderly(String destination, Message<?> message, String hashKey, SendCallbacksendCallback, long timeout);
d. 单向消息:只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答
void sendOneWay(String destination, Object payload);
void sendOneWay(String destination, Message<?> message);
// 单向顺序消息
void sendOneWayOrderly(String destination, Object payload, String hashKey);
void sendOneWayOrderly(String destination, Message<?> message, String hashKey);
2. RocketMQLocalTransactionListener:本地事务监听器 。
3. RocketMQListener:消费信息监听器 。
4. MessageQueueSelector:消息队列选择策略。
5. DefaultMQProducer:默认的生产者。
生产者
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/test")
public void test(){
// 如同需要指定tag:topic:tag
rocketMQTemplate.convertAndSend("topic-test:TagTest", "普通消息");
// 指定key,方便后期在 rocketMQ 中查询消息
Message<String> message = MessageBuilder.withPayload("同步消息").setHeader(RocketMQHeaders.KEYS, "11").build();
SendResult syncSendResult = rocketMQTemplate.syncSend("topic-test:TagTest", message);
rocketMQTemplate.asyncSend("topic-test:TagTest", "异步消息", new SendCallback() {
@Override
public void onSuccess(SendResult asyncSendResult) {
System.out.println("发送异步消息成功 msgId:" + asyncSendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
System.out.println("发送异步消息失败 e:" + e);
}
});
}
}
消费者
@Component
@RocketMQMessageListener(
consumerGroup = "group-test",
topic = "topic-test",
selectorType = SelectorType.TAG,
selectorExpression = "TagTest")
public class TestConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("消费者:" + message);
}
}
测试
消费者:普通消息
消费者:同步消息
发送异步消息成功 msgId:7F00000134C818B4AAC250AD01C80002
消费者:异步消息
@RocketMQMessageListener 主要参数详情
1. consumerGroup 消费组;
2. topic 主题;
3. selectorType 消息选择器类型;
SelectorType.TAG 根据tag选择 (默认值)
SelectorType.SQL92 根据SQL92表达式选择
4. selectorExpression 选择器表达式,默认值 "*"
5. consumeMode 消费模式
ConsumeMode.CONCURRENTLY 并行处理 (默认值)
ConsumeMode.ORDERLY 按顺序处理
6. messageModel 消息模型
MessageModel.CLUSTERING 集群 (默认值)
MessageModel.BROADCASTING 广播
事务消息
1. 生产者将半事务消息推送给MQ。
2. MQ将消息持久化成功后,向生产者返回ACK,确认消息发送成功,此时消息为半事务消息。
3. 生产者执行本地事务逻辑。
4. 生产者根据本地事务执行结果向MQ二次提交(Commit、Rollback、Unknown)。
a. TransactionStatus.CommitTransaction: 提交事务,它允许消费者消费此消息。
b. TransactionStatus.RollbackTransaction: 回滚事务,它代表该消息将被删除,不允许被消费。
c. TransactionStatus.Unknown: 未知状态,它代表需要检查消息队列来确定状态。
5. 在断网后者未知状态下,MQ没有收到生产者发送的二次确认结果,在经过固定时间后,MQ对生产者发起消息回查。
a. 生产者收到消息回查后,需要检查对应的消息本地事务执行结果。
b. 生产者根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理。
回查
- 回查间隔时间:系统默认每隔30秒发起一次定时任务,对未提交的半事务消息进行回查,共持续12小时。
- 第一次消息回查最快时间:该参数支持自定义设置。若指定消息未达到设置的最快回查时间前,系统默认每隔30秒一次的回查任务不会检查该消息。
生产者
@RestController
@RequestMapping("/test")
public class Test {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@RequestMapping("/sendTransactionMessage")
public void sendTransactionMessage(@RequestParam("msg") String msg,
@RequestParam("key") String key) {
Message<String> message = MessageBuilder.withPayload(msg).setHeader(RocketMQHeaders.KEYS, key).build();
// 发送事务消息
TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction("topic-test", message, null);
// 发送状态
String sendStatus = transactionSendResult.getSendStatus().name();
// 本地事务执行状态
String localState = transactionSendResult.getLocalTransactionState().name();
System.out.println("发送状态:" + sendStatus + ";本地事务执行状态" + localState);
}
}
事务监听器
/**
* 事务消息监听器:监听本地事务执行的状态和检查本地事务状态
*/
@RocketMQTransactionListener
public class MQTransactionListener implements RocketMQLocalTransactionListener {
/**
* 执行本地事务(在发送消息成功时执行)
* 处理业务后,根据业务处理情况,返回事务执行状态
* rollback:回滚事务,消息将被丢弃不允许消费
* commit:提交事务,允许消费者消费该消息
* unknown:暂时无法判断状态,等待固定时间以后消息队列RocketMQ根据回查规则向生产者进行消息回查
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
/**
* 模拟返回事务状态
*/
int index = 0;
switch (index) {
case 1:
String jsonStr = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8);
System.out.println("本地事务回滚,回滚消息:" + jsonStr);
return RocketMQLocalTransactionState.ROLLBACK;
case 2:
System.out.println("unknown");
return RocketMQLocalTransactionState.UNKNOWN;
default:
System.out.println("事务提交,消息正常处理");
return RocketMQLocalTransactionState.COMMIT;
}
}
/**
* 检查本地事务的状态
* 当MQ未得到生产者应答,或者超时,或者应答是unknown的情况,调用此方法进行检查确认,返回值和上面的方法一样
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
String jsonStr = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8);
System.out.println("调用回查本地事务接口:" + jsonStr);
return RocketMQLocalTransactionState.COMMIT;
}
}
消费者
@Component
@RocketMQMessageListener(consumerGroup = "group-test", topic = "topic-test")
public class MQConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("消费者:" + message);
}
}
测试
Commit
Rollback
模拟 index = 1
Unknown
模拟 index = 2,消息回查