RabbitMQ入门之精品讲解

文章目录

一、初识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:消息,服务器和应用程序传送的数据,有PropertiesBody组成。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")
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值