RabbitMQ中Work的两种工作模式
一.Work的轮询分发
RabbitMQ 的 Work 模式中的轮询分发(Round-Robin)是一种消息分发机制,其特点如下:
- 定义:轮询分发是指当有多个消费者接入时,RabbitMQ 按照顺序轮流将消息分发给每个消费者。
- 原理:生产者将消息发送到队列中,RabbitMQ 从队列中取出消息,按照轮询顺序分发给消费者。每个消费者接收消息后进行处理,处理完毕后确认消息已被消费,然后 RabbitMQ 再继续分发下一条消息。
轮询分发适用于需要将任务均匀分发给多个消费者的场景,如简单的任务调度、并行处理等。当消费者处理消息的速度不同时,轮询分发可能会导致负载不均衡的问题。处理速度快的消费者可能会空闲,而处理速度慢的消费者可能会积压大量消息。

生产者:
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class Producer { public static void main(String[] args) { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("8.137.76.12"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("生产者"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 6: 准备发送消息的内容 for (int i = 1; i <= 20; i++) { //消息的内容 String msg = "你好,马明:" + i; // 7: 发送消息给中间件rabbitmq-server // @params1: 交换机exchange // @params2: 队列名称/routingkey // @params3: 属性配置 // @params4: 发送消息的内容 channel.basicPublish("", "queue1", null, msg.getBytes()); } System.out.println("消息发送成功!"); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } }
消费者1:
import com.rabbitmq.client.*; import java.io.IOException; public class Work1 { public static void main(String[] args) { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("8.137.76.12"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("消费者-Work1"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 5: 申明队列queue存储消息 /* * 如果队列不存在,则会创建 * Rabbitmq不允许创建两个相同的队列名称,否则会报错。 * * @params1: queue 队列的名称 * @params2: durable 队列是否持久化 * @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭 * @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。 * @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。 * */ // 这里如果queue已经被创建过一次了,可以不需要定义 // channel.queueDeclare("queue1", false, false, false, null); // 同一时刻,服务器只会推送一条消息给消费者 // 6: 定义接受消息的回调 Channel finalChannel = channel; finalChannel.basicQos(1); finalChannel.basicConsume("queue1", true, new DeliverCallback() { @Override public void handle(String s, Delivery delivery) throws IOException { try{ System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8")); Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } } }, new CancelCallback() { @Override public void handle(String s) throws IOException { } }); System.out.println("Work1-开始接受消息"); System.in.read(); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null && connection.isOpen()) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } }
消费者2:
import com.rabbitmq.client.*; import java.io.IOException; public class Work2 { public static void main(String[] args) { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("8.137.76.12"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("消费者-Work2"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 5: 申明队列queue存储消息 /* * 如果队列不存在,则会创建 * Rabbitmq不允许创建两个相同的队列名称,否则会报错。 * * @params1: queue 队列的名称 * @params2: durable 队列是否持久化 * @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭 * @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。 * @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。 * */ // 这里如果queue已经被创建过一次了,可以不需要定义 //channel.queueDeclare("queue1", false, true, false, null); // 同一时刻,服务器只会推送一条消息给消费者 //channel.basicQos(1); // 6: 定义接受消息的回调 Channel finalChannel = channel; finalChannel.basicQos(1); finalChannel.basicConsume("queue1", true, new DeliverCallback() { @Override public void handle(String s, Delivery delivery) throws IOException { try{ System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8")); Thread.sleep(200); }catch(Exception ex){ ex.printStackTrace(); } } }, new CancelCallback() { @Override public void handle(String s) throws IOException { } }); System.out.println("Work2-开始接受消息"); System.in.read(); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null && connection.isOpen()) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } }
结果展示:


如上图:两个消费者分别收到的都是不同的消息,而且是均分的,即使每个消费者都有速度限制,但是还是同等的消费
这就体现了轮询分发的特点,不会根据服务器速度匹配,而是平均的分配给每个消费者
二.Work的公平分发
RabbitMQ的Work模式中的公平分发(Fair Dispatch)是一种根据消费者的消费能力进行消息分发的机制。具体来说,公平分发模式确保处理速度快的消费者能够处理更多的消息,而处理速度慢的消费者则处理较少的消息,实现“能者多劳”。
公平分发的实现原理
- 手动确认机制:在公平分发模式下,需要将消息确认模式改为手动确认。这意味着消费者在处理完消息后,需要手动发送确认(ACK)给RabbitMQ,以告知消息已被成功处理。
- 限制预取消息数:通过设置
prefetchCount参数,可以限制每个消费者在未确认消息之前接收的消息数量。这样,RabbitMQ会确保在消费者处理完并确认一条消息后,才会再发送下一条消息给它。
负载均衡:公平分发模式能够根据消费者的实际处理能力来分配消息,从而避免某些消费者过载而其他消费者空闲的情况。
提高系统效率:通过让处理速度快的消费者处理更多的消息,可以充分利用系统资源,提高整体处理效率
生产者:
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class Producer { public static void main(String[] args) { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("8.137.76.12"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("生产者"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 6: 准备发送消息的内容 for (int i = 1; i <= 20; i++) { //消息的内容 String msg = "你好,马明:" + i; // 7: 发送消息给中间件rabbitmq-server // @params1: 交换机exchange // @params2: 队列名称/routingkey // @params3: 属性配置 // @params4: 发送消息的内容 channel.basicPublish("", "queue1", null, msg.getBytes()); } System.out.println("消息发送成功!"); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } }
消费者1:
import com.rabbitmq.client.*; import java.io.IOException; public class Work1 { public static void main(String[] args) { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("8.137.76.12"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("消费者-Work1"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 5: 申明队列queue存储消息 /* * 如果队列不存在,则会创建 * Rabbitmq不允许创建两个相同的队列名称,否则会报错。 * * @params1: queue 队列的名称 * @params2: durable 队列是否持久化 * @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭 * @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。 * @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。 * */ // 这里如果queue已经被创建过一次了,可以不需要定义 // channel.queueDeclare("queue1", false, false, false, null); // 同一时刻,服务器只会推送一条消息给消费者 // 6: 定义接受消息的回调 Channel finalChannel = channel; finalChannel.basicQos(1); finalChannel.basicConsume("queue1", false, new DeliverCallback() { @Override public void handle(String s, Delivery delivery) throws IOException { try{ System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8")); Thread.sleep(2000); finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false); }catch(Exception ex){ ex.printStackTrace(); } } }, new CancelCallback() { @Override public void handle(String s) throws IOException { } }); System.out.println("Work1-开始接受消息"); System.in.read(); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null && connection.isOpen()) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } }
消费者2:
import com.rabbitmq.client.*; import java.io.IOException; public class Work2 { public static void main(String[] args) { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("8.137.76.12"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("消费者-Work2"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 5: 申明队列queue存储消息 /* * 如果队列不存在,则会创建 * Rabbitmq不允许创建两个相同的队列名称,否则会报错。 * @params1: queue 队列的名称 * @params2: durable 队列是否持久化 * @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭 * @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。 * @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。 * */ // 这里如果queue已经被创建过一次了,可以不需要定义 //channel.queueDeclare("queue1", false, true, false, null); // 同一时刻,服务器只会推送一条消息给消费者 //channel.basicQos(1); // 6: 定义接受消息的回调 Channel finalChannel = channel; finalChannel.basicQos(1); finalChannel.basicConsume("queue1", false, new DeliverCallback() { @Override public void handle(String s, Delivery delivery) throws IOException { try{ System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8")); Thread.sleep(200); finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false); }catch(Exception ex){ ex.printStackTrace(); } } }, new CancelCallback() { @Override public void handle(String s) throws IOException { } }); System.out.println("Work2-开始接受消息"); System.in.read(); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null && connection.isOpen()) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } }
结果截图:


如上图,使用公平分发模式之后我们会发现,队列会通过处理的快慢来进行分发消息,这使得消费者1收到少量的消息,而消费者2收到很多消息
这就是为什么公平模式需要手动应答,只有处理完信息之后告诉队列,然后队列好分配新的消息,处理的快的就多处理,而处理的慢的就少处理
三.RabbitMQ的使用场景
RabbitMQ 是一个基于 AMQP(Advanced Message Queuing Protocol)协议的消息队列中间件,广泛用于分布式系统中。它通过消息队列的机制实现了解耦、削峰和异步等核心功能,这些特性使得系统更加灵活、高效和可靠。
解耦是 RabbitMQ 的一个重要特性,指的是将系统的不同组件或模块之间的直接依赖关系解除,从而降低系统的耦合度。
削峰是指通过消息队列平滑处理高并发请求的能力,避免系统因瞬间流量激增而崩溃。
异步是指将耗时的操作从主线程中剥离出来,通过消息队列实现非阻塞式的处理,从而提高系统的响应速度和吞吐量。
RabbitMQ 的解耦、削峰和异步功能,分别解决了系统间的紧耦合问题、高并发流量冲击问题以及耗时操作阻塞问题。这三种特性共同作用,使得分布式系统更加健壮、灵活和高效,能够应对复杂的业务场景和高并发需求。
同步异步的问题(串行)
串行方式:将订单信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端

public void makeOrder(){ // 1 :保存订单 orderService.saveOrder(); // 2: 发送短信服务 messageService.sendSMS("order");//1-2 s // 3: 发送email服务 emailService.sendEmail("order");//1-2 s // 4: 发送APP服务 appService.sendApp("order"); }
并行方式 异步线程池
并行方式:将订单信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间

public void makeOrder(){ // 1 :保存订单 orderService.saveOrder(); // 相关发送 relationMessage(); } public void relationMessage(){ // 异步 theadpool.submit(new Callable<Object>{ public Object call(){ // 2: 发送短信服务 messageService.sendSMS("order"); } }) // 异步 theadpool.submit(new Callable<Object>{ public Object call(){ // 3: 发送email服务 emailService.sendEmail("order"); } }) // 异步 theadpool.submit(new Callable<Object>{ public Object call(){ // 4: 发送短信服务 appService.sendApp("order"); } }) // 异步 theadpool.submit(new Callable<Object>{ public Object call(){ // 4: 发送短信服务 appService.sendApp("order"); } }) }
存在问题:
1:耦合度高
2:需要自己写线程池自己维护成本太高
3:出现了消息可能会丢失,需要你自己做消息补偿
4:如何保证消息的可靠性你自己写
5:如果服务器承载不了,你需要自己去写高可用
异步消息队列的方式

public void makeOrder(){ // 1 :保存订单 orderService.saveOrder(); rabbitTemplate.convertSend("ex","2","消息内容"); }
1:完全解耦,用MQ建立桥接
2:有独立的线程池和运行模型
3:出现了消息可能会丢失,MQ有持久化功能
4:如何保证消息的可靠性,死信队列和消息转移的等
5:如果服务器承载不了,你需要自己去写高可用,HA镜像模型高可用。
按照以上约定,用户的响应时间相当于是订单信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍
高内聚,低耦合


-
-------END-------

浙公网安备 33010602011771号