RabbitMQ的五种常用消息模型

目录

五种消息模型

直连模型

work模型

订阅模型-Fanout(广播)

订阅模型-Direct(路由)

订阅模型-Topic(通配符)

持久化

消息确认机制(ACK)


 

五种消息模型

 

RabbitMQ提供了7种消息模型。其中3、4、5这三种都属于订阅模型,只不过进行路由的方式不同。67这里暂时不表。

总结:直连模式1v1,work模式抢红包,广播模式公众号,路由模式小团体,Topic模式土豪版小团体

RabbitMQ的五种常用消息模型_第1张图片

 

 

直连模型

 

原理

P(producer/ publisher):生产者,发送消息到消息队列

C(consumer):消费者从消息队列中取出消息进行消费

Queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。

总之:生产者将消息发送到队列,消费者从队列中获取消息,队列是存储消息的缓冲区注意,当消费者从队列中消费消息后,这条消息是否还存在队列中,以及这个队列是否存在,都需要你自定义进行设置,在“持久化”这一节里面有讲。

 

举例

producer

新建一个ems用户,新建一个/ems虚拟主机,让ems用户与ems虚拟主机进行绑定

RabbitMQ的五种常用消息模型_第2张图片

新建一个maven父子项目,父项目空即可,子项目中导入rabbitmq和junit依赖

        
            com.rabbitmq
            amqp-client
            5.7.2
        
        
            junit
            junit
            4.11
        

我们先新建一个工具类,用来创建连接对象,这里用到了单例模式!


public class RabbitMQUtils {
    private static ConnectionFactory connectionFactory;

    static {
        connectionFactory = new ConnectionFactory();
        //我们把重量级资源通过单例模式加载
        connectionFactory.setHost("192.168.62.130");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("ems");
        connectionFactory.setVirtualHost("/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();
        }
    }
}

在HelloWorld(模式一官方命名就叫helloworld)包下新建Producer.java

public class Provider {
    //生产消息
    @Test
    public void testSendMessage() throws IOException, TimeoutException{
        //通过工具类获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //创建通道
        Channel channel = connection.createChannel();
        //参数1:是否持久化,参数2:是否独占队列 参数3:是否自动删除 参数4:其他属性
        channel.queueDeclare("hello",true,false,false,null);
        channel.basicPublish("","hello",null,"hello rabbitmq".getBytes());
        RabbitMQUtils.closeConnectionAndChanel(channel,connection);
    }
}

查看消息是否发送到队列上 

RabbitMQ的五种常用消息模型_第3张图片

consumer

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

        //通过工具类获取连接
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare("hello",true,false,false,null);
        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));
            }
        });
    }
}

消费者去队列中把消息消费掉。

RabbitMQ的五种常用消息模型_第4张图片

传递消息时可以对参数进行设置

RabbitMQ的五种常用消息模型_第5张图片

 

 

work模型

 

原理

 RabbitMQ的五种常用消息模型_第6张图片

我们可以让多个消费者监听同一队列。消费者接收到消息后, 通过线程池异步消费。但是一个消息只能被一个消费者获取

类似于抢红包,workqueue常用于避免消息堆积问题。

 

举例

public class Consumer1 {
    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));
            }
        });
    }
}
public class Consumer2 {
    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));
            }
        });
    }
}

先启动消费者

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 queue").getBytes());
        }

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

再启动生产者

我们发现消费者是按照轮询消费的,但这种消费存在一个问题,假如Consumer1处理能力极快,Consumer2处理能力极慢,这是Consumer2会严重拖累整体消费进度,而Consuemr1又早早的完成任务而无所事事。

RabbitMQ的五种常用消息模型_第7张图片

可不可以让“能者多劳”呢?

 

能者多劳

首先要明白一个核心问题,默认情况下,10个消息2个消费者,消息队列是直接把5个消息给C1,5个消费给C2的,这是不可能做到能者多劳的,因为每个消费者的消息永远被C1或C2独占

