RabbitMQ的学习

news2024/11/24 9:03:43

MQ引言

什么是MQ

MQ(Message Quene):翻译为消息队列,通过典型的生产者消费者模型生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入轻松的实现系统间解耦。别名为消息中间件,通过利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。

MQ有哪些

当今市面上有很多主流的消息中间件,如老牌的ActiveMQRabbitMQ,炙手可热的Kafka,阿里巴巴自主开发RocketMQ等。

不同MQ特点

  • ActiveMQ 方案成熟/性能缺陷(小公司)

    ActiveMQ、是Apache出品,最流行的,能力强劲的开源消息总线。它是一个完全支持JNS规范的的消息中间件。丰富的APT,多种集群架构模式让认kctivelo在业界成为老牌的消息中间件,在中小型企业颇受欢迎!

  • Kafka 性能强劲/一致性堪忧(大数据)

    Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务。

  • RocketMQ 没有缺点/未完全开源(大公司)

    RocketMQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。

  • RabbitMQ 最佳平替

    RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由〈包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。

RabbitMQ引言

基于 AMOP协议,erlang语言开发,是部署最广泛的开源消息中间件是最受欢迎的开源消息中间件之一。

AMQP协议

AMQP(advanced message queuing protocol)在2003年时被提出,最早用于解决金融领不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的
provider天然性就是跨平台的。以下是AMQP协议模型:

img

Centos7安装配置

官网:https://www.rabbitmq.com/

由于是Centos7所以只能下载带el7标识的

**注意:**RabbitMQ的版本和Erlang的版本是有关联的,需要在官网中去查阅

  1. 下载Erlang安装包

    erlang-23.3.4.11-1.el7.x86_64.rpm

  2. 下载RabbitMQ安装包

    rabbitmq-server-3.8.30-1.el7.noarch.rpm

  3. 进入云服务器中,将文件放到/home路径下

    image-20221026112842117

  4. 安装socat

    yum -y install socat
    
  5. 安装Erlang

    rpm -ivh erlang-23.3.4.11-1.el7.x86_64.rpm
    
  6. 输入erl查看是否安装成功

    image-20221026113214873

  7. 安装RabbitMQ

    rpm -ivh rabbitmq-server-3.8.30-1.el7.noarch.rpm
    
  8. 运行cd /etc/rabbitmq,编写配置文件

    如果想要使guest用户远程访问的话可以设置

    那么我们guest用户的账号和密码都是guest

    vi rabbitmq.config新建文件,写入如下

    [
    {rabbit, [{loopback_users, []}]}
    ].
    
  9. 启动网页管理插件

    rabbitmq-plugins enable rabbitmq_management
    

    image-20221026120141417

  10. 查看RabbitMQ运行状态

    systemctl status rabbitmq-server
    

    image-20221026120342913

  11. 启动RabbitMQ

    systemctl start rabbitmq-server
    

    启动完成后再查看一下状态

    image-20221026121629075

  12. 停止RabbitMQ

    如果要停止,可以使用以下命令

    systemctl stop rabbitmq-server
    
  13. 防火墙设置

    可以通过以下的命令进行查看防火墙状态

    systemctl status firewalld
    

    如果防火墙是开启的,你可以通过放行15672端口或者关闭防火墙

  14. 远程访问RabbitMq

    通过IP地址:15672访问

    用户名和密码如果配置了guest的话,那么就都是guest

    image-20221026122231361

添加新用户

使用代码添加

如果不想使用guest用户访问的话,可以通过添加新用户的方式来访问

  • 添加用户,前面是账号,后面是密码
rabbitmqctl add_user admin password
  • 给新用户分配操作权限
rabbitmqctl set_user_tags admin administrator
  • 给新用户分配资源权限,如果是赋予超级管理员的权限的话,下面的就不用再授权了
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
在web界面添加

image-20221026131542936

RabbitMQ的使用

引入依赖

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

第一种模型(直连)

开发生产者

package com.lzj;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;

