(6)RabbitMQ之fanout交换机——Publish/Subscribe(发布订阅)

上节我们介绍了RabbitMQ的工作队列,这一节先会对RabbitMQ的几种交换机做个大概的介绍,然后会介绍一下fanout(扇形)类型的exchange,并通过代码示例达到Publish/Subscribe(发布订阅)也就是广播的效果。

概述

我们之前的例子当中,将exchange的名字设为了空字符串,貌似都是将消息直接发送给了Queue,实际上也是发送给了交换机的,只不过这个exchange的名字是个空字符串,是RabbitMQ的默认的交换机。RabbitMQ中消息传递模型的核心思想是生产者永远不会将任何消息直接发送到队列。实际上,生产者通常甚至不知道消息是否会被传递到任何队列。

如果对于交换机、routing key、绑定、绑定key这些概念不太了解,可以先看一下《(2)RabbitMQ基础概念及工作流程详解》的介绍,这里不做重复介绍了。

生产者发送消息时指定交换机名称、类型和routing key,消息发送到指定的交换机之后,exchange会根据交换机类型和routing key将消息路由到绑定的队列上面。

交换机分类

交换机主要包括如下4种类型:

  1. Fanout exchange(扇型交换机)
  2. Direct exchange(直连交换机)
  3. Topic exchange(主题交换机)
  4. Headers exchange(头交换机)

另外RabbitMQ默认定义一些交换机:默认匿名交换机、amq.* exchanges。还有一类特殊的交换机:Dead Letter Exchange(死信交换机)

fanout

fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,所以此时routing key是不起作用的。

 上图中,生产者(P)发送到Exchange(X)的所有消息都会路由到图中的两个Queue,并最终被两个消费者(C1与C2)消费。

direct

direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。

 以上图的配置为例,我们以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…);如果我们以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。

topic

前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:

  • routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
  • binding key与routing key一样也是句点号“. ”分隔的字符串
  • binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)

 以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。

headers

headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。

在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。

该类型的Exchange用的没有上面三种广泛,所以本次博客中就不单独进行示例介绍了。

其他交换机

默认匿名交换机(default exchange):实际上是一个由RabbitMQ预先声明好的名字为空字符串的直连交换机(direct exchange),所以我们前面发送消息时设置的exchange的name都是空字符串。它有一个特殊的属性使得它对于简单应用特别有用处:那就是每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。如:当你声明了一个名为”hello_queue”的队列,RabbitMQ会自动将其绑定到默认交换机上,绑定(binding)的路由键名称也是为”hello_queue”。因此,当携带着名为”hello_queue”的路由键的消息被发送到默认交换机的时候,此消息会被默认交换机路由至名为”hello_queue”的队列中。即默认交换机看起来貌似能够直接将消息投递给队列。

例如我们之前用 channel.basicPublish("", “hello_queue”, null, message.getBytes()) 的方式发送消息,空字符串就是默认匿名交换机的名称, hello_queue就是routing key。

类似amq.*的名称的交换机:这些是RabbitMQ默认创建的交换机。这些队列名称被预留做RabbitMQ内部使用,不能被应用使用,否则抛出403 (ACCESS_REFUSED)错误

通过查看AMQP default 交换机详情中的binding,我们也能看到对应的说明如下: 

上面是通过RabbitMQ的web管理台看到的,我们也可以通过命令:rabbitmqctl list_exchanges 进行查看,可以看到第二行的一个交换机名字是空字符串,类型是direct

[root@wkp4 ~]# rabbitmqctl list_exchanges
Listing exchanges
amq.direct	direct
	direct
amq.rabbitmq.log	topic
amq.headers	headers
amq.fanout	fanout
amq.topic	topic
amq.rabbitmq.trace	topic
amq.match	headers

Dead Letter Exchange(死信交换机):在默认情况,如果消息在投递到交换机时,交换机发现此消息没有匹配的队列,则这个消息将被悄悄丢弃。为了解决这个问题,RabbitMQ中有一种交换机叫死信交换机。当消费者不能处理接收到的消息时,将这个消息重新发布到另外一个队列中,等待重试或者人工干预。这个过程中的exchange和queue就是所谓的”Dead Letter Exchange 和 Queue”。

程序示例

