文章目录
一、初识RabbitMq
RabbitMq是一个开源的消息代理和队列服务器,用来通过普通的协议在不同的应用之间共享数据,RabbitMq是使用Erlang语言编写的,并且RabbitMq是基于AMQP协议的。
1、RabbitMq有什么优点呢?
1.提供可靠性消息投递模式(confirm)、返回模式(return)······
2.与SpringAMQP完美的融合,提供了丰富的API
3.集群模式丰富,支持表达式配置,HA模式(High Availability—高可用),镜像队列模型(稳定)
4.保证数据不丢失的前提下,做到高可靠性,高可用性
2、RabbitMq高性能的原因?
1.Erlang语言最初在于交换机领域的架构模式,因此RabbitMq天生具有处理高并发的能力,
2.Erlang有着和Socket(RPC通讯框架Dubbo底层就是Socket实现的)一样优秀的延迟效果,
3、AMQP
1.AMQP(Advanced Message Queuing Protocol)是高级队列协议
2.AMQP定义:是一个二进制协议,是一个提供同一消息的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息中间件而设计的
3.AMQP协议模型
4、AMQP核心概念
1.Server:又称Broker(中间人,代理人),接收客户端的连接,实现AMQP的实体服务
2.Connection:创建连接
3.Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立多个Channel,每个Channel代表一个会话任务。如果去掉一个n,就变成了chanel(香奈儿)
4.Message:消息,服务器和应用程序传送的数据,有Properties和Body组成。Properties可以对消息进行修饰(修改默认值),比如消息的优先级、延迟等,Body就是消息的实体
5.Virtual host:虚拟地址,用于进行逻辑隔离,最上层的消息路由。一个Virtual Host中可以有多个 Exchange和Queue,同一个Virtual Host中不能有重名的Exchange和Queue
6.Exchange:交换机,接收消息,将消息Routing-Key(路由)到Queue进行Binding(绑定)。
7.Binding:Exchange和Queue之间的虚拟连接,Binding中包好Routing-Key(路由)
8.Routing-key:一个路由规则,虚拟机可以用它来确定如何路由一个特定的消息
9.Queue:消息队列,保存消息并将其转发给消费者
5、RabbitMq的整体架构图
6、RabbitMq消息是如何流转的
二、RabbitMq安装
centos7+linux安装(注意:Erlang和RabbitMq版本要根据官网给出的对照进行安装)
三、RabbitMq命令行操作
1、基础操作
rabbitmqctl stop_app:关闭应用
rabbitmqctl start_app:启动应用
rabbitmqctl status:节点状态
rabbitmqctl add_user username password:添加用户
rabbitmqctl list_users:列出所有用户
rabbitmqctl delect_username:删除用户
rabbitmqctl change_password username newpassword:修改密码
rabbitmqctl clear_permissions -p vhostpath username:清除用户权限
rabbitmqctl list_user_permissions username:列出用户权限
rabbitmqctl set_permissions -p vhostpath username:设置用户权限
rabbitmqctl add_vhost vhostpath:创建虚拟主机
rabbitmqctl list_vhosts:列出所有虚拟主机
rabbitmqctl list_permissions -p vhostpath:列出虚拟主机上的所有权限
rabbitmqctl delete_vhost vhostpath:删除虚拟主机
rabbitmqctl list_queues:查看所有队列信息
rabbitmqctl -p vhostpath purge_queue blue:清除队列里的消息
2、高级操作
rabbitmqctl reset:移除所有数据,要在rabbitmqctl stop_app之后使用
rabbitmqctl join_cluster <clusternode> [--ram]:组成集群命令,[--ram]是存储模式 (加入的节点存储在内存中),[--disc]存储模式是将节点写入磁盘中
rabbitmqctl cluster_status:查看集群状态
rabbitmqctl change_cluster_node_type disc | ram:修改集群节点的存储形式
rabbitmqctl forget_cluster_node [--offline]:摘除节点(忘记节点)
rabbitmqctl rename_cluster_node oldnode1 newnode1:修改节点名称
3、演示一个命令
下图所示是RabbitMq默认的Exchange
命令符操作:
RabbitMq既然有web可视化操作页面,为什么还有用命令行操作呢?提升逼格!!!
四、HELLO_WORLD,
创建一个SpringBoot项目(随意,普通的maven也行)
1、导入RabbitMq所需的依赖
<!--RabbitMQ 最好和安装版本相对照-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.6.5</version>
</dependency>
2、创建Producer(生产者)
package com.storm.rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Created by StormEnum on 2019/3/18.
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建connectionFaction,并设置相关属性值
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.50.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//通过connectionFaction创建连接
Connection connection = connectionFactory.newConnection();
//通过connection创建一个Channel
Channel channel = connection.createChannel();
//发布消息
String message = "Hello-World";
channel.basicPublish("","test",null,message.getBytes());
//关流
channel.close();
connection.close();
}
}
3、创建Consumer(消费者)
package com.storm.rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Created by StormEnum on 2019/3/18.
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//创建connectionFaction,并设置相关属性值
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.50.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//通过connectionFaction创建连接
Connection connection = connectionFactory.newConnection();
//通过connection创建一个Channel
Channel channel = connection.createChannel();
//声明一个队列
//参数--exclusive:独占(这个队列只有一个连接可以使用)
String queueName = "test";
channel.queueDeclare(queueName,true,false,false,null);
//创建消费者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
//设置Channel
channel.basicConsume(queueName,true,queueingConsumer);
//设置Channel
channel.basicConsume(queueName,true, queueingConsumer);
//获取消息
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("消费端消费的消息:"+message);
}
}
4、查看效果
启动Consumer
启动Producer
如下图:已经被消费
控制台输出
消费端消费的消息:Hello-World
5、问题?
有没有发现我们的Producer中并没有声明(定义)一个Exchange,上面我们看到无论是在RabbitMq的整体架构图中,还是消息流转图中,都有一个必不可少的Exchange,但这里为什么没有了呢??我们带着这个疑问进入下一个核心知识点
五、Exchange
1、为什么没有声明Exchange可以路由到队列中??
我们看一下"test"这个消息的绑定信息
Default exchange binding(队列和默认Exchange绑定)
那我们看一下这个默认的Exchange是什么东西?
进入这个默认的Exchange,看一下它的绑定信息
译:这个default Exchange可以将消息路由到与routing-key相等的queue中。
现在应该明白了,虽然没有声明Exchange,但RabbitMq提供了一个默认的Exchange,只要routing-key(路由规则)和queue相等,则可以成功将消息路由到queue中
2、Exchange的几种类型
direct:
direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。
fanout:
fanout类型的Exchange路由规则非常简单(其实就没有路由规则),它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中(转发消息最快、简单粗暴、省心、省力,就是不怎么用得着)。
topic:
与direct类型的Exchange类似,也是把消息路由到那些binding key与routing key完全匹配的Queue中,
但又扩展了一些约定:
1)、routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
2)、binding key与routing key一样也是句点号“. ”分隔的字符串
3)、binding key中可以存在两种特殊字符星号与“#”,用于做模糊匹配,其中星号用于匹配一个单词,“#”用于匹配多个单词(注意:可以是零个)
六、消息的可靠性投递
1、生产端的可靠性投递
1.1、完成消息可靠性投递需要确保以下几点
1.保障消息的成功发出
2.保证MQ节点的成功接收
3.发送端收到MQ节点(Broker-service)确认应答
4.完善的消息补偿机制
1.2、如何实现消息的可靠性投递
1.消息入库,对消息状态进行更改
step1:将Message封装insert到DB
step2:发送消息到Broker
step3:Broker返回消费确认
step4:将DB中的Message的status更改为确认消费(比如status = 1)
step5:设置一个定时任务,每隔一定时间对DB进行一次查询并get到status为没有消费确认的消息
step6:从新发送Message(回到step1)
当然上图所示也并非完善,也存在如下问题
1.如果step3断路了(RPC通信),那么DB中的message的状态永远都是未被确认状态,那么也就导致消息一致发送
2.消息确认机制需要一定的时间,如果消息发送到Broker,还没等到消息confirm,此时定时任务执行,导致正常的消息被当作未被消费确认而重新发送
2.消息的延迟投递,做二次确认,回调检查(通常适用于数据量较大的环境)
step1:发送message到Broker,
step2:step1执行的同时,进行延迟发送message
step3:消费者消费message
step4:message被消费,返回confirm
step5:call back监听返回confirm,并持久化到message db中
step6:call back监听到Delay Message,然后和message db中进行比较,不存在,重新发送消息
2、Confirm确认机制
2.1、Confirm确认消息的理解
1.消息确认,是指生产者投递消息后,如果Broker收到消息,则会给生产者一个ackonledge,代码名称 ACK(回复:收到确认)
2.生产者进行接受应答,用来确定这条消息是否正常的发送到Broker,这种方式也是消息的可靠性投递的核心保障!
2.2、Confirm确认消息的流程
2.3、Confirm如何确认消息
1.在Channel上开启确认模式:channel.confirmSelect()
2.在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送,或记录日志等操作·········
2.4、在Hello-World代码基础上完成我们的Confirm消息确认(使用Topic类型)
Producer
/**
* Created by StormEnum on 2019/3/19.
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建connectionFaction,并设置相关属性值
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.50.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//通过connectionFaction创建连接
Connection connection = connectionFactory.newConnection();
//通过connection创建一个Channel
Channel channel = connection.createChannel();
//开启确认模式
channel.confirmSelect();
//发布消息
String exchangeName = "confirm.exchange.name";
String routingKeyName = "confirm.routing.key.name";
String message = "Hello-World";
channel.basicPublish(exchangeName,routingKeyName,null,message.getBytes());
//添加confirm监听
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
//收到确认执行的方法 Acknowledge (回复已确认)
System.out.println("ACK");
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//确认失败执行的方法 NoAcknowledge (没有回复确认)
System.out.println("NoAck");
}
});
}
}
Consumer
/**
* Created by StormEnum on 2019/3/19.
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//创建connectionFaction,并设置相关属性值
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.50.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//通过connectionFaction创建连接
Connection connection = connectionFactory.newConnection();
//通过connection创建一个Channel
Channel channel = connection.createChannel();
//声明交换机和队列、然后进行绑定设置、最后指定Routing-Key
String exchangeName = "confirm.exchange.name";
String routingKeyName = "confirm.#"; //exchange使用的是topic、可以使用*,#进行模糊匹配
String queueName = "confirm.queue.name";
channel.exchangeDeclare(exchangeName,"topic",true);
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,exchangeName,routingKeyName);
//创建消费者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
//设置Channel
channel.basicConsume(queueName,true,queueingConsumer);
//获取消息
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("消费端消费的消息:"+message);
}
}
启动Consumer
启动Producer
在这说明一下:exchange、queue、routing-key的声明可以在Producer中也可以在Consumer中
3、Return消息机制
3.1、Return消息机制理解
1.ReturnListener用于处理一些不可路由的消息!
2.Producer,通过指定一个Exchange和routing-Key,吧消息送达到某一个队列中,然后我们的消费者监听队列,进行消费处理操作。
3.但是在某些情况下,如果我们在发送消费的时候,当前的exchange不存在或者指定的routing-key路由不到,这时候如果我们需要监听这种不可达的消息,就要使用Return Listener。
3.2、Return消息机制流程
3.3、如何使用Return消息机制
Producer中发送消息的代码中有一个参数Boolean mandatory
看一下源码:
void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
throws IOException;
默认为false。如果为true,则监听器会接受到路由不可达的消息,然后进行后续处理,
如果为false,那么Broker端会自动删除该消息
3.4、来看下代码
Consumer我就不贴出来了,和之前的比,除了更换了Queue、exchange、routing-key的名字,没什么变化
/**
* Created by StormEnum on 2019/3/19.
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建connectionFaction,并设置相关属性值
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.50.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//通过connectionFaction创建连接
Connection connection = connectionFactory.newConnection();
//通过connection创建一个Channel
Channel channel = connection.createChannel();
//开启确认模式
//channel.confirmSelect();
//发布消息
String exchangeName = "return.exchange.name";
String routingKeyName = "error.routing.key.name";
String message = "Hello-World";
//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("-----handler-------result-------");
System.err.println("replyText"+replyText);
System.err.println("exchange"+exchange);
System.err.println("routingKey"+routingKey);
System.err.println("properties"+properties);
System.err.println("body"+new String(body));
}
});
channel.basicPublish(exchangeName,routingKeyName,true,null,message.getBytes());
}
}
//routingkeyName的名字是一个错误的名字,同时mandatory设置为true(Return机制)
Producer控制台输出:看一下handlerReturn中参数都是什么
-----handler-------result-------
replyTextNO_ROUTE //没有找到routing-key路由规则
exchangereturn.exchange.name //Exchange名称
routingKeyerror.routing.key.name //设置的routing-key
//message的properties,我们设置的为null
properties#contentHeader<basic>(content-type=null, content-encoding=null, headers=null, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
bodyHello-World //message的body实体
七、自定义监听
1、说明
1.之前,我们使用queueingConsumer.nextDelivery();来接受并消费消息,显然这种方式不太灵活
2.我们可以使用自定义的Consumer来进行监听,解耦行更强,也更加常见。
2、用法
1.自定义监听类继承defaultConsumer类,重写handleDelivery方法
2.将consumer中的basicConsumer()中的消费者进行替换,OK!
3、代码实现
Producer和之前一样
3.1、MyConsumer
/**
* Created by StormEnum on 2019/3/20.
*/
public class MyConsumer extends DefaultConsumer {
/**
* Constructs a new instance and records its association to the passed-in channel.
*
* @param channel the channel to which this consumer is attached
*/
public MyConsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
System.out.println("consumerTag"+consumerTag);
System.out.println("envelope"+envelope);
System.out.println("properties"+properties);
System.out.println("body"+new String(body));
}
}
3.2、Consuemr
/**
* Created by StormEnum on 2019/3/20.
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//创建connectionFaction,并设置相关属性值
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.50.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//通过connectionFaction创建连接
Connection connection = connectionFactory.newConnection();
//通过connection创建一个Channel
Channel channel = connection.createChannel();
//声明交换机和队列、然后进行绑定设置、最后指定Routing-Key
String exchangeName = "consumer.exchange.name";
String routingKeyName = "consumer.#"; //exchange使用的是topic、可以使用*,#进行模糊匹配
String queueName = "consumer.queue.name";
channel.exchangeDeclare(exchangeName,"topic",true);
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,exchangeName,routingKeyName);
//设置Channel
channel.basicConsume(queueName,true,new MyConsumer(channel));
}
}
3.3、handleDelivery方法中的参数
看打印台输出内容
//内部生成的一个标示
consumerTag----amq.ctag-GR_BKdYrjMFYCVrhromC_Q
//deliveryTag=1,message的唯一标识,划重点!重点!重点!重点!重点,
envelope----Envelope(deliveryTag=1, redeliver=false, exchange=consumer.exchange.name, routingKey=consumer.routing.key.name)
//我们设置为null,message的组成部分
properties----#contentHeader<basic>(content-type=null, content-encoding=null, headers=null, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
//Body实体
body----Hello-World
八、消费端限流
1、为什么要消费端限流
当我们的消息队列中存在大量的消息,开启消费端,大量的消息同时涌入,消费端是吃不消的,因此需要进行消费端限流
2、消费端如何限流
RabbitMQ提供了一种qos(服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过consumer和channel设置Qos的值)未被确认之前,不进行消费新的消息。
/**
* prefetchSize:消息大小限制,消费端一般不设置,默认0
* prefetchCount:消费端一次最大消费多少消息,我们设置为最大消费1个消息
* global:消费端限流的级别,true:Channel级别,false:consumer级别
*/
void BasicQos(uint prefetchSize,ushort prefetchCount,bool global);
3、代码实现
Producer
//和之前基本没有变化,在发送消息的时候多了个for循环,发送5条消息
for (int i = 0; i < 5; i++) {
channel.basicPublish(exchangeName,routingKeyName,null,message.getBytes());
}
Consuemr
/**
* Created by StormEnum on 2019/3/20.
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//创建connectionFaction,并设置相关属性值
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.50.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//通过connectionFaction创建连接
Connection connection = connectionFactory.newConnection();
//通过connection创建一个Channel
Channel channel = connection.createChannel();
//声明交换机和队列、然后进行绑定设置、最后指定Routing-Key
String exchangeName = "qos.exchange.name";
String routingKeyName = "qos.#"; //exchange使用的是topic、可以使用*,#进行模糊匹配
String queueName = "qos.queue.name";
channel.exchangeDeclare(exchangeName,"topic",true);
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,exchangeName,routingKeyName);
//消费端限流
channel.basicQos(0,1,false);
//消费端限流必须在 非自动应答状态下(划重点!!重点,重点,重点,重点,重点,重点)
channel.basicConsume(queueName,false,new MyConsumer(channel));
}
}
MyConsumer(这之前比较,我们引入了channel,因为我们需要应答机制)
/**
* Created by StormEnum on 2019/3/20.
*/
public class MyConsumer extends DefaultConsumer {
private Channel channel;
public MyConsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//查看消费了多少条消息
System.out.println("每消费一条消息,我就现身一次");
//如果仔细的人,应该注意到了前面的--划重点!内容 DeliveryTag(消息的唯一标示)
//channel.basicAck(envelope.getDeliveryTag(),false);
}
}
注意:MyConsumer中的basicAck()方法被注释掉了
4、观察效果
启动consumer
启动producer
控制台输出:
我们将MyConsumer中的basicAck()方法放开,在此启动
由此可以看出:在消息未被确认消费之前,不消费新的消息。
九、消费端ACK与重回队列
1、消费端ACK
ACK在消费端限流中其实已经使用过了,而ACK和NACK的意思在Confirm的确认机制中也已经说明过
2、消费端重回队列
1.消费端重回队列是对没有处理成功的消息,将消息重新递给Broker;
2.一般情况下,我们并不会开启重回队列,也就是说设置为false;
3、代码实现
Producer
/**
* Created by StormEnum on 2019/3/20.
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建connectionFaction,并设置相关属性值
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.50.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//通过connectionFaction创建连接
Connection connection = connectionFactory.newConnection();
//通过connection创建一个Channel
Channel channel = connection.createChannel();
//开启确认模式
//channel.confirmSelect();
//发布消息
String exchangeName = "ACK.exchange.name";
String routingKeyName = "ACK.routing.key.name";
for (int i = 0; i < 5; i++) {
String message = "Hello-World-----"+i;
Map<String,Object> headers = new HashMap<>();
headers.put("StormEnum",i);
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2) //2代表投递模式持久化
.contentEncoding("UTF-8")
.headers(headers)
.build();
channel.basicPublish(exchangeName,routingKeyName,properties,message.getBytes());
}
}
}
Consumer
/**
* Created by StormEnum on 2019/3/20.
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//创建connectionFaction,并设置相关属性值
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.50.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//通过connectionFaction创建连接
Connection connection = connectionFactory.newConnection();
//通过connection创建一个Channel
Channel channel = connection.createChannel();
//声明交换机和队列、然后进行绑定设置、最后指定Routing-Key
String exchangeName = "ACK.exchange.name";
String routingKeyName = "ACK.#"; //exchange使用的是topic、可以使用*,#进行模糊匹配
String queueName = "ACK.queue.name";
channel.exchangeDeclare(exchangeName,"topic",true);
channel.queueDeclare(queueName,true,false,false,null);
channel.queueBind(queueName,exchangeName,routingKeyName);
//消费端限流
//channel.basicQos(0,1,false);
//手动签收 aotoAck:false
channel.basicConsume(queueName,false,new MyConsumer(channel));
}
}
MyConsumer 注意看注释内容
/**
* Created by StormEnum on 2019/3/20.
*/
public class MyConsumer extends DefaultConsumer {
private Channel channel;
public MyConsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//查看消费了多少条消息
System.out.println("每消费一条消息,我就现身一次-------"+new String(body));
//如果自定义的header == 0,则投递失败,由于requeue = true,此消息会重新投递
if((Integer)properties.getHeaders().get("StormEnum") == 0){
//参数:deliveryTag,,multiple(是否批量处理),requeue(重新投递,将投递失败的消息重新投递到消息的尾端)
channel.basicNack(envelope.getDeliveryTag(),false,true);
}else{
//如果仔细的人,应该注意到了前面的--划重点!内容 DeliveryTag(消息的唯一标示)
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
}
启动Consumer Producer
查看控制台:
十、TTL队列&消息
1、TTL介绍
1.TTL是Time To Live的缩写,生存时间
2.RabbitMQ可以指定消息的过期时间,在消息投递时,可以进行指定
3.RabbitMQ支持队列的过期时间,从消息投入队列开始计算,超过队列的超时时间设置,消息就会自动删除
十一、死信队列
1、死心队列介绍
1.DLX,Dead-Letter-Exchange(我认为:私信交换机更容易理解)
2.里哟个DLX,当消息在一个队列中编程死信(dead message)之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX
2.消息变成死信队列的几种情况
1.消息被拒绝(basic.reject | basic.nack)并且requeue = false(消息投递失败,不重新投递)
2.消息TTL过期
3.队列达到最大长度
3.死信队列详细说明
1.DLX也是一个正常的Exchange,和一般的Exchange没有区别,他可以在任何的队列上被指定,实际上就是设置某个队列的属性
2.当这个队列中有死信时候,RabbitMQ就会自动的将这个消息重新发不到设置的Exchange(DLX),进而被路由到零一个队列
4.用法
1.我们需要在此声明一个交换机(使用toptic),队列,routing-key(使用#全匹配)。
2.声明消息的时候最后一个参数arguments进行设定。
//key值固定(不能修改),value是声明的死信交换机
arguments.put("x-dead-letter-exchange","dlx.exchange")