为什么解决重复消费的问题?
- 生成者在发送消息时可能因为网络或其他原因导致消息发送延时,补偿机制会重新发送相同的消息
- 消费者消费异常
解决思路
- 生产者发送消息时携带一个唯一的id
- 消费者每次消费前先判断一下在redis中是否在id,不存在就消费,消费完之后就把id存储到redis中
代码实现
- 消费者发送消息携带id
@GetMapping("/directredis")
public String sendMsgRedis(@RequestParam Map<String,Object> msg){
try{
// 模拟相同的key,实际业务使用业务id
String uuid = "keykeykey";
Message message = MessageBuilder.withBody(JSON.toJSONString(msg).getBytes()).setMessageId(uuid).build();
rabbitTemplate.convertAndSend("EmailExchange","emailRouting",message);
return "ok";
}catch (Exception e){
e.printStackTrace();
return "连接异常....";
}
}
- 消费者的接收(redisCacheUtil在文章最后给出)
@RabbitListener(queues = "emailQueue")
public void listenerMqAtRedis(Message message){
String messageId = message.getMessageProperties().getMessageId();
// 判断messageId在Redis中是否存在
if(messageId != null && !redisCacheUtil.exists(messageId)){
// 没有重复消息
System.out.println(">>>>>>>>>>>>开始发送邮件:"+message.getBody().toString());
redisCacheUtil.setKey(messageId,true);
}else{
System.out.println(">>>>>>>>>>>>已经消费..........");
}
}
测试
先准备几条id相同的消息
启动消费者进行接收,可以看到消息只消费了一次。
附
redisCacheUtil
package com.wcong.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
/**
* @author wcong
* @version 1.0
* @date 2020-07-31 8:11
*/
@Component
public class RedisCacheUtil {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 写入缓存
* @param key
* @param val
* @return
*/
public boolean setKey(final String key,Object val){
boolean result = false;
try {
redisTemplate.opsForValue().set(key,val);
result = true;
}catch (Exception e){
e.printStackTrace();
}
return result;
}
/**
* 写入缓存,可以设置过期时间
* @param key
* @param val
* @return
*/
public boolean setKey(final String key,Object val,Long time){
boolean result = false;
try {
redisTemplate.opsForValue().set(key,val,time);
result = true;
}catch (Exception e){
e.printStackTrace();
}
return result;
}
/**
* 判断key是否存在
* @param key
* @return
*/
public boolean exists(final String key){
try{
return redisTemplate.hasKey(key);
}catch (Exception e){
e.printStackTrace();
}
return false;
}
/**
* 读取缓存
* @param key
* @return
*/
public Object getVal(final String key){
return redisTemplate.opsForValue().get(key);
}
/**
* 删除缓存
* @param key
*/
public void remove(final String key){
redisTemplate.delete(key);
}
/**
* 批量删除缓存
* @param keys
*/
public void remove(String... keys){
for (String key : keys) {
redisTemplate.delete(key);
}
}
}
- 设置redis的序列化方式
package com.wcong.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author wcong
* @version 1.0
* @date 2020-07-31 8:18
*/
@Configuration
public class RedisConfig {
/**
* 设置redis的序列化方式
* @return
*/
@Bean
@Primary
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}