RabbitMQ核心概念
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理和队列服务器。RabbitMQ服务器是用Erlang语言编写的。
- Server:也称Broker,接受客户端的连接,实现AMQP实体服务
- Connection:连接与Broker的网络连接
- Channel:网络信道,几乎所有的操作都在Channel上进行,一个客户端可以建立多个Channel,每个Channel代表一个会话任务。
- Message:消息,服务器和应用程序之间传送的数据,由Properties和Body组成。Properties可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body则是消息内容体。
- Virtual host:虚拟地址,用于逻辑隔离,最上层的消息路由。
- Exchange:交换机,接受消息,并根据路由键转发消息所绑定的队列
- Binding:Exchange和Queue之间的虚拟连接。
- Routing key:路由规则
- Queue:消息队列,保存消息并转发给消费者。
Exchange类型
- direct – 直连的方式,消息会被转发RouteKey中指定的Queue中
- topic – Exchange将RouteKey和Topic进行模糊匹配,此时队列需要绑定一个topic。可以使用通配符继续匹配,“#”匹配一个或者多个词。“*”只能匹配一个词。
- fanout – 不处理路由键,只需要简单的将队列绑定到交换机。发送到该交换机的消息都会被转发到该交换机绑定的队列上。Fanout交换机转发消息是最快的。
RabbitMQ一些高级特性
Confirm确认消息:消息的确认,是指生产者投递消息后,如果Broke收到消息会给生产者一个应答
实现流程:
- 在Channel上开启确认模式:channel.confirmSelect();
- 在Channel上添加监听:channel.addConfirmListener,根据成功和失败结果进行后续的处理。
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.1.221");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
Connection collection = connectionFactory.newConnection();
Channel channel = collection.createChannel();
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.a";
//开启消息确认
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
// 消息成功ack
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.err.println("-----消息发送成功-----");
}
//消息失败ack
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.err.println("-----消息发送失败-----");
}
});
String msg = "this is a confirm message !!!";
channel.basicPublish(exchangeName,routingKey,null,msg.getBytes());
Return消息:Return Listener用于处理一些不可路由的消息,exchange或者routingKey找不到。
实现流程:Mandatory:设置为true,则监听器会接收路由不可达的消息。如果为false,那么broker端会自动删除该消息。在Channel上添加addReturnListener监听。
public class ReturnSender {
public static void main(String[] args) throws IOException, TimeoutException {
//Connection collection = CollectionUtil.getCollection();
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.1.221");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
Connection collection = connectionFactory.newConnection();
Channel channel = collection.createChannel();
String exchangeName = "test_return_exchange";
String routingKey1 = "return.a";
String routingKey2 = "abc.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.err.println("replyCode:"+replyCode);
System.err.println("replyText:"+replyText);
System.err.println("exchange:"+exchange);
System.err.println("routingKey:"+routingKey);
System.err.println("body:"+new String(body));
}
});
String msg = "this is a confirm message !!!";
boolean mandatory = true;
channel.basicPublish(exchangeName,routingKey1,mandatory,null,msg.getBytes());
channel.basicPublish(exchangeName,routingKey2,mandatory,null,msg.getBytes());
}
}
消费端限流:rabbitmq提供一种qos(服务质量保证)功能,在非自动确认消息时,如果一定数目的消息未被确认,不进行消费新的消息,会阻塞状态。
实现流程:channel.basicQos(int prefetchCount,boolean global)方法
prefetchCount:一次有prefetchCount没被ack,消费者阻塞,直到有消息ack
消费端ACK和重回队列
消费端可以进行手动ACK和NACK,当NACK时可以让消息重回队列,消息会重新投递到Broker队列尾部。一般关闭,不然容易一直循环。
//生成端代码
public static void main(String[] args) throws IOException, TimeoutException {
//1、创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.1.221");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2、创建连接
Connection connection = connectionFactory.newConnection();
//3、创建channel
Channel channel = connection.createChannel();
String routingKey = "test001";
Map<String,Object> headers = new HashMap<>();
for (int i=0;i<5;i++){
headers.put("flag",i);
AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.headers(headers)
.build();
String msg = "this is a test message , number:"+i;
channel.basicPublish("",routingKey,props,msg.getBytes());
}
}
//消费端代码
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1、创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.1.221");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(3000);
//2、创建连接
Connection connection = connectionFactory.newConnection();
//3、创建channel
Channel channel = connection.createChannel();
String queueName = "test001";
//
channel.queueDeclare(queueName,false,false,false,null);
QueueingConsumer consumer = new QueueingConsumer(channel);
//autoAck设置为false
channel.basicConsume(queueName,false,consumer);
while (true){
//获取消息,没有消息会阻塞
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
System.out.println("收到消息:"+new String(delivery.getBody()));
if ((Integer) delivery.getProperties().getHeaders().get("flag")==0){
//requeue属性true设置为重回队列,一般设置为false,不然容易一直循环
channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,true);
}else {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}
}
}
死信队列:DLX,Dead-Letter-Exchange。利用DLX,一个消息变为死信后,会被重新publish到另一个Exchange,这个Exchange就是DLX。
消息变为死信的几种情况
- 消息被拒绝,并且requeue=false
- 消息TTL过期
- 队列达到最大长度
DLX也是一个普通的Exchange,只是设置了队列属性(添加x-dead-letter-exchange),当这个队列有死信时,RabbitMQ就会自动将这个消息发布到设置的Exchange上,进而被路由到另一个队列。
消息如何保障100%的投递成功?
什么是生产端的可靠性投递
- 保障消息成功发出
- 保障MQ节点的成功接收
- 发送端收到MQ节点的确认应答(ack)
- 完善的消息补偿机制
解决方案:消息落库,对消息状态进行打标
消费端-幂等性保障
主流的幂等性操作:业务唯一ID(或)指纹码机制,利用数据库主键去重