RabbitMQ2/7--RabbitMQ支持的消息模型

RabbitMQ支持的消息模型

在这里插入图片描述
在这里插入图片描述


第一种模型(直连)

概念

生产者和消费者之间是一对一的关系,通过队列进行消息的传输

结构图

在这里插入图片描述

  • P:生产者,也就是要发送消息的程序
  • C:消费者:消息的接受者,会一直等待消息到来。
  • queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。

代码演示之前,先创建新用户并指定虚拟主机!!

创建虚拟主机(必须以 / 开头)

在这里插入图片描述

创建新用户

在这里插入图片描述

用户指定虚拟机

在这里插入图片描述

选择要指定的虚拟主机,并设置

设置成功,返回查看
在这里插入图片描述

代码

引入依赖

<!--引入rabbitmq的相关依赖-->
<dependency>
  <groupId>com.rabbitmq</groupId>
  <artifactId>amqp-client</artifactId>
  <version>5.7.2</version>
</dependency>

生产者代码

package helloword;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Provider {

    //生产消息
    @Test
    public void testSendMessage() throws IOException, TimeoutException {

        //创建连接mq的连接工厂对象
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接rabbitmq主机
        connectionFactory.setHost("192.168.139.129");
        //设置端口号
        connectionFactory.setPort(5672);
        //设置连接那个虚拟主机
        connectionFactory.setVirtualHost("/ems");
        //设置访问虚拟主机的用户名和密码
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("ems");

        //获取连接对象
       Connection connection = connectionFactory.newConnection();
       //获取连接中通道
       Channel channel = connection.createChannel();
       //通道绑定对应消息队列
       //参数1:  队列名称 如果队列不存在自动创建
       //参数2:  用来定义队列特性是否要持久化 true 持久化队列   false 不持久化
       //参数3:  exclusive 是否独占队列  true 独占队列   false  不独占
       //参数4:  autoDelete: 是否在消费完成后自动删除队列  true 自动删除  false 不自动删除
       //参数5:  额外附加参数
        channel.queueDeclare("hello",true,false,false,null);
//        //发布消息
//        //参数1: 交换机名称 参数2:队列名称  参数3:传递消息额外设置  参数4:消息的具体内容
        channel.basicPublish("","hello", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello rabbitmq".getBytes());

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

运行结果

在这里插入图片描述

消息自动刷新

在这里插入图片描述

消费者

消费之前的消息队列信息
在这里插入图片描述

package helloword;

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

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

public class Customer {

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //连接主机
        connectionFactory.setHost("192.168.139.129");
        //连接端口号
        connectionFactory.setPort(5672);
        //虚拟主机
        connectionFactory.setVirtualHost("/ems");
        //用户名
        connectionFactory.setUsername("ems");
        //密码
        connectionFactory.setPassword("ems");

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

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

        //通道绑定对象
        channel.queueDeclare("hello",true,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));
            }
        });
        //---通道和连接不关闭将一直监听消息队列
//        //关闭通道
//        channel.close();
//        //关闭连接
//        connection.close();
    }
}

消费之后
在这里插入图片描述

Tips

junit测试过程中,不支持多线程模型,如果以test方式运行,不支持监听,还未回调,主线程已经结束了,所以消费者要放在main函数中执行!!!

工程优化–工具类封装(连接对象封装成工具类)

工具类封装-RabbitMQUtils

package utils;


import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.Properties;

public class RabbitMQUtils {

    private static ConnectionFactory connectionFactory;
    private static Properties properties;
    static{
        //重量级资源  类加载执行之执行一次
        connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.139.129");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/ems");
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("ems");
    }

    //定义提供连接对象的方法
    public static Connection getConnection() {
        try {
            return connectionFactory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    //关闭通道和关闭连接工具方法
    public static void closeConnectionAndChanel(Channel channel, Connection conn) {
        try {
            //空指针判断
            if(channel!=null) channel.close();
            if(conn!=null)   conn.close();
        } catch (Exception e) {
            e.printStackTrace();

        }
    }

}

生产者引用工具类

package helloword;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import org.junit.Test;
import utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Provider {

    //生产消息
    @Test
    public void testSendMessage() throws IOException, TimeoutException {
//        //通过工具类获取连接对象
        Connection connection = RabbitMQUtils.getConnection();
//
       //获取连接中通道
       Channel channel = connection.createChannel();
//
//        //通道绑定对应消息队列
//        //参数1:  队列名称 如果队列不存在自动创建
//        //参数2:  用来定义队列特性是否要持久化 true 持久化队列   false 不持久化
//        //参数3:  exclusive 是否独占队列  true 独占队列   false  不独占
//        //参数4:  autoDelete: 是否在消费完成后自动删除队列  true 自动删除  false 不自动删除
//        //参数5:  额外附加参数
        channel.queueDeclare("hello",true,false,false,null);
//
//        //发布消息
//
//        //参数1: 交换机名称 参数2:队列名称  参数3:传递消息额外设置  参数4:消息的具体内容
        channel.basicPublish("","hello", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello rabbitmq".getBytes());
//        //调用工具类
        RabbitMQUtils.closeConnectionAndChanel(channel,connection);
    }
}

消费者引入工具类

package helloword;

import com.rabbitmq.client.*;
import utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Customer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //通过工具类获取连接
        Connection connection = RabbitMQUtils.getConnection();

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

        //通道绑定对象
        channel.queueDeclare("hello",true,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));
            }
        });
    }
}

