zoukankan      html  css  js  c++  java
  • Rabbitmq入门到进阶看这篇就够了!

    安装前提

    • 安装 erlang
    • windows用户名非中文

    可以关注我的公众号 知识追寻者,回复 rabbitmq 获取已经下载好的安装包和配套源码地址

    本套教程对应知识追寻者网址:

    1. windows安装rabbitmq (zszxz.com)
    2. RabbitMQ 入门概念 (zszxz.com)
    3. rabbitMQ 生产者与消费者 (zszxz.com)
    4. RabbitMQ 进阶 (zszxz.com)
    5. springboot集成Rabbitmq (zszxz.com)

    erlang 安装

    erlang官网地址https://www.erlang.org/downloads/23.1

    选择对应的平台版本下载软件

    双击exe,改变安装路径,无脑安装;

    新建环境变量:ERLANG_HOME

    值 erlang 安装路径;

    path添加环境变量: %ERLANG_HOME%in

    验证:

    rabbitmq 安装

    下载安装

    官网下载地址:https://www.rabbitmq.com/download.html

    github下载地址:https://github.com/rabbitmq/rabbitmq-server/releases

    知识追寻者下载的是:3.8.9

    双击exe无脑安装

    新建环境变量:RABBITMQ_SERVER

    值:mq安装路径;

    path添加环境变量: %RABBITMQ_SERVER%sbin

    安装插件

    切换至 sbin 目录底下

    cmd安装插件命令

    可以使用 rabbitmqctl status 查看 rabbitmq 是否正常运行

    rabbitmq-plugins enable rabbitmq_management
    

    启动rabbitmq

    rabbitmq-server
    

    后台启动

    rabbitmq-server -detached
    

    重启服务

    net stop RabbitMQ && net start RabbitMQ
    

    访问地址 : http://localhost:15672/#/

    默认账号 guest 密码 guest ;

    配置账号

    如果需要远程登陆,虚配置账号密码,并将账号设置为管理员;

    配置账号

    rabbitmqctl.bat add_user username password
    

    将账号设置为管理员

    rabbitmqctl.bat set_user_tags username administrator
    

    使用配置好的账号登陆

    添加权限

    • 设置用户权限

    rabbitmqctl set_permissions -p VHostPath User ConfP WriteP ReadP

    • 查看(指定hostpath)所有用户的权限信息

    rabbitmqctl list_permissions [-p VHostPath]

    • 查看指定用户的权限信息

    rabbitmqctl list_user_permissions User

    • 清除用户的权限信息

    rabbitmqctl clear_permissions [-p VHostPath] User

    例如给 用户添加 权限

    rabbitmqctl set_permissions -p "/" username ".*" ".*" ".*"
    

    RabbitMQ 入门概念

    生产者消费模型

    rabbitmq 整体上是一个生产者,消费者模型,主要用于接收,存储,转发消息;可以把次过程简单理解为 商家(producer)将货物(message)给 物流公司 (broker),物流公司再将货物快递给你(consumer)一个过程;

    • 生产者 (producer) 为投递消息的一方; 消息包含2部分,消息体(payload) 和 标签(Lable); 消息体是业务逻辑数据,比如json字符串; 消息标签是消息的描述;
    • 消费者(consumer): 消息的接收方;消费者会连接上Rabbitmq服务器,并订阅队列;当消费者消费消息的时候只是消费消息体,消息标签会在路由的过程中被抛弃,所以队列中只存储消息体;
    • 服务节点(broker):可以简单的理解broker为 RabbitMq的一个实例;

    队列

    Queue 队列 是 RabbitMq 的 内部对象,用于储存消息; 生产者可以将消息 进行发布,然后会储存至队列, 消费者可以订阅队列,进行消息的消费;多个消费者订阅一个队列时,消息队列中的消息会被轮询给多个消费者;

    交换器

    交换器(Exchange),用来接收生产者发送的消息并将这些消息路由给服务器中的队列;注意 生产者将消息发给交换器时会附带一个路由键(routing key);

    绑定(Binding),用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表

    RabbitMq 中 的 消息交换器有四种类型,分别是 fanout, direct , topic , headers ;

    • fanout: fanout 类型交换器会将所有消息都路由到交换器绑定的队列中;
    • direct :消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中;
    • topic: topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上;比如 . 用于分隔单词,# 用于模糊匹配一个单词, * 用于匹配多个单词;
    • headers: headers类型转换器 不使用路由规则路由消息,而是使用 消息内容中的headers属性进行匹配, 性能较差,使用不广泛;

    信道

    信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接

    Virtual Host

    虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 /

    客户端开发

    连接rabbitMQ

    连接客户端之前需要建立 vhost, 有三种方式,如果是spring集成则采用第一种方式;由于是原生开发,采用第二种方式,具体操作步骤看下图

    • 处理方式一(推荐)
      在 application.properties 文件中添加 spring.rabbitmq.virtual-host=test
    • 处理方式二(推荐)
      在 rabbitmq 管理后天中添加test 目录
    • 处理方式三
      在启动docker时指定vhost : RABBITMQ_DEFAULT_VHOST=test

    引入客户端依赖

    	 <dependencies>
            <dependency>
                <groupId>com.rabbitmq</groupId>
                <artifactId>amqp-client</artifactId>
                <version>4.1.0</version>
            </dependency>
        </dependencies>
    

    获取连接方式,如果启动控制台没有报错,说明连接成功;

    public static Connection getConnection() throws IOException {
            //定义连接工厂
            ConnectionFactory factory = new ConnectionFactory();
            //定义连接地址
            factory.setHost("ip");
            //定义端口
            factory.setPort(5672);
            //设置账号信息,用户名、密码、vhost
            factory.setVirtualHost("test");
            factory.setUsername("zszxz");
            factory.setPassword("密码");
            // 通过工厂获取连接
            Connection connection = null;
            try {
                connection = factory.newConnection();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            return connection;
        }
    
    

    创建队列和交换器

    • 声明一个交换机 test-ex , 类型为 direct, 持久化,并且非自动删除;exchangeDeclare方法 有多个重载方法;第一个参数exchange 交换机名称,第二个参数type 交换机类型,第三个参数durable 是否持久化;第四个参数autodelete 是否自动删除;第五个是否内置internal;第六个参数argument结构化参数;
    • 声明一个队列 test-ch, 持久化,非排他,非自动删除;第一个参数exchange 队列名称,第二个参数durable 是否持久化;第三个参数是否排他;第四个参数autodelete 是否自动删除;第五个参数argument队列的其它参数;
    • queueBind 方法参数 第一个参数queue 队列名称, 第二个参数 exchage 交换机名称,第三个参数 routingKey 用来绑定交换机和队列的路由键;第四个参数argument 其它参数;
        public static void main(String[] args) {
            try {
                Connection connection = getConnection();
                //获得信道
                Channel channel = connection.createChannel();
                String queueName = "test-ch";
                String exchangeName = "test-ex";
                String routingKey = "test-router";
                //声明交换器
                channel.exchangeDeclare(exchangeName, "direct", true);
                // 声明队列
                channel.queueDeclare(queueName, true, false, false, null);
                // 绑定队列
                channel.queueBind(queueName,exchangeName,routingKey);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    注:rabbimq 中 交换器不会消耗性能,而队列会消耗性能;

    创建成功后管理面板显示交换机

    点击交换机text -ex 可以看到绑定的队列

    发送消息

    发送消息使用到 channel.basicPublis 方法;

    • 第一个参数exchange为交换机名称,
    • 第二参数routingKey为绑定的路由键;
    • 第三个参数props为消息属性;
    • 第四个参数body为消息内容;
    • 其中消息属性指定了消息内容格式为text/plain , 优先级为1 ,传递模式2为持久化模式;
    public static void main(String[] args) {
            try {
                Connection connection = getConnection();
                //获得信道
                Channel channel = connection.createChannel();
                String queueName = "test-ch";
                String exchangeName = "test-ex";
                String routingKey = "test-router";
                //声明交换器
                channel.exchangeDeclare(exchangeName, "direct", true);
                // 声明队列
                channel.queueDeclare(queueName, true, false, false, null);
                // 绑定队列
                channel.queueBind(queueName,exchangeName,routingKey);
                //发布消息
                byte[] messageBodyBytes = "Hello Word !!!".getBytes();
                // 设置 消息属性
                AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
                builder.contentType("text/plain")
                        .priority(1)
                        .deliveryMode(2);
    
                channel.basicPublish(exchangeName,routingKey,builder.build(),messageBodyBytes);
                channel.close();
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
    

    管理面板点击队列 test-ch ,然后获取消息,可以看见刚刚发送的消息内容;

    消费消息

    消费消息分为推模式和拉取模式;推模式类似订阅,能够自动接收队列里面的消息;如果是拉模式则需要手动接收;在消费模式中有个消息应答确认机制ack,如果消费者接收到消息后提交应答,那么 rabbitMq 会将内存或者磁盘中的消息进行删除;所以为了确保做到真正的消费消息,一般情况下我们都会设置自动应答autoAck 为false ,然后通过方法进行手动提交;

    推模式

    推模式消费消息使用到basicConsume 方法;消费消息注意点为接收到消息后才进行应答,所以 autoAck 需要设置为false,不自动应答;

    basicConsume 的参数如下

    • 第一个参数queue 队列名称
    • 第二个参数 autoAck 设置是否应答;
    • 第三个参数 consumerTag 消费者标签;
    • 第四个参数回调 callback 这边使用 DefaultConsumer 实现;
     public static void main(String[] args) {
            try {
                Connection connection = getConnection();
                boolean autoAck = false;
                String queueName = "test-ch";
                String tagName = "consume-tag";
                //获得信道
                Channel channel = connection.createChannel();
                channel.basicConsume(queueName,autoAck,tagName,new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        String routingKey = envelope.getRoutingKey();
                        System.out.println("键:"+routingKey+"-----"+new String(body));
                        long deliveryTag = envelope.getDeliveryTag();
                        // 应答
                        channel.basicAck(deliveryTag,false);
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    输出

    键:test-router-----Hello Word !!!
    

    再次去后台获取消息时,队列中的消息为空,因子消息被消费了;

    拉模式

    我们重新使用生产者发送一条消息;然后进行消费;

        public static void main(String[] args) {
            try {
                Connection connection = getConnection();
                //获得信道
                Channel channel = connection.createChannel();
                String queueName = "test-ch";
                // 获取响应
                GetResponse response = channel.basicGet(queueName, false);
                System.out.println(new String(response.getBody()));
                //
                channel.basicAck(response.getEnvelope().getDeliveryTag(),false);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    输出结果

    Hello Word !!!
    

    如果查看队列中有多少消息呢,可以通过后台进行查看,ready 表示 等待被消费的消息数量,特色D表示持久化durable队列

    拒绝消息

    消费者可以接收消息,也可以拒绝消息;

    单条消息拒绝 channel.basicReject(long delieveryTag, boolean requere); 方法进行拒绝应答;

    如果是多条消息channel.basicNack(long delieveryTag,boolean multiple, boolean requere); 参数 multiple 为false 功能与 channel.basicReject 方法功能一致,为true 表示拒绝 编号 delieveryTag 之前的所有消息;

    关闭连接

    关闭连接是为了释放系统资源,关闭连接需要连接关闭和通道关闭,

    channel.close();
    connection.close();
    

    RabbitMQ 进阶

    消息失败监听

    channel.basicPublish() 有多个重载方法,当有五个参数时,第三个参数 mandatory 为true 时,表示交换机无法根据类型和路由键找到队列时会将失败消息返回给RabbitMq,接收消息时需要使用channel.addReturnListener 方法; 当 mandatory 为false 时,出现发送失败的情况直接将消息丢弃;

    代码示例

     channel.basicPublish(exchangeName,routingKey,true,builder.build(),messageBodyBytes);
    
                channel.addReturnListener(new ReturnListener() {
                    @Override
                    public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
                        String msg = new String(bytes);
                        System.out.println("返回消息:"+msg);
    
                    }
                });
    

    设置过期时间TTL

    过期时间可以对具体的消息进行设置过期时间,这种方式的优点是可以对每个消息进行自定义设置过期时间;其次也可以对队列进行设置过期时间;

    对消息设置过期时间

    主要是加了 x-message-ttl 参数, 单位为毫秒;并且当前队列为非持久化队列;

    			//获得信道
                Channel channel = connection.createChannel();
                String queueName = "test-undurable";
                String exchangeName = "test-ex";
                String routingKey = "test-router";
                //声明交换器
                channel.exchangeDeclare(exchangeName, "direct", true);
                HashMap<String, Object> args = new HashMap<>();
                // 设置消息
                args.put("x-message-ttl",8000);
                // 声明队列
                boolean durable = false;
                boolean exclusive = false;
                boolean autodelete = false;
                channel.queueDeclare(queueName, durable, exclusive, autodelete, args);
                // 绑定队列
                channel.queueBind(queueName,exchangeName,routingKey);
    

    如果打开后台就能观测到队列 test-undurable 特色标志为TTL ,有信息ready值为1,然后经过8秒后ready为0;

    对队列设置过期时间

    设置参数 x-expires 单位为毫秒;如下示例代码片段演示的是3分钟;

     			String queueName = "test-expire";
                String exchangeName = "test-ex";
                String routingKey = "test-router";
                //声明交换器
                channel.exchangeDeclare(exchangeName, "direct", true);
                HashMap<String, Object> args = new HashMap<>();
                // 设置队列过期时间
                args.put("x-expires",1000*60*3);
                // 声明队列
                boolean durable = false;
                boolean exclusive = false;
                boolean autodelete = false;
                channel.queueDeclare(queueName, durable, exclusive, autodelete, args);
                // 绑定队列
                channel.queueBind(queueName,exchangeName,routingKey);
    

    后台多了一个特色为Exp的队列 test-expire

    死信队列

    死信队列全称为 死信交换器(dead-letter-exchange),当一个消息在一个队列中变成死信后,它就会被重新发送到另一个交换器,这个交换器就被成为死信交换器DLX, 绑定DLX的队列称为死信队列;DLX 与平常的交换器没什么不同,就多了一个接收死信队列消息的功能;dlx通常用于处理异常状态下消息不能被处理的问题;

      			//获得信道
                Channel channel = connection.createChannel();
                String queueName = "test-dlx";
                String exchangeName = "test-ex-dlx";
                String routingKey = "test-router-exlx";
                //声明交换器
                channel.exchangeDeclare(exchangeName, "direct", true);
                HashMap<String, Object> args = new HashMap<>();
                // 指定dlx
                args.put("x-dead-letter-exchange",exchangeName);
                // 声明队列
                boolean durable = false;
                boolean exclusive = false;
                boolean autodelete = false;
                channel.queueDeclare(queueName, durable, exclusive, autodelete, args);
                // 绑定队列
                channel.queueBind(queueName,exchangeName,routingKey);
    

    指定参数 x-dead-letter-exchange 发送消息后 后台会出现一个 test-dlx 的队列 ,特色类型为DLX队列;

    其次我们使用TTL过期消息绑定到DLX交换器,这样如果消息时间过期后会将过期的消息发送到死信队列;

     			//获得信道
                Channel channel = connection.createChannel();
                String queueName = "test-ttl";
                String exchangeName = "test-ex-ttl";
                String routingKey = "test-router-ttl";
                //声明交换器
                channel.exchangeDeclare(exchangeName, "direct", true);
                HashMap<String, Object> args = new HashMap<>();
                // 指定dlx
                args.put("x-dead-letter-exchange","test-ex-dlx");
                // 指定dlx 路由键
                args.put("x-dead-letter-routing-key","test-router-exlx");
                // 设置消息过期时间
                args.put("x-message-ttl",8000);
                // 声明队列
                boolean durable = false;
                boolean exclusive = false;
                boolean autodelete = false;
                channel.queueDeclare(queueName, durable, exclusive, autodelete, args);
                // 绑定队列
                channel.queueBind(queueName,exchangeName,routingKey);
    

    刚刚启动ttl,dlx 2个服务,各自产生一个队列并且各自存储一个消息;

    当ttl 消息过期后,会将过期的消息发送到死信队列;死信队列的的消息从1变为2;

    延迟队列

    延迟队列是生成者发送消息后经过一定的延时后才将消息发送到消费者手中;延迟队列的场景一般使用在订单系统,比如一个用户下单30分钟内订单支付失败,该订单就会通过异常处理,这时就可以使用延迟队列实现;rabbitMQ并未具体实现延迟队列,但我们可以使用上节的dlx队列和ttl队列结合实现;即首先发送ttl消息为30分钟,如果30分钟后消息未被处理则进入延迟队列;

    优先队列

    优先队列是指在该队列中的消息被消费具有优先权,如果优先权的值越大,则被优先消费的机率也越大;

    通过设置参数x-max-priority 设置队列为优先对俄,值越大,优先级越高,最大值为10;

    对具体的消息也可以通过消息属性设置优先级, 示例代码中设置优先级为1,,范围为 0到5;

     			 //获得信道
                Channel channel = connection.createChannel();
                String queueName = "test-pri";
                String exchangeName = "test-pri";
                String routingKey = "test-router-pri";
                //声明交换器
                channel.exchangeDeclare(exchangeName, "direct", true);
                HashMap<String, Object> args = new HashMap<>();
                // 设置消息
                args.put("x-max-priority",10);
                // 声明队列
                boolean durable = false;
                boolean exclusive = false;
                boolean autodelete = false;
                channel.queueDeclare(queueName, durable, exclusive, autodelete, args);
                // 绑定队列
                channel.queueBind(queueName,exchangeName,routingKey);
                //发布消息
                byte[] messageBodyBytes = "Hello Word !!!".getBytes();
                // 设置 消息属性
                AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
                // 设置消息的优先队列为 1
                builder.contentType("text/plain")
                        .priority(1)
                        .deliveryMode(2);
    
                channel.basicPublish(exchangeName,routingKey,builder.build(),messageBodyBytes);
    

    持久化

    rabbitmq 消息的持久化分为交换器的持久化,队列的持久化,以及消息的持久化;在持久化中需要使用三种一起使用才能保证消息真的持久化;

    如果交换器,队列,消息只要其中一个未设置持久化,那么重启后就会造成对应的消息消失;还有消费者的应答机制也要设置为false,如果设置为true,消费者未来得及处理消息就应答了也会造成消息消失;

    交换器持久化

    boolean durable = true;
    channel.exchangeDeclare(exchangeName, "direct", durable);
    

    队列持久化

     			// 声明队列
                boolean durable = true;
                boolean exclusive = false;
                boolean autodelete = false;
                channel.queueDeclare(queueName, durable, exclusive, autodelete, args);
    

    消息持久化

    int durable = 2;
    // 设置消息的优先队列为 1
                builder.contentType("text/plain")
                        .priority(1)
                        .deliveryMode(durable);
    

    生产者应答

    仅仅保证消息的持久化并不能完全保证消息不会丢失,生产者在发送消息的时候也有可能造成消息丢失;生产者发送消息时,并不知道自己的消息是否真的发送到了rabbimq 的服务端;所以rabbitmq 增加了一个 生产者的消息应答机制,确保消息真的发送到了了rabbimq服务端; 其工作大体流程为,生产者发送消息之前需要开启信道确认模式,开启后生产者每发送一条消息到服务端,都会生成一个唯一的ID用于区分不同的消息,服务端接收到消息后会发送一个确认信息给生产者,如果接收失败,则生产者需要手动进行重新发送;

      			// 设置信道为确认应答模式
                channel.confirmSelect();
                // 发送消息
                channel.basicPublish(exchangeName,routingKey,builder.build(),messageBodyBytes);
                try {
                    if (!channel.waitForConfirms()){
                        System.out.println("发送消息失败");
                    }else{
                        System.out.println("发送消息成功");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    

    生产者还有一个事物机制,也可以实现应答机制类似效果,但事物的使用是阻塞形式,会造成很大的性能消耗;具体工作流程是发送消息之前开启事物,消息发送之后进行异常捕获,如果发生异常,说明消息发送失败,进行事物回滚,重新发送消息;

    		 try {
                    // 设置信道为事物模式
                    channel.txSelect();
                    // 发送消息
                    channel.basicPublish(exchangeName,routingKey,builder.build(),messageBodyBytes);
                    // 提交事物
                    channel.txCommit();
    
                }catch (Exception e){
                    e.printStackTrace();
                    // 回滚事物
                    channel.txRollback();
                }
    

    springboot集成Rabbitmq

    生产者

    direct

    常量: 常量配置 交换机名称, 队列名称,路由键名称

    /**
     * @author lsc
     * <p> </p>
     */
    public class MqConstant {
    
        // 队列名称
        public static final String QUEUE_NAME = "zs-q";
        // 交换机名称
        public static final String EXCHANGE_NAME = "zs-ex";
        // 路由键名称
        public static final String ROUTING_NAME = "zs-r";
    }
    
    

    配置文件

    设置 账号 密码 和 ip ;

    server:
      port: 8096
    spring:
      #给项目来个名字
      application:
        name: rabbitmq-provider
      #配置rabbitMq 服务器
      rabbitmq:
        host: 'ip'
        port: 5672
        username: '账号'
        password: '密码'
        #虚拟host 可以不设置,使用server默认host  /
        virtual-host: test
    

    配置类

    /**
     * @author lsc
     * <p> </p>
     */
    @Configuration
    public class DirectConfig {
        @Bean
        public Queue directQueue() {
            return new Queue(MqConstant.QUEUE_NAME,true,false,false);
        }
    
        @Bean
        DirectExchange directExchange() {
            return new DirectExchange(MqConstant.EXCHANGE_NAME,true,false);
        }
    
        @Bean
        Binding bindingDirect() {
            return BindingBuilder.bind(directQueue()).to(directExchange()).with(MqConstant.ROUTING_NAME);
        }
    
    }
    

    测试类

    /**
     * @author lsc
     * <p> </p>
     */
    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class ProviderTest {
    
        @Autowired
        RabbitTemplate rabbitTemplate;
    
        @Test
        public void testSend(){
            String message = "zs hello word";
            CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
            rabbitTemplate.convertAndSend(MqConstant.EXCHANGE_NAME,MqConstant.ROUTING_NAME,message,correlationId);
        }
    }
    

    发送成功

    topic

    topic 只需要修改配置类,绑定多个队列即可;

    @Configuration
    public class TopicConfig {
    
        @Bean
        public Queue firstQueue() {
            return new Queue("topic-queue-1");
        }
    
        @Bean
        public Queue secondQueue() {
            return new Queue("topic-queue-2");
        }
    
        @Bean
        TopicExchange exchange() {
            return new TopicExchange("topic-ex");
        }
        
        @Bean
        Binding bindingExchangeMessage1() {
            return BindingBuilder.bind(firstQueue()).to(exchange()).with("topic-routing-1");
        }
    
        @Bean
        Binding bindingExchangeMessage2() {
            return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic-routing-2");
        }
    }
    

    在发送消息地时候修改对应地交换机名称,路由键即可发送至不同队列;

    // 发送至队列 topic-queue-1
    rabbitTemplate.convertAndSend("topic-ex","topic-routing-1",message,correlationId);
    // 发送至队列 topic-queue-2
    rabbitTemplate.convertAndSend("topic-ex","topic-routing-2",message,correlationId);
    

    消费者

    自动应答

    在之前生产者地配置基础上,使用注解@RabbitListener 可以实现对队列消息地监听,此监听对应地消息应答机制为自动应答;

    /**
     * @author lsc
     * <p>消费者--消息监听 </p>
     */
    @Component
    @RabbitListener(queues = MqConstant.QUEUE_NAME)//监听的队列名称
    public class QueueListen {
    
    
        @RabbitHandler
        public void process(String message, Channel channel) {
            System.out.println("消费者收到消息-----------" + message);
        }
    }
    

    手动应答

    手动应答需要修改配置文件,添加listener 配置

    server:
      port: 8096
    spring:
      #给项目来个名字
      application:
        name: rabbitmq-provider
      #配置rabbitMq 服务器
      rabbitmq:
        host: 
        port: 5672
        username: 
        password: 
        # 是否返回回调
        publisher-returns: true
        template:
        #开启mandatory: true, basic.return方法将消息返还给生产者
          mandatory: true
        #虚拟host 可以不设置,使用server默认host  /  
        virtual-host: test
        listener:
          simple:
            # 手动应答
            acknowledge-mode: manual
            # 最少消费者数量
            concurrency: 1
            # 最多消费者数量
            max-concurrency: 10
            # 支持重试
            retry:
              enabled: true
    
    

    配置手动应答后如果消费者没有应答接收到消息,重新启动工程后还会接收到之前地消息;

    /**
     * @author lsc
     * <p> </p>
     */
    @Component
    public class AckQueueListen {
    
        @RabbitListener(queues = MqConstant.QUEUE_NAME)
        public void process1(Message message, Channel channel) {
            System.out.println("消费者接收消息: " + new String(message.getBody()));
            try {
                // 手动应答成功
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    如果是手动拒绝

     channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
    

    配套源码 微信公众号: 知识追寻者 回复 rabbitmq

  • 相关阅读:
    巧用加密方法保障电子邮件系统安全 狼人:
    网管员注意:保障邮件安全的七条措施 狼人:
    全面剖析DNS 0DAY攻击、威胁以及防治 狼人:
    简述:电子邮件安全发展 狼人:
    IE漏洞致数百万用户中招 快用瑞星卡卡打补丁 狼人:
    警惕可执行文件:三类危险TXT类型文件 狼人:
    安全使用电子邮件十三法 狼人:
    提高IE和Email的安全性的四步骤 狼人:
    了解电子邮件加密 保证隐私内容安全 狼人:
    防不胜防 了解DNS缓存中毒攻击原理 狼人:
  • 原文地址:https://www.cnblogs.com/zszxz/p/15070289.html
Copyright © 2011-2022 走看看