NameServer:RocketMQ的注册中心,管理集群的Topic-Queue的路由配置、Broker的实时配置信息。其它模块通过NameServer提供的接口获取最新的Topic配置和路由信息。各 NameServer 都有完整的路由信息,不互相通信。
Broker:负责接收并存储消息,同时提供Push/Pull接口来将消息发送给Consumer。可以通过MessageID和MessageKey来查询消息。Borker会将自己的Topic配置信息实时同步到NameServer。
角色:SYNC_MASTER(同步主机)、ASYNC_MASTER(异步主机)、及SLAVE(从机)。
消息的可靠性要较高可采用:SYNC_MASTER、SLAVE 部署方式。
消息可靠性要求不高可采用:ASYNC_MASTER、SLAVE 部署方式。
刷新 FlushDiskType
SYNC_FLUSH(同步刷新)可靠,性能低。
ASYNC_FLUSH(异步处理)性能高,不如同步可靠。
Topic:消息主题。
Topic Partition:分区,物理上的概念,每个Topic包含一个或多个分区。
Offset:消费位点,每个分区当前消息的总条数为最大位点MaxOffset;每个分区的起始位置为起始位点MinOffset。
Group:一组生产者或消费者,这组生产者或消费者通常生产或消费同一组消息,消息发布或订阅的逻辑一致。
Producer:消息发布者,发送消息至Broker。
Consumer:消息订阅者,从Broker接收并消费消息。
Queue:队列,消息主题有多个队列来存消息。
Message:消息载体.
Message Key:消息业务标识,由生产者(Producer)设置,标识某个业务逻辑。
Message ID:消息全局唯一标识,由消息队列RocketMQ系统自动生成,标识某条消息。
Message Tag:消息标签,进一步区分某个Topic下的消息分类。
消息类型:普通消息、顺序消息、事务消息、延迟消息
集群消费:一个Group的所有Consumer平均分摊消费消息。
广播消费:一个Group的所有Consumer都会各自消费某条消息一次。
定时消息:消息到RocketMQ,推迟到某个时间,进行消费,即为定时消息。
延时消息:消息到RocketMQ,延迟一定时间后,进行消费,即为延时消息。
事务消息:类似XA的分布事务功能,RocketMQ的能达到分布式事务的最终一致。
顺序消息:按照顺序进行发布和消费的消息类型,分为全局顺序、分区顺序。
全局顺序消息:对于指定的一个Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。
分区顺序消息:对于指定的一个Topic,所有消息根据Sharding Key进行区块分区。同一个分区内的消息按照严格的FIFO顺序进行发布和消费。
消息堆积:服务端保存着大量未被消费的消息,即消息堆积。
消息过滤:RocketMQ的服务端完根据消息标签(Tag)对消息进行过滤,Consumer只接收被过滤后的消息类型。
消息轨迹:消费处理过程中,各个节点的时间、地点等数据汇聚而成的完整链路信息。
重置消费位点:以时间轴为坐标,消息持久化存储的时间范围内(默认3天),重置Consumer对已订阅的Topic的消费进度,重置后Consumer将接到设定时间点之后由Producer发送到消息队列RocketMQ服务端的消息。
死信队列:死信队列用于处理无法被正常消费的消息,存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。
削峰填谷:如秒杀、抢红包、企业开门红等带来较高的流量脉冲,消息队列RocketMQ可提供削峰填谷的服务来解决该问题。
异步解耦:主业务完成后,RocketMQ实现异步通信与下游业务解耦,确保主站业务的连续性
顺序收发:与先进先出FIFO(First In First Out)原理类似,消息队列RocketMQ提供的顺序消息即保证消息FIFO。
分布式事务一致性:交易系统、支付红包等场景,RocketMQ可实现系统的解耦,又可保证最终的数据一致性。
大数据分析:RocketMQ与流式计算引擎相结合,可实现业务数据的实时分析。
分布式缓存同步:RocketMQ构建分布式缓存,实时通知数据变化。
1、NameServer 启动,Broker 启动时向 NameServer 注册。
2、生产者在发送某个主题的消息之前先从 NamerServer 获取 Broker 服务器地址列表(可能是集群),然后根据负载均衡算法从列表中选择一台 Broker 进行消息发送。
3、NameServer 与每台 Broker 服务器保持长连接,并间隔 30S 检测 Broker 是否存活,如果检测到 Broker 宕机(使用心跳机制,如果检测超过 120S),则从路由注册表中将其移除。
4、消费者在订阅某个主题的消息之前从 NamerServer 获取 Broker 服务器地址列表(可能是集群),消费者选择从 Broker 中 订阅消息,订阅 规则由 Broker 配置决定。
本地文件存储系统,将所有topic的消息全写入同一个文件中(commit log),保证IO写入的顺序性。
由于消息混合存储在一起,要将每个消费者组消费topic最后的偏移量记录下来,就是consumer queue(索引文件),消息在写入commit log 文件时还将偏移量信息写入consumer queue文件。
索引文件中会记录消息的物理位置、偏移量offse,消息size等,消费者消费时根据上述信息就可以从commit log文件中快速找到消息信息。
消息分布在各个broker上,一旦某个broker宕机,该broker上的消息读写会受影响。
RocketMQ提供了master/slave的结构,master宕机,slave提供消费服务,但不能写入消息,此过程对应用透明,由rocketmq内部解决。
扫描间隔:默认10秒,由broker配置参数cleanResourceInterval决定
清理时机:默认每天凌晨4点,由broker配置参数deleteWhen决定;或者磁盘空间达到阈值
文件保留时长:默认72小时,由broker配置参数fileReservedTime决定
空间阈值:当磁盘空间达到阈值时,不再接受消息,broker打印出日志,消息发送失败,阈值为固定值85%
生产者
1.Tag的使用
一个应用尽量用一个Topic,子类型则可用tags标识。消费方订阅消息时用tags通过broker过滤,5.x SDK 可用messageBuilder.setTag(“messageTag”)。2.Keys的使用
每个消息在业务层面设置唯一标识到keys字段,方便定位消息丢失问题。3.日志的打印
消息发送成功或者失败要打印消息日志,用于业务排查问题。4.消息发送失败处理方式
Producer的send方法本身支持内部重试,5.x SDK的重试逻辑参考发送重试策略:
消费者
消费过程幂等
RocketMQ无法避免消息重复(消费者主动重发、因客户端重投机制导致的重复等),可以借助关系数据库去重。
消费速度慢的处理方式
1.提高消费并行度
可以通过加机器,或者在已有机器启动多个线程,5.x PushConsumer SDK 可以通过 PushConsumerBuilder.setConsumptionThreadCount();设置线程数。
2.批量方式消费
某些业务流程如果支持批量方式消费,可大程度提高消费吞吐量,5.xSDK的SimpleConsumer,每次接口调用设置批次大小,一次性拉取消费多条消息。
3.重置位点跳过非重要消息
发生消息堆积时,消费速度追不上发送速度,业务数据要求不高,可丢弃不重要的消息。用重置位点功能直接调整消费位点到指定时刻或者指定位置。
4.优化每条消息消费过程
1.引入依赖
org.apache.rocketmq
rocketmq-spring-boot-starter
2.2.3
2.配置参数
rocketmq:
# 服务地址,多个用逗号分开
name-server: 10.5.103.6:9876
consumer:
group: springboot_consumer_group
# 一次拉取消息最大值,注意是拉取消息的最大值而非消费最大值
pull-batch-size: 10
producer:
# 发送同一类消息的设置为同一个group,保证唯一
group: springboot_producer_group
# 发送消息超时时间,默认3000
sendMessageTimeout: 10000
# 发送消息失败重试次数,默认2
retryTimesWhenSendFailed: 2
# 异步消息重试此处,默认2
retryTimesWhenSendAsyncFailed: 2
# 消息最大长度,默认1024 * 1024 * 4(默认4M)
maxMessageSize: 4096
# 压缩消息阈值,默认4k(1024 * 4)
compressMessageBodyThreshold: 4096
# 是否在内部发送失败时重试另一个broker,默认false
retryNextServer: false
3.其他配置
Name Server相关配置:
rocketmq.namesrv.domain:Name Server的域名,用于自动发现Name Server地址。Producer相关配置:
rocketmq.producer.group:生产者组名,用于标识一组具有相同功能的生产者。
rocketmq.producer.send-msg-timeout:发送消息的超时时间,默认为3秒。
rocketmq.producer.compress-msg-body-over-howmuch:消息体大于指定字节大小时启用压缩。Consumer相关配置:
rocketmq.consumer.group:消费者组名,用于标识一组具有相同功能的消费者。
rocketmq.consumer.consume-thread-min:消费者线程池的最小线程数。
rocketmq.consumer.consume-thread-max:消费者线程池的最大线程数。
rocketmq.consumer.consume-message-batch-max-size:批量消费消息时每次拉取的最大消息数量。
rocketmq.consumer.pull-interval:拉取消息间隔时间,默认为0,表示尽可能快地拉取消息。Message相关配置:
rocketmq.message.max-size:消息的最大大小,默认为4MB。
rocketmq.message.compress-level:消息压缩级别,可选值为0(不压缩)到9(最高压缩率)。
rocketmq.message.timeout:消息的过期时间,默认为3天。集群模式相关配置:
rocketmq.broker.cluster.name:Broker集群的名称。
rocketmq.broker.cluster.slave-read-only:Slave节点是否只读,默认为true。
import com.alibaba.fastjson.JSON;
import com.south.spring.note.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.List;
@Slf4j
@Component
public class MQProducerService {
@Value("${rocketmq.producer.send-message-timeout}")
private Integer messageTimeOut;
// 正常规模项目统一用一个 TOPIC
private static final String topic = "JUN_TEST_TOPIC";
// 标签1
private static final String tag1 = ":tag1";
// 标签2
private static final String tag2 = ":tag2";
// 标签3
private static final String tag3 = ":tag3";
// 标签4
private static final String tag4 = ":tag4";
// 标签5
private static final String tag5 = ":tag5";
// 直接注入使用,用于发送消息到 broker 服务器
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 普通发送
*/
public void send(User user) {
rocketMQTemplate.convertAndSend(topic + tag1, user);
// rocketMQTemplate.send(topic + ":tag1", MessageBuilder.withPayload(user).build()); // 等价于上面一行
}
/**
* 同步方法
* syncSend() 方法会阻塞当前线程。 成功返回 SendResult 对象,包含了消息的发送状态、消息ID等信息;失败 syncSend 会抛出 MessagingException 异常。
*/
public SendResult sendMsg(String msgBody) {
SendResult sendResult = rocketMQTemplate.syncSend(topic+tag1, MessageBuilder.withPayload(msgBody).build());
log.info("【sendMsg】sendResult={}", JSON.toJSONString(sendResult));
return sendResult;
}
/**
* 异步方法
* 不会阻塞当前线程,asyncSend方法会立即返回。如果需要等待消息发送完成并处理发送结果,可以使用SendCallback回调接口。
*/
public void sendAsyncMsg(String msgBody) {
rocketMQTemplate.asyncSend(topic+tag1, MessageBuilder.withPayload(msgBody).build(), new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
// 处理消息发送成功逻辑
}
@Override
public void onException(Throwable throwable) {
// 处理消息发送异常逻辑
}
});
}
/**
* 单向消息(只负责发送消息,不等应答,不关心结果)
*/
public void sendOneWayMsg(String msgBody) {
rocketMQTemplate.sendOneWay(topic+tag1, MessageBuilder.withPayload(msgBody).build());
}
/**
* 单向顺序消息
*/
public void sendOneWayOrderlyMsg(String msgBody,String id){
rocketMQTemplate.sendOneWayOrderly(topic+tag2, MessageBuilder.withPayload(msgBody).build(),id);
}
/**
* 同步顺序消息
*/
public void sendSyncOneWayOrderlyMsg(String msgBody,String id){
SendResult sendResult = rocketMQTemplate.syncSendOrderly(topic+tag2, MessageBuilder.withPayload(msgBody).build(),id);
log.info("【sendSyncOneWayOrderlyMsg】sendResult={}", JSON.toJSONString(sendResult));
}
/**
* 异步顺序消息
*/
public void sendAsyncOneWayOrderlyMsg(String msgBody,String id){
rocketMQTemplate.asyncSendOrderly(topic+tag2, MessageBuilder.withPayload(msgBody).build(),id, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info(sendResult.toString());
}
@Override
public void onException(Throwable e) {
log.info(e.getMessage());
}
});
}
/**
* 延时消息
*/
public void syncSendDelayTimeSecondsMsg(String msgBody, int delayLevel) {
// 秒级
SendResult sendResult = rocketMQTemplate.syncSendDelayTimeSeconds(topic+tag3,msgBody, delayLevel);
// 毫秒级
// SendResult sendResult =rocketMQTemplate.syncSendDelayTimeMills(topic+tag2,msgBody, delayLevel);
log.info("【syncSendDelayTimeSecondsMsg】sendResult={}", JSON.toJSONString(sendResult));
}
/**
* 定时消息
*/
public void syncSendDeliverTimeMillsMsg(String msgBody) {
// 每天凌晨处理
long time = LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
SendResult sendResult = rocketMQTemplate.syncSendDeliverTimeMills(topic+tag3,msgBody, time);
log.info("【syncSendDelayTimeSecondsMsg】sendResult={}", JSON.toJSONString(sendResult));
}
/**
* 批量发送
*/
public void syncSendBatchMessage(List msgs) {
SendResult sendResult = rocketMQTemplate.syncSend(topic+tag4,msgs);
log.info("【syncSendBatchMessage】sendResult={}", JSON.toJSONString(sendResult));
}
/**
* 事务消息
*/
public void sendMessageInTransactionMsg(String msgBody) {
Message msgs = MessageBuilder.withPayload(JSON.toJSONString(msgBody))
.setHeader("KEYS", msgBody)
//设置事务ID
.setHeader(RocketMQHeaders.TRANSACTION_ID,"KEY_"+msgBody)
.build();
TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(topic+tag5,msgs,null);
log.info("【sendMessageInTransactionMsg】transactionSendResult={}", JSON.toJSONString(transactionSendResult));
}
}
@Slf4j
@Component
public class MQConsumerService {
/**
* topic要和生产者topic一致;consumerGroup 必须指定;selectorExpression 是tag,默认为“*”,不设置会监听所有消息。
*/
@Service
@RocketMQMessageListener(topic = "JUN_TEST_TOPIC", selectorExpression = "tag1", consumerGroup = "Con_Group_One")
public class ConsumerSend implements RocketMQListener {
// 监听到消息就会执行此方法
@Override
public void onMessage(User user) {
log.info("监听到消息:user={}", JSON.toJSONString(user));
}
}
// MessageExt:是一个消息接收通配符,不管发送的是String还是对象,都可接收。
@Service
@RocketMQMessageListener(topic = "JUN_TEST_TOPIC", selectorExpression = "tag2", consumerGroup = "Con_Group_Three")
public class Consumer implements RocketMQListener {
@Override
public void onMessage(MessageExt messageExt) {
byte[] body = messageExt.getBody();
String msg = new String(body);
log.info("监听到消息:msg={}", msg);
}
}
}
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 事物消息 Producer 事务监听器
*/
@Slf4j
@Component
@RocketMQTransactionListener()
public class TransactionListenerImpl implements RocketMQLocalTransactionListener {
private AtomicInteger transactionIndex = new AtomicInteger(0);
private ConcurrentHashMap localTrans = new ConcurrentHashMap();
/**
* 发送 prepare 消息成功此方法被回调,该方法用于执行本地事务
* @param msg 回传的消息,利用transactionId即可获取到该消息的唯一Id
* @param arg 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
* @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
//事务ID
String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
int value = transactionIndex.getAndIncrement();
int status = value % 3;
assert transId != null;
localTrans.put(transId, status);
if (status == 0) {
log.info("success");
//成功,提交事务
return RocketMQLocalTransactionState.COMMIT;
}
if (status == 1) {
log.info("failure");
//失败,回滚事务
return RocketMQLocalTransactionState.ROLLBACK;
}
log.info("unknown");
//中间状态
return RocketMQLocalTransactionState.UNKNOWN;
}
/**
* 检查事务状态
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
RocketMQLocalTransactionState retState = RocketMQLocalTransactionState.COMMIT;
Integer status = localTrans.get(transId);
if (null != status) {
switch (status) {
case 0:
retState = RocketMQLocalTransactionState.COMMIT;
break;
case 1:
retState = RocketMQLocalTransactionState.ROLLBACK;
break;
case 2:
retState = RocketMQLocalTransactionState.UNKNOWN;
break;
default: break;
}
}
log.info("msgTransactionId:{},TransactionState:{},status:{}",transId,retState,status);
return retState;
}
}
1、应用模块遇到要发送事务消息的场景时,先发送prepare消息给MQ。
2、prepare消息发送成功后,应用模块执行数据库事务(本地事务)。
3、根据数据库事务执行的结果,再返回Commit或Rollback给MQ。
4、如果是Commit,MQ把消息下发给Consumer端,如果是Rollback,直接删掉prepare消息。
5、第3步的执行结果如果没响应,或是超时的,启动定时任务回查事务状态(最多重试15次,超过了默认丢弃此消息),处理结果同第4步。
6、MQ消费的成功机制由MQ自己保证。
要根据业务场景合理的设计幂等技术方案
MessageListenerOrderly 通过分布式锁、本地锁保证同时只有一条线程去消费一个队列。
1.broker端的分布式锁
集群模式下一个消息队列(messageQueue)同一时刻只能被同一个消费组(consumerGroup)下的某一个消费者(consumerClient)消费,
consumerClient 向 Borker 申请锁 ,成功,则拉取消息;失败,则定时任务每隔20秒会重新尝试。
2.MessageQueue 的本地锁 synchronized
消费者在处理拉取消息时,可开启多线程处理,处理消息前要对MessageQueue加锁,保证同一时刻对于同一个队列只有一个线程去消费。
3.ProcessQueue 的本地锁 consumeLock(ReentrantLock)
防止在消费消息的过程中,该消息队列因负载均衡分配给其他客户端,导致的两个客户端重复消费。
1.生产者发送消息,由于网络故障或broker的master节点宕机,导致消息丢失。
2.消息已经发送到RocketMQ,消息暂存内存中,服务宕机,导致消息丢失。
3.消息已经发送到RocketMQ,消费者未消费完就返回ack,此时消费者宕机,导致消息丢失。
1.Producer 生产阶段
Broker 收到消息后,返回确认响应信息给 Producer。
生产者接收到返回的确认响应,代表消息在生产阶段未丢失。
2.Broker 存储阶段
Broker 默认异步刷盘(500ms)(消息持久化),修改 Broker 配置为同步刷盘(10ms刷盘一次),消息存储磁盘成功,才会返回响应。
## 默认情况为 ASYNC_FLUSH
flushDiskType = SYNC_FLUSH
3.消费阶段
消费者执行成功后,将返回 ConsumeConcurrentlyStatus.CONSUME_SUCCESS 状态给 Broker。Broker 未收到消费确认响应,消费者会再次拉取到该条消息,进行重试。
//注册消息监听器处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context){
//开启子线程异步处理消息
new Thread() {
public void run() {
//对消息进行处理
}
}.start();
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
消费时,如果抛出了异常,会重新再次投递给该消费者。
重试次数达到默认的16次后(可通过配置文件修改)如果对应的消息还没被成功消费的话,该消息会投递到DLQ死信队列。
可在控制台Topic列表中看到"DLQ"相关的Topic,默认命名是:%RETRY%消费组名称(重试Topic)%DLQ%消费组名称(死信Topic)。
死信队列也可以被订阅和消费,有效期与正常消息相同,均为 3 天,3 天后会被自动删除。
1.每个 NameServer 都保存着 Broker 集群的所有 Broker 信息,一台NameServer服务器宕机了,其它NameServer可用。
2.每个Broker节点都是主从架构,所以就算主节点宕掉了,从节点可提供服务。
3.RocketMQ以4.5版本后,RocketMQ引入了 Dleger机制 ,采用Raft协议进行主从节点的选举,实现故障自动转移。
4.RocketMQ每个Broker节点只保存整体数据的一部分,当数据量越来越大时,通过数据分散集群的模式实现水平扩展。RabbitMQ中的每个节点保存着全量数据,没法水平扩展。