import java.io.IOException;
import java.util.Calendar;
import java.util.concurrent.TimeoutException;

/**
 * <p>
 * 生产者
 * </p>
 *
 * @author:雷子杰
 * @date:2022/10/26
 */
public class Provider {

    @Test
    public void testSendMessage() throws IOException, TimeoutException {

        //创建连接mq的连接工厂对象
        ConnectionFactory connectionFactory = new ConnectionFactory();

        //设置连接rabbitmq主机
        connectionFactory.setHost("120.79.188.192");

        //设置端口号
        connectionFactory.setPort(5672);

        //设置连接哪个虚拟主机
        connectionFactory.setVirtualHost("/ems");

        //设置访问虚拟主机的用户名和密码
        connectionFactory.setUsername("LeiZijie");
        connectionFactory.setPassword("66323192");

        //获取连接对象
        Connection connection = connectionFactory.newConnection();
        //获取连接中通道
        Channel channel = connection.createChannel();
        //通道绑定对应消息队列
        //参数1:队列名称,不存在则自动创建
        //参数2:用来定义队列是否要持久化
        //参数3:是否独占队列
        //参数4:是否在消费完成后自动删除队列
        //参数5:额外附加参数
        channel.queueDeclare("hello",false,false,false,null);

        //发布消息
        //参数1:交换机名称
        //参数2:队列名称
        //参数3:传递消息额外配置
        //参数4:消息的具体内容
        //参数3可以设置成 MessageProperties.PERSISTENT_TEXT_PLAIN,那么参数就可以完成持久化
        channel.basicPublish("","hello",null,"hello rabitmq".getBytes());

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

    }
}

web中的结果

image-20221026141710047

开发消费者

注意,由于消费者获取是使用的多线程方式,所以不能在test方法中。

在消费者中,最后不要关闭通道和连接,否则可能会导致消息无法获取

package com.lzj;

import com.rabbitmq.client.*;
import org.junit.Test;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * <p>
 *
 * </p>
 *
 * @author:雷子杰
 * @date:2022/10/26
 */
public class Customer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建工厂连接
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("120.79.188.192");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/ems");
        connectionFactory.setUsername("LeiZijie");
        connectionFactory.setPassword("66323192");

        //创建连接对象
        Connection connection = connectionFactory.newConnection();

        //创建通道
        Channel channel = connection.createChannel();

        //通道绑定对象
        channel.queueDeclare("hello",false,false,false,null);

        //消费信息
        //参数1:消费那个队列的消息,队列名称
        //参数2:开始消息的自动确认机制
        //参数3:消费时的回调接口
        channel.basicConsume("hello",true,new DefaultConsumer(channel){
            //最后一个参数:消息队列中取出的消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("new String(body)="+new String(body));
            }

        });

        //channel.close();
        //connection.close();
    }
}

image-20221026143537119

第二种模型(work queues)

img

工作队列(又名:任务队列)背后的主要思想是避免立即执行资源密集型任务而不得不等待它完成。相反,我们将任务安排在以后完成。我们将任务封装 为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当您运行许多工作人员时,任务将在他们之间共享。

这个概念在 Web 应用程序中特别有用,在这些应用程序中,无法在短暂的 HTTP 请求窗口中处理复杂的任务。

角色:

  • P:生产者
  • C1:消费者1
  • C2:消费者2

生产者

package com.lzj.workqueues;

import com.lzj.utils.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;

/**
 * <p>
 *
 * </p>
 *
 * @author:雷子杰
 * @date:2022/10/26
 */
public class Provider {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare("work",true,false,false,null);

        for (int i = 0; i < 10; i++) {
            //生产消息
            channel.basicPublish("","work",null,(i+"hello work").getBytes());
        }


        RabbitMQUtils.closeConnection(channel,connection);
    }
}

消费者1

package com.lzj.workqueues;

import com.lzj.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * <p>
 *
 * </p>
 *
 * @author:雷子杰
 * @date:2022/10/26
 */
