RabbitMQ七种工作模式介绍:

1.Simple(简单模式):

P:生产者,也就是要发送的程序。

C:消费者,消息的接受者。

Queue:消息队列,图中黄色的部分,类似一个邮箱,P可以往里面投敌消息,消费者可以从其中取出消息。

特点:一个生产者P,一个消费者C,消息只能被消费一次,也被称为点对点模式。

2.Work Queue(工作队列):

一个生产者P,多个消费者C,在多个消息的情况下,Work Queue会将消息分派给不同的消费者,也就是说每个消费者收到的消息都不同。

特点:消息不会重复,分配给不同的消费者。

交换机有关的工作模式:

在订阅模型中,多了一个交换机角色,过程略有办法。

概念介绍:

交换机(exchange)

作用:生产者将消息发送到Exchange中,然后交换机按照一定规则路由到一个或多个队列中。

RabbitMQ交换机有四种类型:fanout,direct,topic,headers,不同类型有不同的路由策略。

Fanout:广播,将所有消息交给所绑定到交换机的队列(Publish/Subscribe模式)

Direct:定向,把消息定向交给指定的routingKey的队列(Routing模式)

Topic:通配符,把消息交给符合Routing pattern(路由模式)的队列(Topics模式)

headers:headers类型的交换器不依赖于路由键的匹配规则来路由消息,而且也不实用,基本不会用到。

3.Publish/Subscribe(发布/订阅):

一个生产者P,多个消费者,这时就多了一个交换机的角色,每个消费者都能收到相同的消息。生产者发送一条消息,经过交换机转换到多个不同的队列,多个队列有多个不同的消费者。

适合场景:消息需要被多个消费者同时接收的场景,如:实时通知或者广播消息。

4.Routing(路由模式):

路由模式是发布订阅模式的变更,比发布订阅模式基础上,加了一个路由key,发布订阅模式是无条件将所有消息分发给所有消费者,路由模式是Exchange根据RoutingKey的规则,将数据进行匹配后发送给对应的消费者队列。

X是交换机,然后根据不同的RoutingKey(a,b,c)发送到对应的队列中。

5.Topics(通配符模式):

通配符模式是路由模式的升级,在RoutingKey的基础上,增加了通配符的功能,使之更加灵活。

Topic和Routing的基本原理相同,但是匹配的规则不同,Routing是相等匹配,而topics模式是通配符匹配。

*号匹配一个且仅一个单词。

#号匹配0个或者多个单词。

6.RPC(RPC通信):

在RPC通信过程中,没有生产者和消费者,比较像咱们RPC远程调用,大概通过两个队列实现了一个可回调过程。

1.客户端发送一个消息到指定的队列,并且在消息属性中设置ReplyTo字段,这个字段指定了一个回到队列,用于接受服务器的响应。

2.服务器就收到消息后,处理请求并响应消息到ReplyTo指定的回调队列中。

3.客户端在回调队列上等待响应消息,一旦接收到响应,客户端会检查消息的correlationId属性,确保是不是自己期望的响应。

7.Publisher Confirm(发布确认):

Publisher Comfirm模式是RabbitMQ提供的一种确保消息可靠发送到RabbitMQ服务器的机制,这种模式下,生产者发送了消息后,可以等待RabbitMQ服务器的确认,以确保消息已经被服务器接受并处理。

1.生产者将channel设置为Confirm模式(通过调用channel.confirmSelect()完成)后,发布每一条消息都会获得一个唯一ID,生产者可以将这些序列号关联起来,追踪消息的状态。

2.当消息被RabbitMQ服务器接收并处理后,服务器会异步的向生产者发送一个确认(ACK)给生产者(包含唯一ID),表示消息已经送达。

工作队列的代码:

1.引入依赖:

java 复制代码
 <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.25.0</version>
 </dependency>

2.编写生产者代码:

java 复制代码
public static void main(String[] args) throws IOException, TimeoutException {
        //创建channel通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//默认值localhost
        factory.setPort(5672);//默认值是5672
        factory.setUsername("guest");//用户名,默认guest
        factory.setPassword("guest");//密码,默认guest
        factory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection=factory.newConnection();
        Channel channel=connection.createChannel();
        //声明队列,如果没有这样的队列,就会自动生成一个,如果有则不生成。
        channel.queueDeclare("hello",true, false, false, null);
        //发送消息
        for (int i = 0; i < 10; i++) {
            String message = "Hello RabbitMQ!";
            //发送消息,默认交换机是""
            channel.basicPublish("","hello",null,message.getBytes());
        }
        //释放资源
        channel.close();
        connection.close();
    }

3.编写消费者代码:

java 复制代码
 public static void main(String[] args) throws IOException, TimeoutException {
        //创建channel通道
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");//默认值localhost
        connectionFactory.setPort(5672);//默认值是5672
        connectionFactory.setUsername("guest");//用户名,默认guest
        connectionFactory.setPassword("guest");//密码,默认guest
        connectionFactory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare("hello", true, false, false, null);
        //接收消息,并消费
        DefaultConsumer consumer = 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.basicConsume("hello",true,consumer);
    }

运行结果:

Publish/Subscribe(发布/订阅)代码:

在发布订阅模式中,多了一个Exchange交换机。同时还有将交换机和队列进行关系绑定,以便路由到对应的队列中,给消费者消费。

1.引入依赖:

java 复制代码
 <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.25.0</version>
 </dependency>

2.生产者代码:

java 复制代码
public static void main(String[] args) throws IOException, TimeoutException {
        //建立channel通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//默认值localhost
        factory.setPort(5672);//默认值是5672
        factory.setUsername("guest");//用户名,默认guest
        factory.setPassword("guest");//密码,默认guest
        factory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //声明交换机,因为这里是发布/订阅模式,所以type是BuiltinExchangeType.FANOUT
        channel.exchangeDeclare("fanout.exchange",BuiltinExchangeType.FANOUT,true,false,false,null);
        //声明队列,这里声明两个队列
        channel.queueDeclare("fanout.queue1",true,false,false,null);
        channel.queueDeclare("fanout.queue2",true,false,false,null);
        //绑定交换机和队列,后面的routinKey是匹配规则,这里的发布订阅模式所以是""
        channel.queueBind("fanout.queue1", "fanout.exchange","");
        channel.queueBind("fanout.queue2", "fanout.exchange","");
        //发送消息
        String message = "Hello fanout!!";
        channel.basicPublish("fanout.exchange","", null, message.getBytes());
        //资源释放
        channel.close();
        connection.close();
    }

3.消费者代码:

这里有两个消费者:

消费者1代码:

java 复制代码
  public static void main(String[] args) throws IOException, TimeoutException {
        //建立channel通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//默认值localhost
        factory.setPort(5672);//默认值是5672
        factory.setUsername("guest");//用户名,默认guest
        factory.setPassword("guest");//密码,默认guest
        factory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare("fanout.queue1", true, false, false, null);
        //消费队列
        DefaultConsumer consumer = 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.basicConsume("fanout.queue1",true,consumer);
    }

消费者2代码:

java 复制代码
public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//默认值localhost
        factory.setPort(5672);//默认值是5672
        factory.setUsername("guest");//用户名,默认guest
        factory.setPassword("guest");//密码,默认guest
        factory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("fanout.queue2", true, false, false, null);
        DefaultConsumer consumer = 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.basicConsume("fanout.queue2",true,consumer);
    }

运行结果:

消费者1:

消费者2:

可以看出发布订阅模式中,每个消费者都收到了相同的消息。

Routing(路由模式):

路由模式不同于上面的发布/订阅模式,路由模式的匹配规则不是任意绑定了,而是要指定一个BindingKey的一种。

而且生产者在像Exchange发送消息时,也需要指定消息的RoutingKey。

Exchange不再把消息交给每个绑定的key,而是根据消息的RoutingKey进行判断,只有队列绑定时的BindKey和发送消息的Routingkey完全一致时,才会收到消息。

接下来看代码实现:

1.引入依赖:
java 复制代码
 <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.25.0</version>
 </dependency>
2.编写生产者代码:

这时交换机类型是DIRECT。