项目GitHub地址 GitHub - RookieMember/RabbitMQ-Learning: 自己写RabbitMQ博客过程中的一些demo示例,客户端采用的java语言,博客地址:https://blog.csdn.net/u012988901。这节我们先介绍一下fanout类型的交换机,实现一下Publish/Subscribe(发布订阅)的效果,即一条消息发送给多个消费者,之前的work queues都是只发送给一个消费者。下面是消息发送者代码,我们可以看到生产者只是声明了交换机,然后将消息发送到了交换机中,整个过程当中并没有设置队列相关的参数。

注意:由于使用fanout类型的交换机时,routing key是不起作用的,所以代码中生产者发送消息、消费者中交换机与队列绑定时,routing key设置的都是空字符串,你可以将routing key改为任意值,你发现消费者都还是可以收到消息的。

package cn.wkp.rabbitmq.newest.exchange.fanout;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import cn.wkp.rabbitmq.util.ConnectionUtil;

public class Send {

    private final static String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道
        Channel channel = connection.createChannel();

        // 声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        // 消息内容
    	String message = "这是一条fanout类型交换机消息";
    	channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
    	System.out.println("Sent message:" + message);
        //关闭通道和连接
        channel.close();
        connection.close();
    }
}

上面的生产者代码声明交换机时使用了重载的方法channel.exchangeDeclare(String exchange, BuiltinExchangeType type),其中BuiltinExchangeType是个枚举类,列出了所有类型的交换机(该type参数也可以使用字符串"fanout"的形式),其源码如下所示。

public enum BuiltinExchangeType {

    DIRECT("direct"), FANOUT("fanout"), TOPIC("topic"), HEADERS("headers");

    private final String type;

    BuiltinExchangeType(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}

完整方法为:channel.exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete,Map<String, Object> arguments),各个参数解释如下:

  • exchange:交换机名称
  • type:交换机类型
  • durable:是否持久化。如果持久性,则RabbitMQ重启后,交换机还存在
  • autoDelete:当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它
  • arguments:扩展参数

消费者1代码如下:

package cn.wkp.rabbitmq.newest.exchange.fanout;

import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import cn.wkp.rabbitmq.util.ConnectionUtil;

public class Recv1 {

	private final static String EXCHANGE_NAME = "fanout_exchange";
	private final static String QUEUE_NAME = "fan_queue1";

	public static void main(String[] argv) throws Exception {

		// 获取到连接以及mq通道
		Connection connection = ConnectionUtil.getConnection();
		Channel channel = connection.createChannel();

		 // 声明交换机
                channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
		// 声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);

                // 绑定队列到交换机
                channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
		
		//指该消费者在接收到队列里的消息但没有返回确认结果之前,它不会将新的消息分发给它。
		channel.basicQos(1);

		// 定义队列的消费者
		Consumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费者1收到消息:" + new String(body));
				//消费者手动发送ack应答
				channel.basicAck(envelope.getDeliveryTag(), false);
			}
		};
		// 监听队列
		channel.basicConsume(QUEUE_NAME, false, consumer);
	}
}

消费者2代码如下(注意两个消费者队列名称不同):

package cn.wkp.rabbitmq.newest.exchange.fanout;

import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import cn.wkp.rabbitmq.util.ConnectionUtil;

public class Recv2 {

	private final static String EXCHANGE_NAME = "fanout_exchange";
	private final static String QUEUE_NAME = "fan_queue2";

	public static void main(String[] argv) throws Exception {

		// 获取到连接以及mq通道
		Connection connection = ConnectionUtil.getConnection();
		Channel channel = connection.createChannel();

		 // 声明交换机
                channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
		// 声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);

                // 绑定队列到交换机
                channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
		
		//指该消费者在接收到队列里的消息但没有返回确认结果之前,它不会将新的消息分发给它。
		channel.basicQos(1);

		// 定义队列的消费者
		Consumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费者2收到消息:" + new String(body));
				//消费者手动发送ack应答
				channel.basicAck(envelope.getDeliveryTag(), false);
			}
		};
		// 监听队列
		channel.basicConsume(QUEUE_NAME, false, consumer);
	}
}

 运行结果如下所示:

Sent message:这是一条fanout类型交换机消息
消费者1收到消息:这是一条fanout类型交换机消息
消费者2收到消息:这是一条fanout类型交换机消息