设置消息队列持久化和消息持久化

在这里插入图片描述
说明:

  1. 队列持久化:b,设置为false,服务宕机重启后,队列消失,设置为true,队列持久化
  2. 消息持久化:MessageProperties,设置为PERSISTENT_TEXT_PLAIN,服务重启后,消息还在队列中,持久化存储,直到被消费掉
  3. 生产者和消费者的消息队列信息要保持一致
    在这里插入图片描述

总结

第一种模型(直连)helloworld的这种模型,适用于登录,短信发送这种场景,一对一,一个生产者对应一个消费者,每次只生产一条消息,消费者不中断的监听消息队列,只要队列中有消息,就进行消费

缺点
消息堆积,生产者不断生产消息到消息队列,消费者消费的速度跟不上生产的速度,会产生消息堆积


第二种模型(工作队列)

概念

Work queues,也被称为(Task queues),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。

结构

在这里插入图片描述

  • P:生产者:任务的发布者
  • C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
  • C2:消费者-2:领取任务并完成任务,假设完成速度快

代码演示

生产者

package workquene;

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

import java.io.IOException;

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 = 1; i <=20; i++) {
            //生产消息
            channel.basicPublish("", "work", null, (i + "hello work quene").getBytes());
        }

        //关闭资源
        RabbitMQUtils.closeConnectionAndChanel(channel, connection);

    }
}

消费者-1

package workquene;

import com.rabbitmq.client.*;
import utils.RabbitMQUtils;
import java.io.IOException;

public class Customer1 {
    public static void main(String[] args) throws IOException {
        //调用工具类获取连接
        Connection connection = RabbitMQUtils.getConnection();
        final Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        //参数1:队列名称  参数2:消息自动确认 true  消费者自动向rabbitmq确认消息消费  false 不会自动确认消息
        channel.basicConsume("work",false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try{
                    //休眠2秒,测试是否平均消费
                    Thread.sleep(2000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("消费者-1: "+new String(body));
            }
        });
    }
}

消费者-2

package workquene;

import com.rabbitmq.client.*;
import utils.RabbitMQUtils;
import java.io.IOException;

public class Customer2 {
    public static void main(String[] args) throws IOException {
        //调用工具类,获取连接
        Connection connection = RabbitMQUtils.getConnection();
        final Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        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("消费者-2: "+new String(body));
            }
        });
    }
}

运行结果

消费前

在这里插入图片描述

消费后

在这里插入图片描述

消费者-1

在这里插入图片描述

消费者-2

在这里插入图片描述

存再的问题-循环

消费者-1,和消费者-2平均消费消息队列中的消息,这种经过,经过多次测试,加上线程休眠,结果还是平均消费,官方称为循环

默认循环机制存在的缺点

一个消费的快,另一个消费的慢,依旧会存在消息堆积的现象

优化–消息确认机制(能者多劳模式)

如何开启消息确认
在消费者端channel.basicConsume()方法的第二个参数设置为true
在这里插入图片描述

消息自动确认带来的问题

如果两个消费者平均分配消息,假如一个消费已经从队列中拿到了5个消息,在消费到第3个消息的时候宕机了,后面的消息将会丢失
在这里插入图片描述

消息确认机制的优化

1.关闭消息确认机制,设置第二个参数为false
2.设置每次只能消费一个消息
3.设置消息消费完毕后进行手动确认

优化后的代码(消费者端)

在这里插入图片描述

结果

能者多劳,保证不会丢失消息


第三种模型(广播模型)

概念

工作队列的变体,生产者负责把消息放入交换机中,由交换机负责分发消息到队列中

结构图

在这里插入图片描述

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

默认的交换机类型

在这里插入图片描述

代码演示

生产者

package fanout;

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

import java.io.IOException;

public class Provider {