public class Customer1 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare("work",true,false,false,null);

        channel.basicConsume("work",true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者-1:"+new String(body));
            }
        });
    }
}

消费者2

package com.lzj.workqueues;

import com.lzj.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * <p>
 *
 * </p>
 *
 * @author:雷子杰
 * @date:2022/10/26
 */
public class Customer2 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare("work",true,false,false,null);

        channel.basicConsume("work",true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者-2:"+new String(body));
            }
        });
    }
}

能者多劳配置

将消费者中配置修改下即可

//每次只能消费一次
channel.basicQos(1);
channel.queueDeclare("work",true,false,false,null);

//参数二由true改为false
channel.basicConsume("work",false,new DefaultConsumer(channel){
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("消费者-1:"+new String(body));

        //手动确认消息
        channel.basicAck(envelope.getDeliveryTag(),false);//手动确认消息
    }
});

第三种模型(fanout)

fanout:扇出,也称为广播

img

在广播模式下,消息发送流程是这样的:

  • 可以有多个消费者
  • 每个消费者有自己的queue(队列)
  • 每个队列都要绑定到Exchange(交换机)
  • 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定
  • 交换机把消息发送给绑定过的所有队列
  • 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

生产者

package Fanout;

import Utils.MQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Provider {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接对象
        Connection connection = MQConnection.getConnection();
        // 获取通道对象
        Channel channel = connection.createChannel();
        // 将通道声明指定的交换机
        // 参数:交换机名称、交换机类型(fanout为广播类型)
        channel.exchangeDeclare("logs","fanout");
        // 发送消息
        // fanout中routingkey没意义
        channel.basicPublish("logs", "", null, ("fanout type message").getBytes());
        // 关闭资源
        MQConnection.closeChannelAndConnection(channel, connection);
    }
}

消费者(1,2,3)

由于消费者代码基本相同,只有输出的信息不同,所以这里就不写出来了

package Fanout;

import Utils.MQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer1 {
    public static void main(String[] args) throws IOException {
        // 获取连接对象
        Connection connection = MQConnection.getConnection();
        // 获取通道对象
        Channel channel = connection.createChannel();
        // 将通道声明指定的交换机
        // 参数:交换机名称、交换机类型(fanout为广播类型)
        channel.exchangeDeclare("logs","fanout");
        // 临时队列
        String queueName=channel.queueDeclare().getQueue();
        // 绑定交换机和队列
        channel.queueBind(queueName,"logs","");
        // 消费消息
        channel.basicConsume(queueName,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1:"+new String(body));
            }
        });

    }
}

第四种模型(Routing之订阅模型-Direct)

在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

img

在Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
  • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey。
  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息
  • P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
  • C1:消费者,其所在队列指定了需要routing key 为 error 的消息
  • C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息

生产者

package Direct;

import Utils.MQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Provider {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接对象
        Connection connection = MQConnection.getConnection();
        // 获取通道对象
        Channel channel = connection.createChannel();
        // 将通道声明指定的交换机
        // 参数:交换机名称(自己起)、交换机类型(direct为路由模式)
        channel.exchangeDeclare("logs_direct", "direct");
        
        // 发送消息
        String routingKey = "error";
        // fanout中routingkey没意义
        channel.basicPublish("logs_direct", routingKey, null,
                ("这是direct模型发布的基于routingkey:[" + routingKey + "] 发送的消息").getBytes());
        // 关闭资源
        MQConnection.closeChannelAndConnection(channel, connection);
    }
}

消费者1

package Direct;

import Utils.MQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer1 {
    public static void main(String[] args) throws IOException {
        // 获取连接对象
        Connection connection = MQConnection.getConnection();
        // 获取通道对象
        Channel channel = connection.createChannel();
        // 将通道声明指定的交换机
        // 参数:交换机名称、交换机类型
        channel.exchangeDeclare("logs_direct", "direct");
        // 临时队列
        String queueName = channel.queueDeclare().getQueue();
        
        // 基于routingKey绑定交换机和队列
        channel.queueBind(queueName, "logs_direct", "error");
        // 消费消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1:" + new String(body));
            }
        });

    }
}