关于fanout类型的交换机就先介绍到这里,下一节将会介绍一下direct类型的交换机。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <amqp.h> #include <amqp_tcp_socket.h> int main(int argc, char *argv[]) { char const *hostname; int port, status; char const *exchange; char const *bindingkey; amqp_socket_t *socket = NULL; amqp_connection_state_t conn; hostname = "localhost"; port = 5672; exchange = "amq.topic"; bindingkey = "hello"; conn = amqp_new_connection(); socket = amqp_tcp_socket_new(conn); if (!socket) { printf("Error creating TCP socket\n"); return -1; } status = amqp_socket_open(socket, hostname, port); if (status) { printf("Error opening TCP socket\n"); return -1; } amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); amqp_channel_open(conn, 1); amqp_get_rpc_reply(conn); amqp_exchange_declare(conn, 1, amqp_cstring_bytes(exchange), amqp_cstring_bytes("topic"), 0, 0, 0, 0, amqp_empty_table); amqp_get_rpc_reply(conn); amqp_basic_publish(conn, 1, amqp_cstring_bytes(exchange), amqp_cstring_bytes(bindingkey), 0, 0, NULL, "Hello World!"); amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS); amqp_connection_close(conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(conn); return 0; }我已经写好了一段代码,可以实现 RabbitMQ 发布模式。它使用 C 语言编写,你可以根据自己的需求来改进它。 ### 回答2: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <rabbitmq-c/rabbitmq.h> #define QUEUE_NAME "my_queue" #define MESSAGE "Hello, RabbitMQ!" int main() { // 建立连接 amqp_connection_state_t conn; conn = amqp_new_connection(); // 打开通道 amqp_socket_t *socket = amqp_tcp_socket_new(conn); amqp_socket_open(socket, "localhost", 5672); // 登录认证 amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); amqp_channel_open(conn, 1); amqp_get_rpc_reply(conn); // 声明一个持久化的队列 amqp_queue_declare(conn, 1, amqp_cstring_bytes(QUEUE_NAME), 0, 1, 0, 0, amqp_empty_table); amqp_get_rpc_reply(conn); // 发布消息 amqp_basic_properties_t props; props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG; props.content_type = amqp_cstring_bytes("text/plain"); props.delivery_mode = 2; // 持久化消息 amqp_basic_publish(conn, 1, amqp_cstring_bytes(""), amqp_cstring_bytes(QUEUE_NAME), 0, 0, &props, amqp_cstring_bytes(MESSAGE)); amqp_get_rpc_reply(conn); // 关闭通道 amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS); amqp_get_rpc_reply(conn); // 关闭连接 amqp_connection_close(conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(conn); return 0; } ### 回答3: RabbitMQ是一种广泛使用的消息队列中间件,它支持多种消息传输模式,包括发布/阅(Publish/Subscribe)模式。在C语言中,我们可以使用RabbitMQ的C客户端库来编写一个发布模式的代码示例。 首先,我们需要安装RabbitMQ的C客户端库,并通过头文件引入相关函数和结构体。接下来,我们可以创建一个发布者(publisher)的代码,用于向RabbitMQ发送消息。 下面是一个简单的RabbitMQ发布模式的代码示例: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <amqp.h> int main() { // 连接到RabbitMQ服务器 amqp_connection_state_t conn = amqp_new_connection(); amqp_socket_t* socket = amqp_tcp_socket_new(conn); amqp_socket_open(socket, "localhost", 5672); // 创建一个通道 amqp_channel_open(conn, 1); amqp_get_rpc_reply(conn); // 创建一个交换机,用于消息发布 amqp_exchange_declare(conn, 1, amqp_cstring_bytes("my_exchange"), amqp_cstring_bytes("fanout"), false, true, false, false, amqp_empty_table); // 发布消息 const char* message = "Hello, RabbitMQ!"; amqp_basic_publish(conn, 1, amqp_cstring_bytes("my_exchange"), amqp_cstring_bytes(""), 0, 0, NULL, amqp_cstring_bytes(message)); // 关闭通道和连接 amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS); amqp_connection_close(conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(conn); return 0; } ``` 以上代码中,我们首先通过`amqp_connection_state_t`类型的`conn`变量创建RabbitMQ连接,并通过`amqp_tcp_socket_new`函数创建一个TCP socket与RabbitMQ服务器建立连接。接下来,我们通过`amqp_channel_open`函数创建一个通道,并使用`amqp_exchange_declare`函数声明一个名为`my_exchange`的交换机,用于消息的发布。然后,我们调用`amqp_basic_publish`函数发布一条消息到交换机。 最后,通过`amqp_channel_close`和`amqp_connection_close`函数关闭通道和连接,并通过`amqp_destroy_connection`函数释放连接资源。 这是一个基本的RabbitMQ发布模式的示例代码。根据实际需求,你可以进一步扩展代码,添加错误处理和异常情况的处理逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值