RocketMq踩坑

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可以设置最多重试次数,最后一次重试失败会间隔最后一次重试间隔时间后发送到死信队列

第几次重试每次重试间隔时间
110 秒
230秒
31 分钟
42 分钟
53 分钟
64 分钟
75 分钟
86 分钟
97 分钟
108 分钟
119 分钟
1210 分钟
1320 分钟
1430 分钟
151 小时
162 小时

死信队列(%DLQ%+consumerGroup)

死信队列默认权限是2(可写) 需要改为6(可读可写)

权限码权限
2W
4R
6WR

如果需要处理死信队列的消息,首先需要设置死信队列的读写权限,默认是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进行代码判断
consumer.subscribe()方法源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值