场景:推送门店昨日销售业绩给各个门店店长,数据提供方来自大数据,正常情况下是9点出数据,但是处理时可能会延迟有可能9点2分,9点5分甚至是9点30分还未出数据,后端程序每天9点定时任务推送数据,如果推送时从大数据接口获取不到数据,那么整天的数据就需要人为干涉手动推送,十分的不人性化。
解决方案:通过MQ的死信队列+消息过期时间,实现无数据时5分钟后重新推送,超过N次后将为推送的门店入库,发送消息(邮件)告知管理员。
流程图
代码
通过SpringBoot+RabbitMQ实现
1 申明交换机,消息队列,绑定规则
@Configuration
public class RabbitConfig {
/**
* 交换机
*/
@Bean
public DirectExchange directExchange(){
return new DirectExchange("message-exchange",true,false);
}
/**
* 死信队列设置过期时间,过期消息重新发送给消息队列
* @return
*/
@Bean
public Queue deadQueue(){
QueueBuilder queue = QueueBuilder.durable("deadQueue");
queue.deadLetterExchange("message-exchange");
queue.deadLetterRoutingKey("message");
queue.ttl((int)Duration.ofMinutes(5).toMillis());
return queue.build();
}
/**
* 消息队列绑定死信交换机和队列
* */
@Bean
public Queue message(){
QueueBuilder queue = QueueBuilder.durable("message");
queue.deadLetterExchange("message-exchange");
queue.deadLetterRoutingKey("deadQueue");
return queue.build();
}
/**
* 绑定规则
*/
@Bean
public Binding binding(){
return BindingBuilder.bind( deadQueue() ).to(directExchange()).with("deadQueue");
}
/**
* 绑定规则
*/
@Bean
public Binding binding2(){
return BindingBuilder.bind( message() ).to(directExchange()).with("message");
}
}
消费者代码
@Component
public class MessageListener {
private static final int retryCnt = 5;
private Logger logger = LoggerFactory.getLogger( MessageListener.class );
//模拟redis缓存
private ConcurrentHashMap<String, AtomicInteger> cache = new ConcurrentHashMap(8);
@RabbitListener(queues = "message")
public void listener(Message message, Channel channel, @Headers Map<String,Object> map, @Payload Integer body) throws IOException {
//如果是奇数直接消费
if( body%2 != 0 ){
channel.basicAck( message.getMessageProperties().getDeliveryTag(),false );
logger.info("成功消费消息");
return;
}
//这里主要用户删掉消费5次的消息
if( get(body) > retryCnt ){
channel.basicAck( message.getMessageProperties().getDeliveryTag(),false );
logger.error("删除消息"+body);
return;
}
logger.error("当前消息{},消费第 {} 次,无数据返回,3秒钟后重试",body, get(body));
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
incr(body);
if( get(body) >retryCnt ){
logger.error("消息超过最大消费次数{},入库并发送邮件通知-->{}",retryCnt,body);
}
}
public int incr(Integer num){
String key = StrUtil.toString( num );
if( cache.get( key ) == null ){
cache.put( key ,new AtomicInteger(1) );
}
return cache.get( key ).incrementAndGet();
}
public Integer get(Integer num){
String key = StrUtil.toString( num );
if( cache.get( key ) == null ){
cache.put( key ,new AtomicInteger(1) );
}
return cache.get( key ).get();
}
}