背景
在前端时间开发的项目中,在消息通知模块业务上有钉钉、微信、短信、邮箱、站内消息通知,一个接口下可能会同时调用这5个消息通知方式,如果是同步的方式来进行处理,接口的效应速率会大大降低,而且5个消息的发送不统一处理代码的耦合性太高,根据这两点我们决定采用消息中间件Rabbitmq来进行处理消息的发送。
好处:
1、消息定制统一的消息模板发送消息时将消息封装到消息模板统一处理是代码更加便于维护和清晰
2、发送消息时只需要将消息投递到消息队列,将同步变异步使得接口的响应速率更快
坏处:
使用Rabbitmq可能会出现消息的丢失、消息重复消费等问题需要解决
方案流程图
RabbitMQConfig
消息队列、交换机、绑定关系的创建
@Configuration
public class RabbitMQConfig {
public static final String NOTICE_QUEUE_NAME = "notice.queue";
public static final String NOTICE_EXCHANGE_NAME = "notice.exchange";
public static final String NOTICE_ROUTING_KEY_NAME = "notice.routing.key";
@Bean
public Queue noticeQueue() {
return new Queue(NOTICE_QUEUE_NAME, true);
}
@Bean
public DirectExchange noticeExchange() {
return new DirectExchange(NOTICE_EXCHANGE_NAME, true, false);
}
@Bean
public Binding noticeBinding() {
return BindingBuilder.bind(noticeQueue()).to(noticeExchange()).with(NOTICE_ROUTING_KEY_NAME);
}
}
RabbitMQTemplateConfig
消息发送到交换机监听及消息发送失败的处理
@Configuration
@Slf4j
public class RabbitMQTemplateConfig {
@Autowired
private IMessageLogService messageLogService;
@Bean
public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter());
//设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("消息成功发送到Exchange");
String msgId = correlationData.getId();
MessageLog messageLog = new MessageLog();
messageLog.setId(Integer.valueOf(msgId));
messageLog.setStatus(MessageLog.MsgLogStatus.DELIVER_SUCCESS);
messageLogService.updateById(messageLog);
} else {
log.error("消息发送到Exchange失败, {}, cause: {}", correlationData, cause);
}
});
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
log.info("消息从Exchange路由到Queue失败: exchange: {}, route: {}, replyCode: {}, replyText: {}, message: {}", exchange, routingKey, replyCode, replyText, message);
});
return rabbitTemplate;
}
@Bean
public MessageConverter jackson2JsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
RabbitMsgProducer
消息发送
@Slf4j
@Component
public class RabbitMsgProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public boolean sendMessage(MessageParam messageParam) {
if (Objects.nonNull(messageParam)) {
CorrelationData correlationData = new CorrelationData(messageParam.getMsgId());
// 发送消息
rabbitTemplate.convertAndSend(RabbitMQConfig.NOTICE_EXCHANGE_NAME, RabbitMQConfig.NOTICE_ROUTING_KEY_NAME,
MessageHelper.objToMsg(messageParam), correlationData);
}
return true;
}
}
RabbitMsgConsumer
消息消费
@Component
@Slf4j
public class RabbitMsgConsumer {
@RabbitListener(queues = {RabbitMQConfig.NOTICE_QUEUE_NAME})
public void generalConsume(Message message, Channel channel) throws IOException, InterruptedException {
//1、根据message MessageHelper.msgToObj(message, MessageParam.class);获取消息信息
//2、获取消息ID去消息日志里判断消息是否被消费
//3、消息消费
//4、消费成功手动ACK,消费失败Nack,对应修改日志状态
}
}
主要解决的问题
1、利用Rabbitmq解决了代码的耦合,接口响应速率较慢的问题
2、利用消息日志记录消息状态 解决消息的重复消费及消息丢失的问题
3、利用Rabbitmq的confirm机制(消息发送到交换机可能出现消息丢失)及手动ACK解决消息丢失问题