provider
默认情况下不需要设置instanceName,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
如果同一个jvm中,不同的producer需要往不同的rocketmq集群发送消息,需要设置不同的instanceName
原因如下:如果不设置instanceName,那么会使用ip@pid作为producer唯一标识,那么会导致多个producer内部只有一个MQClientInstance(与mq交互)实例,从而导致只往一个集群发消息。
consumer
默认情况下不需要设置instanceName,rocketmq会使用ip@pid作为instanceName(pid代表jvm名字)
如果设置instanceName,rocketmq会使用ip@instanceName作为consumer的唯一标示,此时需要注意instanceName需要不同。
如果consumer设置了instanceName,如果是多台主机上,每一台主机一个JVM进程,这种情况还好。如果是一台主机上,多个JVM进程,且每个consumer的instanceName还相同的情况下,就会出现只有一台consumer消费全部消息的情况。
因为 rocketmq会使用ip@instanceName作为consumer的唯一标示,这样多个JVM进程中的consumer只有一种cid
这样在rebalance过程中,会导致只有一个consumer消费所有
messagequeue
MessageListenerConcurrently 并发消费 如果单条消息消费失败,会进入时间间隔,16次消费失败后进入死信队列
MessageListenerOrderly 顺序消费 如果单条消息消费失败 不会进入时间间隔,会一直消费,两个消费方式返回消费状态不同
正常队列消费失败进入到重试队列(%RETRY%+consumerGroup) 默认16次消费失败后进入死信队列(%DLQ%+consumerGroup)
consumer可以设置最多重试次数,最后一次重试失败会间隔最后一次重试间隔时间后发送到死信队列
第几次重试 | 每次重试间隔时间 |
---|---|
1 | 10 秒 |
2 | 30秒 |
3 | 1 分钟 |
4 | 2 分钟 |
5 | 3 分钟 |
6 | 4 分钟 |
7 | 5 分钟 |
8 | 6 分钟 |
9 | 7 分钟 |
10 | 8 分钟 |
11 | 9 分钟 |
12 | 10 分钟 |
13 | 20 分钟 |
14 | 30 分钟 |
15 | 1 小时 |
16 | 2 小时 |
死信队列(%DLQ%+consumerGroup)
死信队列默认权限是2(可写) 需要改为6(可读可写)
权限码 | 权限 |
---|---|
2 | W |
4 | R |
6 | WR |
如果需要处理死信队列的消息,首先需要设置死信队列的读写权限,默认是2,然后再用consume监听死信队列名称
1、可以通过mq配置文件改
2、可以通过控制台修改topic的perm
provider使用方式
@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "rocketmq.producer")
public class ExampleProducerMqConfig {
private String groupName;
private String namesrvAddr;
/** 4M */
private int maxMessageSize;
private int sendMsgTimeout;
@Bean("testProducer")
public DefaultMQProducer getRocketmqProducer(){
Assert.assertNotNull(groupName, ErrorCodeEnum.PARAMS_ERROR, "groupName");
Assert.assertNotNull(namesrvAddr, ErrorCodeEnum.PARAMS_ERROR, "namesrvAddr");
DefaultMQProducer producer = new DefaultMQProducer(this.groupName);
producer.setNamesrvAddr(this.namesrvAddr);
producer.setMaxMessageSize(this.maxMessageSize);
producer.setSendMsgTimeout(this.sendMsgTimeout);
try {
producer.start();
log.info("bill producer is start ! groupName:[{}],namesrvAddr:[{}]" , this.groupName, this.namesrvAddr);
} catch (MQClientException e) {
log.error("bill producer is error {}", e);
throw new BaseException(ErrorCodeEnum.SERVICE_MQ_PRODUCER_ERROR);
}
return producer;
}
}
consumer使用方式
消费者配置:
@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "rocketmq.consumer")
public class ExampleConsumerMqConfig {
private String namesrvAddr;
private String groupName;
private String topic;
private int consumeThreadMin;
private int consumeThreadMax;
@Autowired
private ExampleMessageHandler msgConsumeComponent;
@Bean("factoryConsumer")
public DefaultMQPushConsumer getRocketmqConsumer(){
Assert.assertNotNull(groupName, ErrorCodeEnum.PARAMS_ERROR, "groupName");
Assert.assertNotNull(namesrvAddr, ErrorCodeEnum.PARAMS_ERROR, "namesrvAddr");
Assert.assertNotNull(topic, ErrorCodeEnum.PARAMS_ERROR, "topic");
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
consumer.setNamesrvAddr(namesrvAddr);
consumer.setConsumeThreadMin(consumeThreadMin);
consumer.setConsumeThreadMax(consumeThreadMax);
ExampleMessageListener messageListener = new ExampleMessageListener();
messageListener.setMsgConsumeComponent(msgConsumeComponent);
consumer.registerMessageListener(messageListener);
try {
consumer.subscribe(topic, "");
consumer.start();
log.info("test consumer is start !!! groupName:{},topic:{},namesrvAddr:{}",groupName,topic,namesrvAddr);
} catch (MQClientException e){
throw new BaseException(ErrorCodeEnum.SERVICE_MQ_CONSUMER_ERROR);
}
return consumer;
}
}
监听配置:
@Slf4j
public class ExampleMessageListener implements MessageListenerConcurrently {
private ExampleMessageHandler msgConsumeComponent;
public void setMsgConsumeComponent(ExampleMessageHandler msgConsumeComponent) {
this.msgConsumeComponent = msgConsumeComponent;
}
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt messageExt : msgs) {
boolean result = msgConsumeComponent.handleMessage(messageExt);
if (!result){
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
消费处理逻辑:
@Slf4j
@Component
@ConfigurationProperties(prefix = "rocketmq.producer")
public class ExampleMessageHandler {
public boolean handleMessage(MessageExt messageExt) {
try {
} catch (Exception e) {
}
return true;
}
}
事务消息监听:
@Slf4j
public class ExampleTransactionListener implements TransactionListener {
private ConcurrentHashMap<String, Integer> countHashMap = new ConcurrentHashMap<>();
private final static int MAX_COUNT = 5;
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
//
String orderNo = msg.getUserProperty("orderNo"); // 从消息中获取业务唯一ID。
// 将bizUniNo入库,表名:t_message_transaction,表结构 bizUniNo(主键),业务类型。
log.info("orderNo is:", orderNo);
return LocalTransactionState.UNKNOW;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
Integer status = 0;
// 从数据库查查询t_message_transaction表,如果该表中存在记录,则提交,
String orderNo = msg.getUserProperty("orderNo"); // 从消息中获取业务唯一ID。
// 然后t_message_transaction 表,是否存在bizUniNo,如果存在,则返回COMMIT_MESSAGE,
// 不存在,则记录查询次数,未超过次数,返回UNKNOW,超过次数,返回ROLLBACK_MESSAGE
if (query(orderNo) > 0) {
return LocalTransactionState.COMMIT_MESSAGE;
}
return rollBackOrUnown(orderNo);
}
public int query(String orderNo) {
return 1; //select count(1) from t_message_transaction a where a.biz_uni_no=#{bizUniNo}
}
public LocalTransactionState rollBackOrUnown(String orderNo) {
Integer num = countHashMap.get(orderNo);
if (num != null && ++num > MAX_COUNT) {
countHashMap.remove(orderNo);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
if (num == null) {
num = new Integer(1);
}
countHashMap.put(orderNo, num);
return LocalTransactionState.UNKNOW;
}
}
consumer.subscribe()方法最终是放到ConcurrentMap<String, SubscriptionData>中,所以是可以通过这个方法监听多个topic
此处的第二个参数设置为null或者空字符都会转化成*,但是设置了subString(tag),似乎不会进行过滤,(topic+tag)组成的消息不回过滤,都会进入到监听中,如果需要过滤主要需要通过tag进行代码判断