RabbitMq详解

1. 概述

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

1.1 AMQP 协议

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

2. 安装RabbitMQ

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

在这里插撒得入图片描述

因为RabbitMQ是用erlang语言写的,我们要下载erlang安装包,还有依赖,我已经下载好
百度网盘地址:链接:https://pan.baidu.com/s/1pn3dzzYLb6fqLkz6cuDrBA
提取码:aaaa
在这里插入图片描述
上传至服务器或者虚拟机即可

# 安装erlang依赖包
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
# 安装RabbitMQ
yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm
# 将刚安装好的mq配置文件复制到/etc/rabbitmq下并改名
cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
# 因为我们想看mq的管理页面默认是只有本机可以访问,想开启远程进入需要修改配置文件,打开配置文件 
vim /etc/rabbitmq/rabbitmq.config

修改配置文件,把注释去掉即可,注意后面还有一个逗号也要去掉
在这里插入图片描述

#想看到管理界面,还需要启动一个插件
rabbitmq-plugins enable rabbitmq_management
#启动/重启/停止/查看 RabbitMQ的服务 
	systemctl start rabbitmq-server
	systemctl restart rabbitmq-server
	systemctl stop rabbitmq-server
	systemctl status rabbitmq-server
#将防火墙从开机自启移除
	systemctl disable firewalld
#将防火墙关闭
	systemctl stop firewalld 

访问mq管理页面,ip是你服务器的地址
地址: http://IP:15672/ 用户名密码都是guest
下面是登陆之后的样子在这里插入图片描述

页面菜单的解释

  • connections:无论生产者还是消费者,都需要与RabbitMQ建立连接后才可以完成消息的生产和消费,在这里可以查看连接情况
  • channels:通道,建立连接后,会形成通道,消息的投递获取依赖通道。
  • Exchanges:交换机,用来实现消息的路由
  • Queues:队列,即消息队列,消息存放在队列中,等待消费,消费后被移除队列。

2.1创建用户

在这里插入图片描述
角色的描述

  • 超级管理员(administrator)
    可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
  • 监控者(monitoring)
    可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
  • 策略制定者(policymaker)
    可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。
  • 普通管理者(management)
    仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
  • 其他
    无法登陆管理控制台,通常就是普通的生产者和消费者。

创建虚拟主机

虚拟主机
为了让各个用户可以互不干扰的工作,RabbitMQ添加了虚拟主机(Virtual Hosts)的概念。其实就是一个独立的访问路径,不同用户使用不同路径,各自有自己的队列、交换机,互相不会影响。
在这里插入图片描述

用户和虚拟主机绑定 ,点击我们刚创建好的用户
在这里插入图片描述
在这里插入图片描述

3. 开始写代码

RabbitMQ支持的消息模型
在这里插入图片描述
引入依赖

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

3.1 任务模型

当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。也就是一个生产者对应多个消费者
废话不多说,上才艺,货哈,易购窝里giao!giao!

//因为我们生产者消费者都要与mq创建连接,提取一个工具类
//连接工具类
public class MqUtil {
    static Connection connection = null;
    static ConnectionFactory connectionFactory =new ConnectionFactory();
    static {
        new ConnectionFactory();
        connectionFactory.setHost("1.116.221.12");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("test1");
        connectionFactory.setPassword("test1");
        //虚拟主机
        connectionFactory.setVirtualHost("/msg");

    }

    public static  Connection getConnection(){
        try {
            connection = connectionFactory.newConnection();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        return connection;
    }
//关流方法
    public static void closeMq(Connection connection,Channel channel) throws IOException, TimeoutException {
        channel.close();
        connection.close();
    }
}
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = MqUtil.getConnection();
        
        //获取完链接,创建通道
        Channel channel = connection.createChannel();
        //通道连接队列
        //1队列名(如果没有则创建)  2队列是否持久化  3是否独占队列  4是否自动删除  5其他属性
        channel.queueDeclare("duilie",true,false,false,null);
        //往里面放信息
        //1.交换机  2队列名  3其他参数  4要发送的信息
        for (int i = 0; i <30 ; i++) {
            channel.basicPublish("","duilie", MessageProperties.PERSISTENT_TEXT_PLAIN,("我是信息"+i).getBytes());
        }

        
        MqUtil.closeMq(connection,channel);
    }
}

