- 通用解决方案是在消息实体中添加全局唯一的id,例如 msg_id(消息ID),在代码中保证消息的幂等性,
- 消费者在收到消息之后,根据 msg_id 从缓存或者数据库中查询是否存在已有消息;
- 如果不存在已有消息,那么消费之后,将 msg_id 对应的消息实体或者序列化对象写入缓存或者数据库;
- 如果存在已有消息,说明这条消息已被消费过,丢弃消息并且打一条告警日志。
- 并且可以根据重复消费的容忍程度以及性能要求选择使用缓存还是使用数据库,
- 如果对判断的速度要求高,可以使用 Redis 作为缓存;
- 如果对判断的稳定性和鲁棒性要求高,使用数据库存储消息实体,同时将 msg_id 作为数据库表的唯一键,插入重复记录一定会抛出异常,避免数据库因为并发问题产生脏数据,保证了消息消费的不可重复性。
- 这里使用stringRedisTemplate.opsForHash()保证消息不被重复消费
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(value = "queue.name.user", declare = "true"), // 创建info队列,declare默认队列持久化
key = {"route.user"}, // 路由key
exchange = @Exchange(type = "direct", name = "exchange-directs-user")
)})
public void receive12211(Message message, Student student, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException {
System.out.println("message:"+message);
MessageProperties messageProperties = message.getMessageProperties();
long tag = deliveryTag;
Map<String, Object> headers = messageProperties.getHeaders();
String messageId = headers.get("spring_returned_message_correlation").toString();
System.out.println("message_id: "+messageId);
if(stringRedisTemplate.opsForHash().entries("rabbitmq_log").containsKey(messageId)){
//redis中包含该key,说明已经被消费过了
System.out.println(messageId+"消息已经被消费过一次");
//确认消息已被消费
channel.basicAck(tag,false);
return;
}
try {
System.out.println("路由模式message1 = " + student);
stringRedisTemplate.opsForHash().put("rabbitmq_log",messageId,"v");
channel.basicAck(tag,false);
System.out.println(messageId+"消息消费成");
}catch (Exception e){
e.printStackTrace();
System.out.println("消息消费异常");
channel.basicNack(tag,false,true);
}
}
server:
port: 8071
spring:
rabbitmq:
host: localhost
port: 5672
username: admin
password: 123456
virtual-host: /ems
template:
retry: #重试,消息发送失败会重试
enabled: true # 开启重试
initial-interval: 10000ms #第一次十秒重试
max-interval: 80000ms #最后一次是八秒重试
multiplier: 2 #重试翻倍率
publisher-confirms: true #发送者开启 confirm 确认机制
publisher-returns: true # 发送者开启 return 确认机制
listener:
simple:
acknowledge-mode: manual #开启手动ack
datasource:
url: jdbc:mysql://localhost:3306/one?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&AllowPublicKeyRetrieval=True
username: root
password: root
redis:
host: localhost
port: 6379
password:
mybatis-plus:
mapper-locations: classpath*:com/test/mapper/xml/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
type-aliases-package: com.test.domain