5. 交换机
5.1 Exchanges
RabbitMQ消息传递模型的思想是:生产者生产的消息从不会直接发送到队列;相反,生产者只能将消息发送到交换机,一方面,他接收到生产者的信息,另一方面,将他们推入队列
5.1.1 Exchange的类型
直接(direct)、主题(topic)、标题(headers)、扇出(发布/订阅:fanout)
5.1.2 无名(默认)Exchange
channel.basicPublish("", queueName, null, message.getBytes(StandardCharsets.UTF_8));
之前使用的 “” 就是默认交换机
5.1.3 绑定(binding)
binding就是exchange和queue的桥梁,由他指定交换机和哪些队列进行绑定
5.2 Fanout 扇出交换机
5.2.1 fanout简介
fanout就是一种发布/订阅模式,他将接收到的消息广播到他知道的所有队列中
5.2.2 编码实现
一个发送,两个接收
/**
* 消息的接收
*/
public class ReceiveLog1 {
public static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//声明队列 临时队列队,列名称随机
String queueName = channel.queueDeclare().getQueue();
//绑定交换机与队列
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println("等待接收消息,接收到消息打印...");
channel.basicConsume(queueName, true, (consumerTag, message) -> {
System.out.println("ReceiveLog1接收到的消息:" + new String(message.getBody()));
}, (consumerTag) -> {}
);
}
}
/**
* 消息的接收
*/
public class ReceiveLog2 {
public static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//声明队列 临时队列队,列名称随机
String queueName = channel.queueDeclare().getQueue();
//绑定交换机与队列
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println("等待接收消息,接收到消息打印...");
channel.basicConsume(queueName, true, (consumerTag, message) -> {
System.out.println("ReceiveLog2接收到的消息:" + new String(message.getBody()));
}, (consumerTag) -> {}
);
}
}
/**
* 发送消息
*/
public class EmitLog {
//交换机名称
public static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
//1:交换机名 2:routingKey 3:其它参数 4:发送的消息
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("发送的消息:" + message);
}
}
}
5.3 Direct 直接交换机
5.3.1 Direct简介
Direct模式其实就是路由模式。在fanout(发布/订阅)模式中,发送信息时,会发送到exchange中,绑定了该交换机的队列都会接收到消息,那是因为绑定的routingKey值都是相同的。当routeKey值不同时,发送消息时可以通过指定的routingKey值发送给特定的队列。这就是路由模式(Direct)。
5.3.2 案例
实现以下direct模式:当交换机指定不同routingKey时,发送到不同队列
/**
* 接收console队列的消息
*/
public class ReceiveLogsDirect1 {
public static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
//获取信道
Channel channel = RabbitMqUtils.getChannel();
//声明一个交换机 1:交换机名 2:模式
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声明队列 1:队列名 2:是否持久化 3:是否共享 4:是否自动删除 5:其它参数
channel.queueDeclare("console", false, false, false, null);
//绑定 1:队列名 2:交换机名 3:routingKey
channel.queueBind("console", EXCHANGE_NAME, "info");
channel.queueBind("console", EXCHANGE_NAME, "warning");
//消费消息 1:队列名 2:自动应答 3:消费成功回调 4:消费取消回调
channel.basicConsume("console", true, (consumerTag, message) -> {
System.out.println("ReceiveLogsDirect1接收到的消息:" + new String(message.getBody()));
}, (consumerTag) -> {
});
}
}
/**
* 接收disk队列的消息
*/
public class ReceiveLogsDirect2 {
public static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
//获取信道
Channel channel = RabbitMqUtils.getChannel();
//声明一个交换机 1:交换机名 2:模式
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声明队列 1:队列名 2:是否持久化 3:是否共享 4:是否自动删除 5:其它参数
channel.queueDeclare("disk", false, false, false, null);
//绑定交换机和队列 1:队列名 2:交换机名 3:routingKey
channel.queueBind("disk", EXCHANGE_NAME, "error");
//消费消息 1:队列名 2:自动应答 3:消费成功回调 4:消费取消回调
channel.basicConsume("disk", true, (consumerTag, message) -> {
System.out.println("ReceiveLogsDirect2接收到的消息:" + new String(message.getBody()));
}, (consumerTag) -> {
});
}
}
/**
* 发送消息
*/
public class DirectLogs {
//交换机名称
public static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
Map<String, String> map = new HashMap<>();
map.put("info", "info...");
map.put("warning", "warning...");
map.put("error", "error...");
for (Map.Entry<String, String> entry : map.entrySet()) {
String message = entry.getValue();
//发送信息并指定交换机和routingKey 1:交换机名 2:routingKey 3:其它参数 4:发送的消息
channel.basicPublish(EXCHANGE_NAME, entry.getKey(), null,
message.getBytes(StandardCharsets.UTF_8));
System.out.println("发送的消息:" + message);
}
}
}
5.4 Topic 主题交换机
5.4.1 Topic 简介及规范
routingKey支持类似正则的规范,可以匹配一类具体的routingKey。相比于fanout和direct更灵活。
规范:
- 必须是一个单词列表,以点号分隔开。如:user.man.young.a
- ***** 号可以代替一个单词 。如:一共三个单词且中间单词是user:
*.user.*
,一共三个单词且最后一个单词是abc:*.*.abc
- # 号可以代替零个或多个单词。如:第一个单词是user:
user.#
- 当队列绑定的routingKey是 # 号,那么这个队列将接受所有数据,如同fanout
- Topic模式包含了fanout和direct模式
5.4.2 案例
/**
* 主题交换机 消费者C1
*/
public class ReceiveLogsTopic1 {
//交换机名称
public static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
//获取信道
Channel channel = RabbitMqUtils.getChannel();
//声明交换机 topic
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//声明队列 1:队列名 2:持久化 3:共享 4:自动删除 5:其他信息
channel.queueDeclare("Q1", false, false, false, null);
//绑定 1:队列名 2:交换机名 3:routingKey
channel.queueBind("Q1", EXCHANGE_NAME, "*.orange.*");
System.out.println("等待接收消息");
//1:队列名 2:自动应答 3:消费成功回调 4:消费取消回调
channel.basicConsume("Q1", true, (consumerTag, message) -> {
System.out.println("接收到routingKey=" + message.getEnvelope().getRoutingKey() + "的消息:" + new
String(message.getBody()));
}, (consumerTag) -> {
});
}
}
/**
* 主题交换机 消费者C2
*/
public class ReceiveLogsTopic2 {
//交换机名称
public static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
//获取信道
Channel channel = RabbitMqUtils.getChannel();
//声明交换机 topic
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//声明队列 1:队列名 2:持久化 3:共享 4:自动删除 5:其他信息
channel.queueDeclare("Q2", false, false, false, null);
//绑定 1:队列名 2:交换机名 3:routingKey
channel.queueBind("Q2", EXCHANGE_NAME, "*.*.rabbit");
channel.queueBind("Q2", EXCHANGE_NAME, "lazy.#");
System.out.println("等待接收消息");
//1:队列名 2:自动应答 3:消费成功回调 4:消费取消回调
channel.basicConsume("Q2", true, (consumerTag, message) -> {
System.out.println("接收到routingKey=" + message.getEnvelope().getRoutingKey() + "的消息:" + new
String(message.getBody()));
}, (consumerTag) -> {
});
}
}
/**
* 生产者:发送消息
*/
public class EmitLogTopic {
//交换机名称
public static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
//*.*.rabbit lazy.# *.orange.*
Map<String, String> binding = new HashMap<>();
binding.put("a.orange.rabbit", "a.orange.rabbit...");
binding.put("lazy.orange.abc", "lazy.orange.abc...");
binding.put("lazy.orange.rabbit", "lazy.orange.rabbit...");
binding.put("abc.rabbit.orange", "abc.rabbit.orange...");
binding.put("abc.orange.efg", "abc.orange.efg...");
binding.put("lazy.123.efg", "lazy.123.efg...");
for (Map.Entry<String, String> entry : binding.entrySet()) {
String message = entry.getValue();
//发送信息并指定交换机和routingKey 1:交换机名 2:routingKey 3:其它参数 4:发送的消息
channel.basicPublish(EXCHANGE_NAME, entry.getKey(), null,
message.getBytes(StandardCharsets.UTF_8));
System.out.println("发送的消息:" + message);
}
}
}