消费者2

package Direct;

import Utils.MQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer2 {
    public static void main(String[] args) throws IOException {
        // 获取连接对象
        Connection connection = MQConnection.getConnection();
        // 获取通道对象
        Channel channel = connection.createChannel();
        // 将通道声明指定的交换机
        // 参数:交换机名称、交换机类型
        channel.exchangeDeclare("logs_direct", "direct");
        // 临时队列
        String queueName = channel.queueDeclare().getQueue();
        // 基于routingKey绑定交换机和队列
        channel.queueBind(queueName, "logs_direct", "error");
        channel.queueBind(queueName, "logs_direct", "info");
        channel.queueBind(queueName, "logs_direct", "warning");
        // 消费消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2:" + new String(body));
            }
        });

    }
}

测试结果

当生产者发送Route key为error的消息时

img img

当生产者发送Route key为info的消息时

img img

第五种模型(Routing之订阅模型-Topic)

Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key的时候使用通配符!这种模型Routingkey 一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

img

统配符:

  • *:匹配恰好1个单词
  • #:匹配一个或多个单词

如:

  • audit.#:匹配audit.irs.corporate或者audit.irs
  • audit.*:只能匹配audit.irs
  • *.audit.#:中间必须是audit,audit前有一个或多个单词,后有一个单词

生产者

package Topic;

import Utils.MQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Provider {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接对象
        Connection connection = MQConnection.getConnection();
        // 获取通道对象
        Channel channel = connection.createChannel();
        // 将通道声明指定的交换机
        // 参数:交换机名称(自己起)、交换机类型(topic为动态路由)
        channel.exchangeDeclare("topics", "topic");
        String routingKey = "user.save";
        // 发送消息
        // fanout中routingkey没意义
        channel.basicPublish("topics", routingKey, null,
                ("这是topic动态路由模型,routingkey:[" + routingKey + "]").getBytes());
        // 关闭资源
        MQConnection.closeChannelAndConnection(channel, connection);
    }
}

消费者(1,2)

消费者1和2的不同之处在于类名、第20行、第25行的名字

package Topic;

import Utils.MQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer1 {
    public static void main(String[] args) throws IOException {
        // 获取连接对象
        Connection connection = MQConnection.getConnection();
        // 获取通道对象
        Channel channel = connection.createChannel();
        // 将通道声明指定的交换机
        // 参数:交换机名称、交换机类型
        channel.exchangeDeclare("topics", "topic");
        // 临时队列
        String queueName = channel.queueDeclare().getQueue();
        // 基于通配符形式routingKey绑定交换机和队列
        // Consumer2设为user.#
        channel.queueBind(queueName, "topics", "user.*");
        // 消费消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1:" + new String(body));
            }
        });
    }
}

测试结果

img img

SpringBoot整合RabbitMQ

搭建环境

  1. 引入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
        <version>2.7.5</version>
    </dependency>
    
  2. 配置文件设置

    spring:
      rabbitmq:
        host: 120.79.188.192
        port: 5672
        username: LeiZijie
        password: 66323192
        virtual-host: /ems
    

第一种hello worl模型使用

生产者

package ecnu.cn;

import org.junit.Test;
import org.junit.runner.RunWith;
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;

@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
    // 注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    // hello world
    @Test
    public void test(){
        rabbitTemplate.convertAndSend("hello","hello world");
    }
}

消费者

package ecnu.cn.Hello;

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
// 指定监听hello队列
// 默认是持久化、非独占、不自动删除队列的
@RabbitListener(queuesToDeclare = @Queue(value = "hello", durable = "true", exclusive = "true", autoDelete = "true"))
public class Consumer {

    @RabbitHandler
    public void receive(String message) {
        System.out.println("message: " + message);
    }
}

第二种work模型使用

生产者

// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;

// work
@Test
public void testWork() {
    for (int i = 0; i < 10; i++) {
    	rabbitTemplate.convertAndSend("work", "work模型" + i);
    }
}

消费者

package ecnu.cn.Work;

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class WorkConsumer {

    // 第1个消费者
    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void receive1(String message) {
        System.out.println("message1: " + message);
    }

    // 第2个消费者
    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void receive2(String message) {
        System.out.println("message2: " + message);
    }
}

Fanout广播模型

生产者

// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;

// fanout 广播
@Test
public void testFanout(){
    // 注意参数多了个exchange
    rabbitTemplate.convertAndSend("logs", "", "Fanout模型发送的消息");
}

消费者

package ecnu.cn.Fanout;

import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class FanoutConsumer {

    @RabbitListener(bindings = @QueueBinding(value = @Queue, // 创建临时队列
            exchange = @Exchange(name = "logs", type = "fanout") // 绑定的交换机
    ))
    public void reveive1(String message) {
        System.out.println("message1: " + message);
    }

    @RabbitListener(bindings = @QueueBinding(value = @Queue, // 创建临时队列
            exchange = @Exchange(name = "logs", type = "fanout") // 绑定的交换机
    ))
    public void reveive2(String message) {
        System.out.println("message2: " + message);
    }
}

Route路由模型

生产者

// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;

// route 路由模式
@Test
public void testRoute(){
    rabbitTemplate.convertAndSend("directs","info","发送info的key的路由信息");
}

消费者

package ecnu.cn.Route;

import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class RouteConsumer {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, // 创建临时队列
            // name和value的效果一样
            exchange = @Exchange(value = "directs", type = "direct"), // 自定义交换机信息
            key = {"info","error"}
    ))
    public void receive1(String message) {
        System.out.println("message1: " + message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, // 创建临时队列
            // name和value的效果一样
            exchange = @Exchange(value = "directs", type = "direct"), // 自定义交换机信息
            key = "error"
    ))
    public void receive2(String message) {
        System.out.println("message2: " + message);
    }
}

Topic订阅模型(动态路由模型)

生产者

// 注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;

// topic 动态路由 订阅模式
@Test
public void testTopic(){
    rabbitTemplate.convertAndSend("topics","user.save","user.save 路由信息");
}

消费者

package ecnu.cn.Topic;

import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class TopicConsumer {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,
            exchange = @Exchange(value = "topics", type = "topic"),
            key = {"user.save", "user.*"}
    ))
    public void receive1(String message) {
        System.out.println("message1: " + message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,
            exchange = @Exchange(value = "topics", type = "topic"),
            key = {"order.#", "produce.#", "user.*"}
    ))
    public void receive2(String message) {
        System.out.println("message2: " + message);
    }
}

MQ的应用场景

异步处理

场景说明

用户注册后,需要发注册邮件和注册短信,传统的做法有两种 1.串行的方式 2.并行的方式

串行方式

将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西

img

并行方式

将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。

img

消息队列

假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并行已经提高的处理时间,但是,前面说过,邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,应该是写入数据库后就返回. 消息队列: 引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理

img

由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍

应用解耦

场景

双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口.

img

缺点

当库存系统出现故障时,订单就会失败。 订单系统和库存系统高耦合. 引入消息队列

img

订单系统

用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。

库存系统

订阅下单的消息,获取下单消息,进行库操作。 就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失

流量削峰

场景

秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。

作用

  • 可以控制活动人数,超过此一定阀值的订单直接丢弃(我为什么秒杀一次都没有成功过呢^^)
  • 可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)

img

注意

  • 用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面.
  • 秒杀业务根据消息队列中的请求信息,再做后续处理

RabbitMQ的集群

普通集群(副本集群)

All data/state required for the operation of a RabbitMQ broker is replicated across all nodes. An exception to this are message queues, which by default reside on one node, though they are visible and reachable from all nodes. To replicate queues across nodes in a cluster --摘自官网