java 复制代码
 public static void main(String[] args) throws IOException, TimeoutException {
        //建立channel通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//默认值localhost
        factory.setPort(5672);//默认值是5672
        factory.setUsername("guest");//用户名,默认guest
        factory.setPassword("guest");//密码,默认guest
        factory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //创建交换机
        channel.exchangeDeclare("direct.exchange", BuiltinExchangeType.DIRECT,true,false,false,null);
        //声明队列
        channel.queueDeclare("direct.queue1", true, false, false, null);
        channel.queueDeclare("direct.queue2", true, false, false, null);
        //绑定队列和交换机的关系
        channel.queueBind("direct.queue1", "direct.exchange", "a");
        channel.queueBind("direct.queue2", "direct.exchange", "a");
        channel.queueBind("direct.queue2", "direct.exchange", "b");
        channel.queueBind("direct.queue2", "direct.exchange", "c");
        //发送消息
        String msga="hello a!";
        String msgb="hello b!";
        String msgc="hello c!";
        channel.basicPublish("direct.exchange", "a", null, msga.getBytes());
        channel.basicPublish("direct.exchange", "b", null, msgb.getBytes());
        channel.basicPublish("direct.exchange", "c", null, msgc.getBytes());
        System.out.println("消息发送成功!!!");
        //释放资源
        channel.close();
        connection.close();
    }
3.编写消费者代码:

消费者1代码:

java 复制代码
 public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//默认值localhost
        factory.setPort(5672);//默认值是5672
        factory.setUsername("guest");//用户名,默认guest
        factory.setPassword("guest");//密码,默认guest
        factory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //接收消息并消费
        DefaultConsumer consumer = 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.basicConsume("direct.queue1",true,consumer);
    }

消费者2代码:

java 复制代码
 public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//默认值localhost
        factory.setPort(5672);//默认值是5672
        factory.setUsername("guest");//用户名,默认guest
        factory.setPassword("guest");//密码,默认guest
        factory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //接收消息并消费
        DefaultConsumer consumer = 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.basicConsume("direct.queue2",true,consumer);
    }
运行结果:

消费者1:

消费者2:

从运行结果可以看出Routing模式的规则。因为发送a消息时,只发送给了对应的消费者1,但发送a,b,c时,发送给了消费者2,因为他们二者和交换机的绑定规则不一样。

Topics(通配符模式):

这里的通配符模式,交换机类型是TOPIC

topic类型的交换机在匹配规则上进行了扩展,BindKey支持通配符匹配,direct类型的交换机的规则是Bindingkey和Routingkey完全匹配。

代码和上面的Routing模式一样,我就只写区别代码,也就是交换机和队列绑定的代码。

java 复制代码
  //声明交换机和队列
        channel.exchangeDeclare("topic.exchange", BuiltinExchangeType.TOPIC,true);
        channel.queueDeclare("topic.queue1", true, false, false, null);
        channel.queueDeclare("topic.queue2", true, false, false, null);
        //绑定交换机和队列,这里的绑定规则使用通配符绑定,*表示匹配一个且仅一个单词,#表示匹配0个或多个单词
        channel.queueBind("topic.queue1", "topic.exchange", "*.a.*");
        channel.queueBind("topic.queue2", "topic.exchange", "*.*.b");
        channel.queueBind("topic.queue2", "topic.exchange", "c.#");
        //发送消息,此时,也要注明要将消息发送哪一个交换机,并且写明BingingKey,后续根据对应的BingingKey,匹配到对应的队列中
        String message = "Hello A!";
        channel.basicPublish("topic.exchange", "e.a.f", null, message.getBytes());
        String message2 = "Hello B!";
        channel.basicPublish("topic.exchange", "ef.a.b", null, message2.getBytes());
        String message3 = "Hello C!";
        channel.basicPublish("topic.exchange", "c.ef.b", null, message3.getBytes());

RPC(RPC通信):

RPC(Remote Procedure Call),即远程过程调用,它是一种通过网络从远程计算机上请求服务,而不需要了解底层网络的技术,类是与Http远程调用。

RabbitMQ实现RPC通信的过程,大概是通过两个队列实现一个可回调的过程。