    public static void main(String[] args) throws IOException {
        //获取连接对象
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        //将通道声明指定交换机   //参数1: 交换机名称    参数2: 交换机类型  fanout 广播类型
        channel.exchangeDeclare("logs","fanout");

        //发送消息
        参数1:交换机名字
        参数2:路由key
        参数3:消息持久化特性
        参数4:消息内容
        channel.basicPublish("logs","",null,"fanout type message".getBytes());

        //释放资源
        RabbitMQUtils.closeConnectionAndChanel(channel,connection);
    }
}

生产者指定的交换机不存在,运行后则创建一个交换机
在这里插入图片描述

消费者(三个消费者,代码同)

package fanout;

import com.rabbitmq.client.*;
import utils.RabbitMQUtils;
import java.io.IOException;

public class Customer1 {
    public static void main(String[] args) throws IOException {
        //获取连接对象
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        //通道绑定交换机
        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。

在Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
  • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey。
  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息

简言之,就是生产者与消费者之间通过路由key进行绑定,路由key匹配成功才能接受消息,不成功的接收不到消息
在这里插入图片描述

结构

在这里插入图片描述

  • P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
  • C1:消费者,其所在队列指定了需要routing key 为 error 的消息
  • C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消

代码演示

生产者代码

package direct;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import utils.RabbitMQUtils;
import java.io.IOException;

public class Provider {
    public static void main(String[] args) throws IOException {
        //获取连接对象
        Connection connection = RabbitMQUtils.getConnection();
        //获取连接通道对象
        Channel channel = connection.createChannel();
        String exchangeName = "logs_direct";
        //通过通道声明交换机  参数1:交换机名称  参数2:direct  路由模式
        channel.exchangeDeclare(exchangeName,"direct");
        //发送消息
        String routingkey = "warning";
        channel.basicPublish(exchangeName,routingkey,null,("这是direct模型发布的基于route key: ["+routingkey+"] 发送的消息").getBytes());
        //关闭资源
        RabbitMQUtils.closeConnectionAndChanel(channel,connection);
    }
}

消费者1

package direct;

import com.rabbitmq.client.*;
import utils.RabbitMQUtils;
import java.io.IOException;

public class Customer1 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        String exchangeName = "logs_direct";

        //通道声明交换机以及交换的类型
        channel.exchangeDeclare(exchangeName,"direct");

        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();

        //基于route key绑定队列和交换机和接受的路由key
        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("消费者1: "+ new String(body));
            }
        });

    }
}

消费者2

package direct;

import com.rabbitmq.client.*;
import utils.RabbitMQUtils;
import java.io.IOException;

public class Customer2 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        String exchangeName = "logs_direct";

        //声明交换机 以及交换机类型 direct
        channel.exchangeDeclare(exchangeName,"direct");

        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();

        //临时队列和交换机绑定,路由key的绑定
        channel.queueBind(queue,exchangeName,"info");
        channel.queueBind(queue,exchangeName,"error");
        channel.queueBind(queue,exchangeName,"warning");
        
        //消费消息
        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));
            }
        });
    }
}

运行结果

消费者1和消费者2绑定的路由key不同,能接收的消息也就不同
在这里插入图片描述


第五种模型(通配符)

Routing 之订阅模型-Topic

Topic类型的Exchange与Direct相比,都是可以根据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

在这里插入图片描述

代码演示

生产者

package topic;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import utils.RabbitMQUtils;
import java.io.IOException;

public class Provider {
    public static void main(String[] args) throws IOException {
        //获取连接对象
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        //声明交换机以及交换机类型 topic
        channel.exchangeDeclare("topics","topic");

        //发布消息,路由key
        String routekey = "save.user.delete";

        channel.basicPublish("topics",routekey,null,("这里是topic动态路由模型,routekey: ["+routekey+"]").getBytes());

        //关闭资源
        RabbitMQUtils.closeConnectionAndChanel(channel,connection);

    }
}

消费者1

package topic;

import com.rabbitmq.client.*;
import utils.RabbitMQUtils;
import java.io.IOException;

public class Customer1 {
    public static void main(String[] args) throws IOException {

        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        //声明交换机以及交换机类型
        channel.exchangeDeclare("topics","topic");
        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //绑定队列和交换机  动态统配符形式route key
        channel.queueBind(queue,"topics","*.user.*");

        //消费消息
        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));
            }
        });
    }
}

消费者2

package topic;

import com.rabbitmq.client.*;
import utils.RabbitMQUtils;
import java.io.IOException;

public class Customer2 {
    public static void main(String[] args) throws IOException {

        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        //声明交换机以及交换机类型
        channel.exchangeDeclare("topics","topic");
        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //绑定队列和交换机  动态统配符形式route key
        channel.queueBind(queue,"topics","*.user.#");

        //消费消息
        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));
            }
        });
    }
}

运行结果

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值