默认情况下:RabbitMQ代理操作所需的所有数据/状态都将跨所有节点复制。这方面的一个例外是消息队列,默认情况下,消息队列位于一个节点上,尽管它们可以从所有节点看到和访问

架构图

img

核心解决问题: 当集群中某一时刻master节点宕机,可以对Queue中信息,进行备份

集群搭建

  • 集群规划

    node1: 10.15.0.3 mq1 master 主节点
    node2: 10.15.0.4 mq2 repl1 副本节点
    node3: 10.15.0.5 mq3 repl2 副本节点
    
  • 克隆三台机器主机名和ip映射

    vim /etc/hosts加入:
    10.15.0.3 mq1
    10.15.0.4 mq2
    10.15.0.5 mq3
    node1: vim /etc/hostname 加入: mq1
    node2: vim /etc/hostname 加入: mq2
    node3: vim /etc/hostname 加入: mq3
    
  • 三个机器安装rabbitmq,并同步cookie文件,在node1上执行:

    scp /var/lib/rabbitmq/.erlang.cookie root@mq2:/var/lib/rabbitmq/
    scp /var/lib/rabbitmq/.erlang.cookie root@mq3:/var/lib/rabbitmq/
    
  • 查看cookie是否一致:

    node1: cat /var/lib/rabbitmq/.erlang.cookie
    node2: cat /var/lib/rabbitmq/.erlang.cookie
    node3: cat /var/lib/rabbitmq/.erlang.cookie
    
  • 后台启动rabbitmq所有节点执行如下命令,启动成功访问管理界面:

    rabbitmq-server -detached
    
  • 在node2和node3执行加入集群命令:

    1.关闭 rabbitmqctl stop_app
    2.加入集群 rabbitmqctl join_cluster rabbit@mq1
    3.启动服务 rabbitmqctl start_app
    
  • 查看集群状态,任意节点执行:

    rabbitmqctl cluster_status
    
  • 如果出现如下显示,集群搭建成功:

    Cluster status of node rabbit@mq3 …
    [{nodes,[{disc,[rabbit@mq1,rabbit@mq2,rabbit@mq3]}]},
    {running_nodes,[rabbit@mq1,rabbit@mq2,rabbit@mq3]},
    {cluster_name,<<“rabbit@mq1”>>},
    {partitions,[]},
    {alarms,[{rabbit@mq1,[]},{rabbit@mq2,[]},{rabbit@mq3,[]}]}]
    
  • 登录管理界面,展示如下状态:

    img

  • 测试集群在node1上,创建队列

    img

  • 查看node2和node3节点:

    img

    img

  • 关闭node1节点,执行如下命令,查看node2和node3:

    rabbitmqctl stop_app

    img

    img

镜像集群

This guide covers mirroring (queue contents replication) of classic queues --摘自官网

By default, contents of a queue within a RabbitMQ cluster are located on a single node (the node on which the queue was declared). This is in contrast to exchanges and bindings, which can always be considered to be on all nodes. Queues can optionally be made mirrored across multiple nodes. --摘自官网

镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群的整体高可用性。

集群架构图

img

配置集群架构

  • 策略说明

    rabbitmqctl set_policy [-p <vhost>] [--priority <priority>] [--apply-to <apply-to>] <name> <pattern>  <definition>
    -p Vhost: 可选参数,针对指定vhost下的queue进行设置
    Name:     policy的名称
    Pattern: queue的匹配模式(正则表达式)
    Definition:镜像定义,包括三个部分ha-mode, ha-params, ha-sync-mode
             		ha-mode:指明镜像队列的模式,有效值为 all/exactly/nodes
                          all:表示在集群中所有的节点上进行镜像
                          exactly:表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
                          nodes:表示在指定的节点上进行镜像,节点名称通过ha-params指定
              	 ha-params:ha-mode模式需要用到的参数
                  ha-sync-mode:进行队列中消息的同步方式,有效值为automatic和manual
                  priority:可选参数,policy的优先级
    
    
  • 查看当前策略

    rabbitmqctl list_policies
    
  • 添加策略

    rabbitmqctl set_policy ha-all '^hello' '{"ha-mode":"all","ha-sync-mode":"automatic"}' 
    说明:策略正则表达式为 “^” 表示所有匹配所有队列名称  ^hello:匹配hello开头队列
    
    
  • 删除策略

    rabbitmqctl clear_policy ha-all
    
  • 测试集群