参数解释

  • channel.queueDeclare("duilie",true,false,false,null);
    这个方法我的理解就是通道去创建队列 ,确保发送消息时没问题,如果有这个队列名,参数不一致的话会报错,所以如果运行过一次之后,改了参数,在运行,队列名字不改,会报错
    第一个参数:队列名
    第二个参数:队列是否持久化,为false就是,如果重启mq,该队列会被删除
    第三个参数:是否独占队列,如果为true,我试了一下,我的理解就是,只允许一个消费者消费
    第四个参数:如果为true当数据消费完并且所有消费者断开连接则删除队列
    第五个参数:其他参数,一般为空,我没有去研究
  • channel.basicPublish("","duilie", MessageProperties.PERSISTENT_TEXT_PLAIN,("我是信息"+i).getBytes());
    发送消息
    第一个参数: 交换机,目前这个模型使用默认交换机
    第二个参数:队列名
    第三个参数:如果填写null,重启mq没有消费的信息将会丢失,消息是否持久化的意思
    第四个参数:要发送的消息,这里规定要传字节数组

消费者一条一条的消费,如果消费完成,会确认一下
我们模拟两个消费者消费,消费者1执行的比较慢,消费者2执行的比较快

public class Consumer1 {
    public static void main(String[] args) throws IOException {
        Connection connection = MqUtil.getConnection();

        //获取完链接,创建通道
        Channel channel = connection.createChannel();
        //指该消费者在接收到队列里的消息但没有返回确认结果之前,队列不会将新的消息分发给该消费者。队列中没有被消费的消息不会被删除,还是存在于队列中
        channel.basicQos(1);
        //1队列名(如果没有则创建)  2是否持久化  3是否独占队列  4是否自动删除  5其他属性
        channel.queueDeclare("duilie",false,false,false,null);
        /* 启动一个消费者,并返回服务端生成的消费者标识
        * queue:队列名
        * autoAck:true 接收到传递过来的消息后acknowledged(应答服务器),false 接收到消息后不应答服务器
        * callback: 消费者对象的回调接口
        * @return 服务端生成的消费者标识
        */
        channel.basicConsume("duilie",false,new DefaultConsumer(channel){
            @SneakyThrows
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //让消费者执行的慢一点
                Thread.sleep(2000);
                System.out.println(new String(body));
                //手动确认消息    手动确认消息的标识,是否一次确认多条
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
        //消费者不要关闭流,需要一直监听
    }
}
public class Consumer2 {
    public static void main(String[] args) throws IOException {
        Connection connection = MqUtil.getConnection();

        //获取完链接,创建通道
        Channel channel = connection.createChannel();
        channel.basicQos(1);
        //1队列名(如果没有则创建)  2是否持久化  3是否独占队列  4是否自动删除  5其他属性
        channel.queueDeclare("duilie",false,false,false,null);
        /* 启动一个消费者,并返回服务端生成的消费者标识
        * queue:队列名
        * autoAck:true 接收到传递过来的消息后acknowledged(应答服务器),false 接收到消息后不应答服务器
        * callback: 消费者对象的回调接口
        * @return 服务端生成的消费者标识
        */
        channel.basicConsume("duilie",false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
    }
}

运行生产者,看下管理界面

在这里插入图片描述
运行我们的消费者,先运行消费者1,再运行消费者2,打开管理界面

在这里插入图片描述
发现所有消息已经被消费,看我们的控制台
在这里插入图片描述
可以看到,由于消费者1消费的比较慢,消费者1就消费的少一点,能者多劳嘛

上面这种模型,每一个消息只会被一个消费者执行,但是我想一个消息多个消费者执行呢,就用到了下面这种模型

3.2 广播模型

在这里插入图片描述
广播流程

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

创建连接获取通道,关流,代码重复,不再展示

		//通道绑定交换机    1.交换机的名称(没有则创建) 2.类型 fanout代表是广播
        channel.exchangeDeclare("jiaohuanji","fanout");
        //把消息发送到交换机      1.交换机的名称,2.路由key(目前没有用到),3.其他参数,4.发送的消息
        channel.basicPublish("jiaohuanji","",null,"我是消息".getBytes());
//绑定交换机
        channel.exchangeDeclare("jiaohuanji","fanout");
        //创建临时队列,广播不需要搞一个一直持久化的队列浪费资源
        //获取临时队列的名字
        String queueName = channel.queueDeclare().getQueue();
        System.out.println("交换机的名称"+queueName);
        //将临时队列绑定交换机    第三个参数:路由key(这里用不到)
        channel.queueBind(queueName,"jiaohuanji","");
        //处理消息
        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(new String(body));
            }
        });

创建两个消费者,可以再管理界面看到两个临时队列
在这里插入图片描述

两个消费者代码一模一样,先运行消费者,在运行生产者,可以看到控制台
在这里插入图片描述
两个同时消费到,我们把消费者停掉,去管理界面发现,该通道也被删除
在这里插入图片描述
可以看到这种模型,每个消费者,同一条消息每个消费者都可以消费,我现在又有一个需求,根据消息的不同我想把消息,放入多个队列或者某一个队列,这个时候就需要用到我们的路由key了

3.3 Routing(直连)模型

流程

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个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 的消息