我们需要先在consumer中设置autoAck:参数为false,意思是关闭自动确认消息,也就是说即使这个消费者把消息消费完了,也不会告诉消息队列自己当前没有任务了,也就意味着我们先让消费者永远处于“工作状态” ,除非手动确认消息消费完成

RabbitMQ的五种常用消息模型_第8张图片

设置basicQos方法参数为1,意思是消费者每次只能从通道中哪一个消息,

RabbitMQ的五种常用消息模型_第9张图片

思考下,我们刚才改了两处,现在是不是让2个消费者用户处于工作状态了?并且一次只能拿一个消息消费。把这两个意思拼起来就是说1个工人即使干完了自己的任务,也不能下班,他要不停的去消息队列中拿剩下的任务进行消费,这既是“能者多劳也是压榨呀~”

最后在程序执行之前,思考下。我们这么搞,达到了能者多劳的效果,但是消息队列中的消息是不是永远都没有被确认啊,怎么解决?我们在最后加一行代码,进行手动确认。意思是消费完一条消息,就确认一条消息。至此我们才算完美的解决了能者多劳的问题

RabbitMQ的五种常用消息模型_第10张图片

 

 

订阅模型-Fanout(广播)

 

 原理

RabbitMQ的五种常用消息模型_第11张图片

  • 1) 可以有多个消费者

  • 2) 每个消费者有自己的queue(队列)

  • 3) 每个队列都要绑定到Exchange(交换机)

  • 4) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。

  • 5) 交换机把消息发送给绑定过的所有队列

  • 6) 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

       注意:交换机只能转发消息不能存储消息

Provider

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

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

        //发送消息
        channel.basicPublish("logs","",null,"fanout type message".getBytes());

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

Consumer

public class Consumer1 {
    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));
            }
        });
    }
}

 

 

订阅模型-Direct(路由)

 

原理

在广播模式的基础上进行的变种,可以指定消息(routing key)发送给部分订阅者,而不是直接发送给全部订阅者

RabbitMQ的五种常用消息模型_第12张图片

 

P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key

X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列

C1:消费者,其所在队列指定了需要routing key 为 error 的消息

C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息

Provider

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

        //通过通道声明交换机 参数1交换机名称  参数2交换机模式
        channel.exchangeDeclare("logs_direct","direct");

        //发送消息
        String routingkey = "info";
        channel.basicPublish("logs_direct",routingkey,null,("这是direct模式发布的基于routekey: {"+routingkey+"} 发送的消息").getBytes());

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

Consumer1

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

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

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

        //基于route key绑定队列和交换机
        channel.queueBind(queue,"logs_direct","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));
            }
        });
    }
}

 

 

订阅模型-Topic(通配符)

 

原理

Topic模式相当于在direct的基础上支持通配符匹配,这样省去了routingkey很多时,大量条件的书写

RabbitMQ的五种常用消息模型_第13张图片

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!

Routingkey 一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

#也可以匹配

RabbitMQ的五种常用消息模型_第14张图片

RabbitMQ的五种常用消息模型_第15张图片

Provider

RabbitMQ的五种常用消息模型_第16张图片

Consumer

RabbitMQ的五种常用消息模型_第17张图片

 

 

持久化

 

在MQ正常运行的过程中,我们可以通过ACK机制防止消费者丢失消息,但却解决不了MQ宕机或重启的问题。这时候我们就需要用到持久化技术了

交换机持久化

RabbitMQ的五种常用消息模型_第18张图片

队列持久化(会持久化一个空队列,队列里面没消息)

RabbitMQ的五种常用消息模型_第19张图片

消息持久化

 

 

消息确认机制(ACK)

 

RabbitMQ有一个ACK机制。当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。不过这种回执ACK分两种情况:

  • 自动ACK:消息一旦被接收,消费者自动发送ACK

  • 手动ACK:消息接收后,不会发送ACK,需要手动调用

如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便。如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除,如果此时消费者宕机,那么消息就丢失了。

注意RabbitMQ的ACK和Kafka的ACK不是一个概念。

你可能感兴趣的:([RabbitMQ],rabbitmq)