1.消息如何保证100%投递
同时做到下面四点可以保证消息可靠性的。
完善的消息进行补偿机制:当消息产生特殊情况进行处理,如:消息没有成功发出或者确认应答超时。
解决方案:
(1)消息落库:将消息的投递、发送中、和已经收状态进行打标,将消息目前处于哪一种状态存储在数据库中。对没有投递成功的消息进行重试。
(2)消息延迟投递:相比第一种方案,减少了数据库的访问次数
2. 幂等性
比如一次、两次或者多次insert相同一行数据操作,对于DB结果都是相同的,这就是保证了幂等性。
(1)利用数据库去重:每个消息的处理操作在MQ的下游处理的时候,我们在MQ上游就已经设置好唯一的处理逻辑。(好比:我们以用户只能下一个订单,我们用id和商品id是唯一的,就保证了只可以下一次订单)
它的缺点是:数据库可能在并发下产生瓶颈。
(2)redis的setnx操作来设置订单唯一id,以后第二条订单过来就在redis这一层被过滤掉了。不会进行相关操作。
下面redis的需要问题:
3. confirm和return
confirm的机制:
confirm的过程:
return是去监听一些不可达的消息:如因为交换机不存在而导致的消息不可达。return发现有些消息不可达以后就可以在实现类中进行补偿。
代码:
consumer:
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Date;
@Component
@RabbitListener(queues = "hello")
public class Consumer {
@RabbitHandler
public void process(String hello,Channel channel, Message message) throws IOException {
System.out.println("HelloReceiver收到 : " + hello +"收到时间"+new Date());
try {
//告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会在发
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
System.out.println("receiver success");
} catch (IOException e) {
e.printStackTrace();
//丢弃这条消息
//channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
System.out.println("receiver fail");
}
}
}
produce:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Date;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Produce implements RabbitTemplate.ReturnCallback, RabbitTemplate.ConfirmCallback {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private RabbitTemplate rabbitTemplate;
// 发送消息
@Test
public void send(){
String context = "你好现在是 " + new Date() +"";
System.out.println("HelloSender发送内容 : " + context);
// this.rabbitTemplate.setConfirmCallback(this);
this.rabbitTemplate.setReturnCallback(this);
// 消息的confirm
this.rabbitTemplate.setConfirmCallback(this);
this.rabbitTemplate.convertAndSend("hello", context);
}
// 消息监听
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("消息:"+message.getMessageProperties().getCorrelationId() +
"错误码:"+i +
"失败原因:"+s+
"交换器:"+s1+
"路由key:"+s2);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("消息确认成功cause:"+cause);
} else {
//处理丢失的消息
System.out.println("消息确认失败:"+correlationData.getId()+"#cause"+cause);
}
}
}
配置queue
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
@Bean
public Queue QueueA() {
return new Queue("hello");
}
}
测试confirm:
测试return:
4. 消费端限流
Qos限流:
(1)配置手动ACK:
# 开启手动ACK
spring.rabbitmq.listener.direct.acknowledge-mode=manual
spring.rabbitmq.listener.simple.acknowledge-mode=manual
(2)qos参数说明:代码仍然使用上面3中的代码,只是增加了一行qos的代码
5. 重回队列
定义:对没有处理的消息。重新投入broker(一般设置为false)
重回队列会回到队列的尾部。
ps:还是原来的代码,只是在接收数据失败的时候加了noack,第三个参数表示是否重回队列。
6. TTL
定义:Time To Live 表示消息的过期时间,可以是某个队列的过期时间(队列上的数据到达了时间就会过期),也可以是某个消息的过期时间。
为某个队列设置过期时间:
为某一条消息设置过期时间
rabbitTemplate.convertAndSend("direct.pay.exchange", "OrderPay", user,
message -> {
// 设置5秒过期
message.getMessageProperties().setExpiration("15000");
return message;
},
correlationData);
7. 死信队列
当某个消息没有消费者去消费就会变成死信队列。
死信队列中的消息都是无法被消费者接收到的消息,最后就放入到了死信队列中。
手动设置死信队列
死信队列会接受带有对应参数的队列。