Rocketmq 应用重启后出现重复消费的情况,查询相关资料后发现当客户端版本和服务端版本不同时,会出现重复消费的情况,但实际测试后发现,在版本有差异的情况下(客户端3.5.8版本,服务端4.2.0版本),采用集群模式消费,未出现重复消费的情况,采用广播模式时,会出现重复消费的情况。
Rocketmq 消费者分 集群模式 和 广播模式 两种方式,在消费者实例初试化时进行设置
// 集群模式
//consumer.setMessageModel(MessageModel.CLUSTERING);
//广播模式
consumer.setMessageModel(MessageModel.BROADCASTING);
设置集群模式时,在版本有差异和无差异的情况下,均未出现重复消费的情况(此情况与网上一些资料描述的情况不同)
设置广播模式时,在版本有差异和无差异的情况下,均出现了重复消费的情况。
补充设置消费者实例名称:
//设置消费实例名,在广播消费模式下有效,若未设置,则重启会重复消费
consumer.setInstanceName("instanceName");
//设置此种格式的实例名无效
// consumer.setInstanceName("10.0.75.1192.168.50.36:19876;192.168.50.37:29876");
经测试,在版本无差异的情况下,没有出现重复消费的情况下,在版本有差异的情况下则依旧会重复消费。
有趣的是,设置实例名为带 ip类型的格式时,也会出现重复消费的情况,具体原因,还需详细分析
如下是完整代码:
import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;
import com.alibaba.rocketmq.common.message.MessageExt;
import com.alibaba.rocketmq.common.protocol.heartbeat.MessageModel;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
/**
*
*/
@Component("addressIndexDataMQConsumer")
public class AddressIndexDataMQConsumer implements MessageListenerConcurrently {
public static final Logger log = LoggerFactory.getLogger(AddressIndexDataMQConsumer.class);
@Value("${spring.rocketmq.namesrvAddr}")
private String namesrvAddr;
private Gson gson = new Gson();
private final DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("addressIndexDataMQConsumer");
/**
* 初始化
*
* @throws
*/
@PostConstruct
public void start() {
try {
log.info("MQ:启动消费者");
consumer.setNamesrvAddr(namesrvAddr);
// 从消息队列头开始消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 集群模式
//consumer.setMessageModel(MessageModel.CLUSTERING);
//广播模式
consumer.setMessageModel(MessageModel.BROADCASTING);
//设置消费实例名,在广播消费模式下有效,若未设置,则重启会重复消费
consumer.setInstanceName("instanceName");
//设置此种格式的实例名无效
// consumer.setInstanceName("10.0.75.1192.168.50.36:19876;192.168.50.37:29876");
//VipChannel阿里内部使用版本才用,开源版本没有,默认为true,占用10909端口,此时虚拟机需要开放10909端口,否则会报 :connect to <:10909> failed异常,可以直接设置为false
consumer.setVipChannelEnabled(false);
// 订阅主题
consumer.subscribe("AddressIndexData", "AddressIndexData");
// 注册消息监听器
consumer.registerMessageListener(this);
// 启动消费端
consumer.start();
} catch (MQClientException e) {
log.error("MQ:启动消费者失败:{}-{}", e.getResponseCode(), e.getErrorMessage());
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* 消费消息
* @param msgs
* @param context
* @return
*/
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
MessageExt msg = msgs.get(0);
log.error("====addressindexdata==== " + new String(msg.getBody()));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
@PreDestroy
public void stop() {
if (consumer != null) {
consumer.shutdown();
log.error("MQ:关闭消费者");
}
}
/**
* 获取消费者实例名,采用当前ip与mq服务ip组合的方式 **实测发现,此格式的实例名不生效
* @param namesrvAddr
* @return
*/
private static String getInstanceName(String namesrvAddr) {
return getHostAddress() + namesrvAddr;
}
private static String getHostAddress(){
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return "";
}
}