RabbitMq 的概念及应用(插件、应用、解决方案,面试题篇)
一、RabbitMQ保证消息可靠性(避免消息丢失)
1、保证消息一定送达到Exchange
confirm应答机制
@RestController
public class Publisher {
@Autowired
ClientUtil util;
private String QUEUE_NAME = "workQueue";
@GetMapping("test")
public String sendMsg() throws IOException, TimeoutException {
Connection connect = util.connect();
Channel channel = connect.createChannel();
channel.queueDeclare(QUEUE_NAME , true ,false ,false ,null);
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.contentType("远程会诊")
.contentEncoding("UTF_8")
.deliveryMode(2)
.build();
System.out.println(properties);
//开启confirm机制
channel.confirmSelect();
//新增一个监听器,启用异步的方式,加强性能
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("消息发送成功");
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("消息发送失败。。。。 重新投递,或者落入DB记录,写其他补偿操作");
}
});
channel.basicPublish("" , QUEUE_NAME ,properties , "hello world".getBytes());
System.out.println("消息发送成功");
channel.close();
connect.close();
return "ok";
}
}
2、保证消息可以路由到queue
return机制
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("发送失败走这里。。。。补偿措施");
}
});
// 需要在properties前,设置true,开启return机制
channel.basicPublish("" , QUEUE_NAME ,true,properties , "hello world".getBytes());
3、保证queue可以持久化消息
开启消息的持久化策略(前提是将队列和交换机都持久化)
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.contentType("远程会诊")
.contentEncoding("UTF_8")
.deliveryMode(2) //设置消息持久化
.build();
4、保证消费者可以正常消费消息
消费者开启手动ACK应答机制
channel.bacisAck();
二、springboot 整合RabbitMQ保证消息可靠性(避免消息丢失)
配置文件如下:
spring:
application:
name: rabbit
cloud:
nacos:
discovery:
server-addr: 127.0.0.1
port: 8848
rabbitmq:
username: guest
password: guest
host: 127.0.0.1
port: 5672
virtualHost: /
listener:
simple:
acknowledge-mode: manual #开启手动应答
publisher-confirm-type: CORRELATED #开启confirm模式
publisher-returns: true #开启return模式
示例代码:
@RestController
public class BootPublisher {
@Autowired
RabbitTemplate rabbitTemplate;
public static final String EXCHANGE = "fanout";
public static final String QUEUE = "my-queue";
public static final String ROUTING_KEY = "*.message.*";
@GetMapping("boot")
public void push(){
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
if (b){
System.out.println("成功");
}else {
System.out.println("失败了。。。。");
}
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("失败了。。。。我要补偿一下");
}
});
rabbitTemplate.convertAndSend(EXCHANGE, "hello.message.send", "data", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties properties = message.getMessageProperties();
properties.setAppId("123");
properties.setContentType("自定义");
properties.setDeliveryMode(MessageDeliveryMode.fromInt(2)); // 设置消息持久化
return message;
}
});
System.out.println("消息发送成功");
}
}
三、Springboot 实现延迟队列
@Configuration
public class DeclearMQ {
public String delayExchange = "delay-exchange";
public String delayQueue = "delay-queue";
public String delayRoutingKey = "*.delay";
@Bean
public Exchange delayExchange(){
Map<String, Object> map = new HashMap<>();
map.put("x-delayed-type" , "topic");
CustomExchange customExchange = new CustomExchange(delayExchange ,"x-delayed-message" ,true,false , map);
return customExchange;
}
@Bean
public Queue delayQueue(){
return QueueBuilder.durable(delayQueue).build();
}
@Bean
public Binding delayBinding(){
return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(delayRoutingKey).noargs();
}
}
@Slf4j
@RestController
public class DelayPublisher {
@Autowired
RabbitTemplate rabbitTemplate;
public String delayExchange = "delay-exchange";
public final String delayQueue = "delay-queue";
@GetMapping("delay")
public String delayMsg() {
log.info("开始发送延迟消息。。。");
rabbitTemplate.convertAndSend(delayExchange,"want.delay", (Object) "我是发送过来的消息。。。。。。。2222", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
log.info("加强处理器");
message.getMessageProperties().setDelay(1000);
return message;
}
});
log.info("消息发送完毕。。。");
return "ok";
}
@RabbitListener(queues = {delayQueue})
public void listen(Channel channel , Message message) throws IOException {
System.out.println("监听到消息。。。 开始进行消费");
String contentType = message.getMessageProperties().getContentType();
System.out.println(contentType);
System.out.println(message.getMessageProperties().getAppId());
// 需要在yml文件中,配置手动ack 才可以在接收参数中 加入Message
// channel.basicReject(message.getMessageProperties().getDeliveryTag() ,false);
channel.basicAck(message.getMessageProperties().getDeliveryTag() ,false);
System.out.println("发送的消息体为 : " + new String(message.getBody()));
System.out.println("消息消费完毕。。。。。。。");
}
}
五、常见问题:
常见面试题
怎么防止消息丢失?
1、消息持久化
2、publisher-confirms(发送方确认模式)
一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。
如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(not acknowledged,未确认)消息。
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息
使用Spring AMQP时,我们可以人为配置重发时长。
3、手动执行ack,消息确认机制
自动ACK:消息一旦被接收,消费者自动发送ACK
– 如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便
手动ACK:消息接收后,不会发送ACK,需要手动调用
– 如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。
4、消息补偿机制
消息要写入DB日志,发送日志,接受日志,两者的状态必须记录。然后根据DB日志记录check 消息发送消费是否成功,不成功,进行消息补偿措施,重新发送消息处理。
如何保证mq的有序性?
如果存在多个消费者,那么就让每个消费者对应一个queue,然后把要发送 的数据全都放到一个queue,这样就能保证所有的数据只到达一个消费者从而保证每个数据到达数据库都是顺序的
如何处理消息积压?
紧急扩容。
1、先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉。
2、新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量
3、然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
4、接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据
5、这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
6、等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息
如何处理消息丢失?
如果消息真的丢失了。我们可以在用户休息的时间段,进行手动查询,重新讲任务传递到mq,进行消费。