文章目录
前言
前面我们学习了 RabbitMQ 的基本使用以及 RabbitMQ 的快速上手,那么这篇文章我将为大家介绍 RabbitMQ 提供的 7 种工作模式,我们上一篇快速入门实现的案例其实就是 7 种工作模式中的简单模式。
第一种:Simple 简单模式
第二种:Work Queue 工作队列模式
第三种:Publish/Subscribe 发布/订阅模式
第四种:Routing 路由模式
第五种:Topic 通配符模式
第六种:RPC 模式
第七种就是 Publisher Confirms 发布确认模式。
1. Simple 简单模式
简单模式主要由一个 Producer、一个 Queue和一个 Consumer 组成。
简单模式的特点就是:一个生产者 P,一个消费者 C,消息只能被消费一次,也称为点对点(Point-to-Point)模式。
这里的具体实现上一篇文章我就写了,这里就不写了,大家可以去看看。
2. Work Queue 工作队列模式
工作队列模式由一个生产者 P,一个队列 Queue 和多个消费者 C1、C2…组成,在这种模式下,Work Queue 会将消息分派给不同的消费者,每个消费者都会接收到不同的消息,意思就是 Work Queue 接收到了 10 条消息,那么 Work Queue 会将这 10 条消息分成两部分,每个部分 5 条消息,每条消息都不重复,然后将这五条消息分别发送给 C1 和 C2。
特点:消息不会重复,分配给不同的消费者。
适用场景:集群环境中做异步处理。比如我们平时在银行中办理业务取号的时候,当要办理业务的人取号(生产者)了之后,那么这些号码就会被存放在队列中,银行中的每个窗口(消费者)会给不同号的人办理业务。
那么我们看看通过 Java 代码如何实现一个工作队列模式。
对于这些经常使用到的变量,我们将其归到一个类中进行管理:
public class Constants {
public static final String IP = "*.*.*.*";
public static final Integer PORT = 5672;
public static final String VIRTUAL_HOST = "test";
public static final String USER_NAME = "admin";
public static final String PASSWORD = "xxx";
public static final String WORK_QUEUE = "work.queue";
}
生产者代码:
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(Constants.IP);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
Connection connection = factory.newConnection();
//开启信道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(Constants.WORK_QUEUE,true,false,false,null);
//发送消息
for (int i = 0; i < 10; i++) {
String msg = "work queue" + i;
channel.basicPublish("",Constants.WORK_QUEUE,null,msg.getBytes());
}
System.out.println("消息发送成功");
channel.close();
connection.close();
}
}
消费者1 和消费者 2 的代码是一样的:
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(Constants.IP);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
Connection connection = factory.newConnection();
//开启信道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(Constants.WORK_QUEUE,true,false,false,null);
//消费消息
Consumer 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(Constants.WORK_QUEUE,consumer);
//释放资源
//channel.close(); 我们这里可以先不释放资源,不然当我们先运行消费者的时候queue中没有消息,consumer的连接就会直接关闭了
//connection.close();
}
}
先启动两个消费者,然后再启动生产者:
可以看到 Consumer1 和 Consumer2 拿到的消息都是不重复的消息。
3. Pubulish/Subscribe 发布/订阅模式
Exchange 的类型
可以发现这个模式相较于前面两个模式,多出了一个 X,这个 X 指的是 Exchange 交换机。
交换机的作用是:生产者将消息发送到 Exchange,由交换机将消息按照一定的规则路由到一个或者多个队列中。
AMQP 协议中的交换机的类型有六种:fanout,direct,topic,headers,System和自定义,但是 RabbitMQ 中交换机的类型只有前四种。
- Fanout:广播,将消息交给所有绑定到交换机的队列(Publish/Subcribe模式)
- Direct:定向,把消息交给符合指定 routing key 的队列(Routing 模式)
- Topic:通配符,把消息交给符合 routing pattern(路由模式)的队列(Topic 模式)
- headers类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。headers 类型的交换机性能很差,而且也不实用,基本上不会看到它的存在
Exchaneg 只负责转发消息,不具备存储消息的能力,因此如果没有队列与 Exchange 绑定,或者没有符合路由规则的队列,那么消息就会丢失。
- Routing Key:路由键。生产者将消息发送给转换机的时候,指定的一个字符串,用来告诉交换机应该如何处理这个消息
- Binding Key:绑定。RabbitMQ 中通过 Binding 将交换机和队列关联起来,在绑定的时候一般会指定一个 Binding Key,这样 RabbitMQ 就知道如何正确的将消息路由到队列了。
当 Exchange 拿到生产者发送来的消息之后,会将消息中带的 Routing Key 和与该交换机绑定的队列的 Binding Key 进行匹配,然后将这个消息发送给 Routing Key 和 Binding Key 匹配的队列。
当知道了交换机的几种类型之后,我们来看看如何使用代码实现出来一个 Publish/Subscribe 模式。
生产者代码:
首先还是与 RabbitMQ-Server 建立连接,开启信道,与前面不同的操作是,前面我们声明交换机的时候因为使用的是默认的交换机,所以就没有显式的声明交换机,但是在涉及到交换机类型的时候,我们就需要显式的声明交换机,虽然 RabbitMQ 默认为我们提供了各个类型的交换机,但是名字可能不好记,所以不如我们自己实现一个:
Java 中声明一个交换机的方法主要是 exchangeDeclare()
方法,这个方法有很多个重载方法,但是我们主要使用下面的这种方法:
AMQP.Exchange.DeclareOk exchangeDeclare(String var1, BuiltinExchangeType var2, boolean var3) throws IOException;
- String var1: 这个参数是交换机的名称。它是必须的,用于在RabbitMQ中唯一标识一个交换机。你可以根据需要为这个交换机命名。
- BuiltinExchangeType var2: 这个参数指定了交换机的类型。该类是一个枚举类,内部枚举了交换器的类型
- boolean var3: 这个布尔值参数指定交换机是否应该被标记为持久的(即,在RabbitMQ重启后仍然存在)。如果设置为true,交换机将持久化;如果设置为false,交换机则不会持久化。
public enum BuiltinExchangeType {
DIRECT("direct"),
FANOUT("fanout"),
TOPIC("topic"),
HEADERS("headers");
private final String type;
private BuiltinExchangeType(String type) {
this.type = type;
}
public String getType() {
return this.type;
}
}
声明完成交换器后,就是声明队列,声明队列之后就是需要绑定交换器和队列了:
绑定交换器和队列使用的方法是 queueBind()
方法,该方法也是有两个重载的方法:
AMQP.Queue.BindOk queueBind(String var1, String var2, String var3) throws IOException;
AMQP.Queue.BindOk queueBind(String var1, String var2, String var3, Map<String, Object> var4) throws IOException;
- String var1: 队列的名称。这是你想要绑定到交换机的队列的唯一标识符。
- String var2: 交换机的名称。这是你想要将队列绑定到的交换机的名称。
- String var3: 路由键。当消息发送到交换机时,交换机将使用路由键来确定哪些队列应该接收这个消息。路由键可以是任何字符串,其解释取决于交换机的类型。
- Map<String, Object> var4: 绑定参数。这是一个可选参数,允许你为绑定指定额外的参数,这些参数将根据交换机和队列的特定需求进行解释。例如,对于某些交换机类型(如headers交换机),绑定参数可能用于定义消息头中的条件。对于大多数用途,这个参数可能为空或未使用。
我们这里没有使用到额外的参数,所以就使用三个参数的方法:
channel.queueBind(Constants.FANOUT_QUEUE1,Constants.FANOUT_EXCHANGE,"");
channel.queueBind(Constants.FANOUT_QUEUE2,Constants.