我们现在有个,这样的需求,我error和info类型的日志,都要消费者1处理,消费者2只消费error类型的消息

		String exchangeName="logs";
        //1.交换机的名字,2.交换机类型(direct)
        channel.exchangeDeclare(exchangeName,"direct");
        //路由key
        String routingKey="info";
        //发布消息
        channel.basicPublish(exchangeName,routingKey,null,("我是"+routingKey+"类型的消息").getBytes());
		//这个消费者可以拿到"error"和"info"的信息
        String exchangeName="logs";
        Channel channel = connection.createChannel();
        //创建临时队列
        String queue = channel.queueDeclare().getQueue();
        //绑定队列和交换机
        channel.queueBind(queue,exchangeName,"error");
        channel.queueBind(queue,exchangeName,"info");
        //消费消息
        channel.basicConsume(queue,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));
            }
        });
        String exchangeName="logs";
        String queue = channel.queueDeclare().getQueue();
        //这个消费者只消费error的消息
        channel.queueBind(queue,exchangeName,"error");
        channel.basicConsume(queue,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));
            }
        });

先运行一下生产者,把交换机创建好,在运行两个消费者,在执行生产者,发送error类型的消息,再执行生产者发送info类型的消息,结果:
在这里插入图片描述
可以看到,消费者1消费了路由key为error和info的,而消费者2消费了路由key为error的

3.3 Routing(订阅)模型

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

统配符
* (star) can substitute for exactly one word. 匹配不多不少恰好1个词
# (hash) can substitute for zero or more words. 匹配一个或多个词
如:
audit.# 匹配audit.irs.corporate或者 audit.irs 等
audit.* 只能匹配 audit.irs

这种和上面那种相比较只是,把交换机的类型直连(direct) 换成了订阅(topic),还有消费者绑定路由key那里可以使用通配符
        //生产者代码
        //1.交换机的名字,2.交换机类型(topic)
        channel.exchangeDeclare("topics","topic");
        //路由key
        String routingKey="user.save";
		//消费者代码
        //绑定队列和交换机
        channel.queueBind(queue,exchangeName,"user.*");

4. SpringBoot中开发

导入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置文件

spring:
  application:
    name: springboot_rabbitmq
  rabbitmq:
    host: 10.15.0.9
    port: 5672
    username: ems
    password: 123
    virtual-host: /ems

boot里集成了RabbitM使用非常简单
新建一个boot项目,在测试类中,写入消息

4.1 工作模型

在这里插入图片描述

@SpringBootTest
class DemoApplicationTests {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void testHello(){
        for (int i = 0; i <100 ; i++) {
            //参数1:队列名,参数二:发送的内容
            rabbitTemplate.convertAndSend("hello","第+"+i+"只小阿giao");
        }
    }
}

写一个消息监听方法

@Component
public class Consumer {
    //监听的队列
    @RabbitListener(queuesToDeclare = @Queue("hello"))
    public void receive1(String message){
        System.out.println("消费者1 = " + message);
    }
    @RabbitListener(queuesToDeclare = @Queue("hello"))
    public void receive2(String message){
        System.out.println("消费者2 = " + message);
    }
}

运行测试方法,结果:
在这里插入图片描述
可以看到是,轮询机制,每个消费者消费一条,谁也不多不少

4.2 广播模型

@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testFanout() throws InterruptedException {
	//1:交换机,2:路由key,3:发送的消息
  rabbitTemplate.convertAndSend("logs","","这是日志广播");
}
@Component
//两个消费者
public class FanoutCustomer {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,
            exchange = @Exchange(name="logs",type = "fanout")
    ))
    public void receive1(String message){
        System.out.println("message1 = " + message);
    }

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

4.3 路由模型

	@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic 
	void testDirect(){  
		rabbitTemplate.convertAndSend("directs","error","error 的日志信息");
	}
@Component
public class DirectCustomer {

    @RabbitListener(bindings ={
            @QueueBinding(
                    value = @Queue(),
                    key={"info","error"},
                    exchange = @Exchange(type = "direct",name="directs")
            )})
    public void receive1(String message){
        System.out.println("message1 = " + message);
    }

    @RabbitListener(bindings ={
            @QueueBinding(
                    value = @Queue(),
                    key={"error"},
                    exchange = @Exchange(type = "direct",name="directs")
            )})
    public void receive2(String message){
        System.out.println("message2 = " + message);
    }
}

4.4 动态路由

@Autowired
private RabbitTemplate rabbitTemplate;

@Test
public void testTopic(){
  rabbitTemplate.convertAndSend("topics","user.save.findAll","user.save.findAll 的消息");
}
@Component
public class TopCustomer {
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    key = {"user.*"},
                    exchange = @Exchange(type = "topic",name = "topics")
            )
    })
    public void receive1(String message){
        System.out.println("message1 = " + message);
    }

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

5.0Mq集群

用java连接和我们普通的一样,搭建的话,等我下篇文章!奥里给!
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值