遇到的问题

centos7yum安装RabbitMQ之后没有rabbitmq.config配置文件

官网给出的:
在这里插入图片描述

翻译一下就是,安装之后不会自动给你创建配置文件,你可以在一下几个地址上自己创建。
官网给出的配置例子:rabbitmq.conf.example

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/73805.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL 原理篇】- 凭这个,我拿下字节面试

若是想查看原图&#xff0c;请点击这里 刘卡卡 | ProcessOn 超链接 索引 从存储结构上看&#xff0c;有哪些索引从存储结构上来划分&#xff1a;BTree索引&#xff08;B-Tree或BTree索引&#xff09;&#xff0c;Hash索引&#xff0c;full-index全文索引&#xff0c;R-Tree索…

[附源码]Python计算机毕业设计Django在线票务系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

JavaEE

文章目录前言JRE与JDK区别JAVASE、JAVAEE、JAVAME区别Dos常用命令为什么需要Path环境变量配置前言 &#x1f4cb;前言&#x1f4cb; &#x1f49d;博客&#xff1a;【无聊大侠hello word】&#x1f49d; ✍有一点思考&#xff0c;有一点想法&#xff0c;有一点理性&#xff01;…

关于有限元应力结果精度的几点讨论

1引言 作为一名汽车结构CAE工程师&#xff0c;使用有限元法进行应力分析是我多年的日常工作。但是关于有限元应力结果的一些技术点一直未能吃透&#xff0c;查到的相关文献也不多&#xff0c;文献内容也存在很多不尽不实之处。最近一段时间&#xff0c;抽空重新读了一遍王勖成…

泊松分布一

文章目录1. 泊松分布定义2.泊松分布具体实例实例1&#xff1a;实例2&#xff1a;3.生成泊松分布的代码泊松分布适合于描述单位间隔(时间、距离、面积、体积)内随机事件发生的次数的概率分布。如电话交换机接到呼叫的次数、汽车站台的候客人数、机器出现的故障数、自然灾害发生的…

【Android】用无障碍服务整个脚本——我看刑

本文灵感来源&#xff1a;李跳跳真实好友 app 目录无障碍服务(AccessibilityService)模拟点击组件ID & 组件文本坐标后台保活效果图存在缺陷缺陷一缺陷二缺陷三缺陷四无障碍服务(AccessibilityService) 无障碍服务(AccessibilityService)&#xff0c;是Google推出为了帮助…

从零搭建Sentry

前言 Sentry 为一套开源的应用监控和错误追踪的解决方案。这套解决方案由对应各种语言的 SDK 和一套庞大的数据后台服务组成。应用需要通过与之绑定的 token 接入 Sentry SDK 完成数据上报的配置。通过 Sentry SDK 的配置&#xff0c;还可以上报错误关联的版本信息、发布环境。…

四信机房环境监测方案上线 实现集中监控,统一管理

随着“东数西算”工程和新基建的加速落地&#xff0c;数智化技术正逐渐渗透到各领域。以机房行业为例&#xff0c;由于数据中心建设规模的逐步壮大&#xff0c;机房设备市场需求同步增长&#xff0c;为行业用户带来更多可能性。 伴随着机房中设备数量不断增多&#xff0c;如何…

Vue2实现浏览器ctrl+f功能

Vue2实现浏览器ctrlf功能 安装插件 使用一个Vue2的插件search-bar-vue2 npm install search-bar-vue2全局注册 //全局注册 import SearchBar from search-bar-vue2 Vue.use(SearchBar)局部注册 <template><div><search-bar :root"#app" :highlig…