大致流程如下:

1.客户端发送一个消息到一个指定的队列,并在消息属性中设置replyTo字段,这个字段指定了一个回调队列,服务端处理后,会把响应结果发送到这个队里。

2.服务端接收到请求后,处理请求并发送响应消息到replyTo指定的回调队列。

3.客户端在回调队列上等待响应的消息,一旦有消息响应,客户端就会检查消息的correlationId属性,以确保是自己想要的响应。

PRC的代码实现:

1.引入依赖:

java 复制代码
 <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.25.0</version>
 </dependency>

2.客户端代码:

java 复制代码
 public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//默认值localhost
        factory.setPort(5672);//默认值是5672
        factory.setUsername("guest");//用户名,默认guest
        factory.setPassword("guest");//密码,默认guest
        factory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare("rpc.queue", true, false, false, null);
        //定义回调队列
        channel.queueDeclare("rpc.response.queue", true, false, false, null);
        String message = "Hello RabbitMQ!";
        //本词请求的唯一标志
        String correlationId = UUID.randomUUID().toString();
        //生成发送消息的属性
        AMQP.BasicProperties props=new AMQP.BasicProperties().builder()
                        .correlationId(correlationId)
                .replyTo("rpc.response.queue")
                .build();
        //发送消息
        channel.basicPublish("", "rpc.queue", props,message.getBytes());

        //阻塞队列,用于储存回调结果
        final BlockingQueue<String> queue = new LinkedBlockingQueue<String>(1);
        //接收服务器的响应
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到回调消息" + new String(body));
                //进行标识判断,放到阻塞队列中
                if(correlationId.equals(properties.getCorrelationId())){
                     queue.offer(new String(body));
                }
            }
        };
        channel.basicConsume("rpc.response.queue",true,consumer);
        //获取回调结果
                String result=queue.take();
        System.out.println("RPC响应结果+"+result);
    }

3.服务端代码:

java 复制代码
public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//默认值localhost
        factory.setPort(5672);//默认值是5672
        factory.setUsername("guest");//用户名,默认guest
        factory.setPassword("guest");//密码,默认guest
        factory.setVirtualHost("Virtual host");//虚拟机的名称
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //设置同时只能获取一个消息
        channel.basicQos(1);
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
                //处理请求,并生成返回
                System.out.println("接收到请求!!"+new String(body));
                AMQP.BasicProperties props=new AMQP.BasicProperties().builder()
                        .correlationId(properties.getCorrelationId())
                        .build();
                //回复消息,通知已经收到的请求
                channel.basicPublish("", "rpc.response.queue", props, body);
                //对消息进行应答
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume("rpc.queue",false,consumer);
    }

运行结果:

客户端:

服务端:

可以看出,客户端给服务端成功发送了请求,并成功处理后,服务端还给客户端进行响应请求。

还有一种发布确认模式,在后面的文章会续上。

相关推荐
济南java开发,求内推5 小时前
单个服务器部署多个rabbitmq
运维·服务器·rabbitmq
qqxhb5 小时前
系统架构设计师备考第45天——软件架构演化评估方法和维护
分布式·缓存·系统架构·集群·cdn·单体·已知未知评估
默 语5 小时前
消息中间件选型的艺术:如何在RocketMQ、Kafka、RabbitMQ中做出正确决策
java·架构·kafka·消息队列·rabbitmq·rocketmq·技术选型
库库8399 小时前
Redis分布式锁、Redisson及Redis红锁知识点总结
数据库·redis·分布式
蒙特卡洛的随机游走10 小时前
Spark核心数据(RDD、DataFrame 和 Dataset)
大数据·分布式·spark
埃泽漫笔10 小时前
Kafka、ActiveMQ、RabbitMQ、RocketMQ 对比
kafka·rabbitmq·activemq
NO.102411 小时前
本地缓存怎么在分布式环境下保持一致性
分布式·缓存
superlls11 小时前
(定时任务)接上篇:定时任务的分布式执行与分布式锁使用场景
java·分布式·后端
回家路上绕了弯12 小时前
高并发后台系统设计要点:从流量削峰到低延迟的实战指南
分布式·后端