【VUE】实现分页组件

&#x1f4d8;前言 &#x1f6a9;&#x1f6a9;&#x1f6a9; &#x1f48e;个人主页: 阿选不出来 &#x1f4a8;&#x1f4a8;&#x1f4a8; &#x1f48e;个人简介: 一名大二在校生,学习方向前端,不定时更新自己学习道路上的一些笔记. &#x1f4a8;&#x1f4a8;&#x1f4a…

Navigation--导航机理

1.ROS navigation为移动机器人导航相关包的集合&#xff0c;实现定位规划避障等相关功能。 整体工作流程为&#xff1a; 1.加载地图 navigation通过map_server加载现有地图。navigation无建图相关包&#xff0c;需另外实现后保存&#xff0c;默认只支持2维地图&#xff0c;其…

[附源码]Python计算机毕业设计Django游戏商城平台论文

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;我…

推荐测试用例管理工具,看这篇就行。

我们在考虑测试用例管理的时候&#xff0c;其实不能单纯考虑测试用管理&#xff0c;因为你的测试用例是需要和需求关联起来的&#xff0c;是需要和 bug 关联起来的。在有些行业&#xff0c;比如汽车、医药&#xff0c;不仅要对需求进行测试&#xff0c;还需要对架构设计、详细设…

某CCF C会议对国内和国外作者实行两套标准, 引27%+中稿者发声!

点击文末公众号卡片&#xff0c;不错过计算机会议投稿信息 本文主要反映轻松参会交流群内UIC22(CCF C类) 的81位国内中稿作者的诉求&#xff08;占UIC22中稿数的27%&#xff09;。目前群内作者们的诉求为&#xff0c;希望国内作者可选择线上参会。因为线下参会有困难&#xff0…

2023年湖北监理工程师报考时间是什么时候?

2023年湖北监理工程师报考时间是什么时候&#xff1f; 监理工程师考试时间虽说是全国统一的&#xff0c;但是监理工程师报名时间不统一&#xff0c;每个省份自行安排报名时间&#xff0c;监理工程师报名时间基本都是在2.3月份开始报名&#xff0c;具体关注每个省人事考试院网站…

文件包含漏洞(原理及介绍)

文件包含漏洞&#xff08;原理及介绍&#xff09; File inclusion&#xff0c;文件包含&#xff08;漏洞&#xff09;。程序开发人员通常出于灵活性的考虑&#xff0c;会将被包含的文件设置成变量&#xff0c;然后动态调用这些文件。但正是因为调用的灵活性导致用户可能调用一…

flex布局子项属性

flex布局子项属性 1、flex属性 源代码 flex属性定义子项目分配剩余空间&#xff0c;用flex来表示占多少份数 flex: number; 填数值&#xff0c;分配剩余空间的占比 2、align-self控制子项自己在侧轴上的排列方式 源代码 align-self属性允许单个项目有…

Semi-Supervised Classification with Graph Convolutional Networks

Semi-Supervised Classification with Graph Convolutional Networks, ICLR, 2017 要点&#xff1a; 1、可扩展的半监督学习方法 2、基于卷积神经网络的有效变体&#xff0c;直接对图进行操作 3、通过谱图卷积的局部一阶近似来激励卷积架构的选择 4、在图的边数上进行线性缩放…

车辆纵向动力学、加速性能和燃料消耗研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 车辆纵向动力学方面包括车辆传动系统换挡控制、制动系统的设计与控制以及车辆状态的参数估计;车辆横向动力学方面涉及车辆转向…

lua vm 共享 proto

lua vm共享proto 场景 在skynet中&#xff0c;对于每一个lua服务&#xff0c;实际上就是在snlua上启动了一个lua虚拟机去完成lua逻辑&#xff0c;所以skynet的服务是相互隔离的。 这样就会产生一个问题&#xff0c;多个服务都require同一个lua库&#xff0c;每个服务内都会有…