7.RabbitMQ 订阅模式-----Fanout(广播)

本文介绍了使用RabbitMQ实现的发布/订阅模式,展示了如何在生产者和消费者之间通过FANOUT交换机进行消息广播,并详细解释了生产者与消费者之间的队列绑定配置以及如何处理消息确认和错误处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

订阅模式-----Fanout(广播)

在在工作模式中:每个任务只被传递给一个工作人员。

在这一部分,我们将做一些完全不同的事情 - 我们将会传递一个信息给多个消费者。 这种模式被称为**“发布/订阅”**。

image-20240327102728990

生产者

与之前的工作模型相比区别就是绑定了一个交换机,其他的参数(Queue、RoutingKey)都为默认值(“”)

RabbitMqConstant.EXCHANGE_SUBSCRIPTION_CHANGE:交换机名称(随意)

RabbitMqConstant.ROUTING_DEFAULT:默认名称为(“”)

@ApiModel(value = "AmqpSendController",description = "整合amqp进行消息推送")
@RestController
@RequestMapping("/amqpSend")
public class AmqpSendController {
    @Autowired
    private AmqpTemplate amqpTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @PostMapping("/subscription/{message}")
    public void subscriptionSend(@PathVariable(value = "message") String message){
        System.out.println("subscription发送消息:"+message);
        amqpTemplate.convertAndSend(RabbitMqConstant.EXCHANGE_SUBSCRIPTION_CHANGE,RabbitMqConstant.ROUTING_DEFAULT,message);
    }
}

生产者

@RabbitListener(queues = RabbitMqConstant.QUEUE_WORK_QUEUE,ackMode = "MANUAL")
// 等于 下面的样式 绑定默认的交换机 ,交换机的类型为默认的直连类型
@RabbitListener(bindings = @QueueBinding(
    value = @Queue(value = RabbitMqConstant.QUEUE_WORK_QUEUE),
    exchange = @Exchange(value = RabbitMqConstant.EXCHANGE_DEFAULT,
                         type = ExchangeTypes.DIRECT)))

与之前的工作模式相比绑定了一个交换机

    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = RabbitMqConstant.QUEUE_SUBSCRIPTION_ROUTING_ONE),
                    exchange = @Exchange(value = RabbitMqConstant.EXCHANGE_SUBSCRIPTION_CHANGE,
                            type = ExchangeTypes.FANOUT),
                    key = RabbitMqConstant.ROUTING_DEFAULT),
            ackMode = "MANUAL")
  • bindings: 定义了与消息队列绑定相关的设置。

    • value: 表示队列的相关设置。
      • value = @Queue(value = RabbitMqConstant.QUEUE_SUBSCRIPTION_ROUTING_ONE): 声明一个队列,其中 RabbitMqConstant.QUEUE_SUBSCRIPTION_ROUTING_ONE 是队列的名称。
    • exchange: 表示交换机的相关设置。
      • value = @Exchange(value = RabbitMqConstant.EXCHANGE_SUBSCRIPTION_CHANGE,type = ExchangeTypes.FANOUT): 声明一个交换机,
        • value :表示RabbitMqConstant.EXCHANGE_SUBSCRIPTION_CHANGE 是交换机的名称。
        • type : 交换机类型。交换机常用类型有direct(直连,默认类型)、topic(主题)、fanout(订阅广播模式)
    • key: 表示绑定键,用于指定将消息路由到指定队列的规则。
      • RabbitMqConstant.ROUTING_DEFAULT: 绑定键,用于将消息路由到队列的规则。

    两个消费者的交换机Exchange是一样的,唯一不同的是队列Queue

    消费者One的队列名称:RabbitMqConstant.QUEUE_SUBSCRIPTION_ROUTING_ONE

    消费者Two的队列名称:RabbitMqConstant.QUEUE_SUBSCRIPTION_ROUTING_TWO

@Component
public class RabbitMQListener {
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = RabbitMqConstant.QUEUE_SUBSCRIPTION_ROUTING_ONE),
                    exchange = @Exchange(value = RabbitMqConstant.EXCHANGE_SUBSCRIPTION_CHANGE,
                            type = ExchangeTypes.FANOUT),
                    key = RabbitMqConstant.ROUTING_DEFAULT),
            ackMode = "MANUAL")
    public void subscriptionMessageReceiveOne(Message message, Channel channel) {
        try {
            String messageBody = new String(message.getBody());
            //手动确认消息
            if (messageBody.length() < 10) {
                System.out.println("One SUBSCRIPTION ------------ 消息接收:" + messageBody);
                //第一个参数是消息的标记,第二个参数是是否批量处理
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println(messageBody + ":消息接收失败!");
                new RuntimeException("消息长度大于10");
                //第一个参数是消息的标记,第二个参数是是否批量处理,第三个参数,是否重新入队,true为重新入队,false为丢弃该消息
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = RabbitMqConstant.QUEUE_SUBSCRIPTION_ROUTING_TWO),
                    exchange = @Exchange(value = RabbitMqConstant.EXCHANGE_SUBSCRIPTION_CHANGE,
                            type = ExchangeTypes.FANOUT),
                    key = RabbitMqConstant.ROUTING_DEFAULT ),
            ackMode = "MANUAL")
    public void subscriptionMessageReceiveTwo(Message message, Channel channel) {
        try {
            String messageBody = new String(message.getBody());
            //手动确认消息
            if (messageBody.length() < 10) {
                System.out.println("Two SUBSCRIPTION ------------ 消息接收:" + messageBody);
                //第一个参数是消息的标记,第二个参数是是否批量处理
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println(messageBody + ":消息接收失败!");
                new RuntimeException("消息长度大于10");
                //第一个参数是消息的标记,第二个参数是是否批量处理,第三个参数,是否重新入队,true为重新入队,false为丢弃该消息
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

结果:发送的一个消息被所有订阅的队列消费。

image-20240327113553387

添加新的消费者进入队列:

方法1:手动添加队列信息,并绑定对应的交换机。(推荐,之前队列的消息不会丢失)

**方法2:**删除之前的交换机和队列,可能会有消息丢失。

<think>好的,用户想了解如何在RabbitMQ的C客户端(rabbitmq-c)中实现发布/订阅模式。我需要先回忆一下RabbitMQ的基本概念,特别是发布/订阅相关的机制。发布/订阅通常使用交换机,比如fanout类型,这样消息会被广播到所有绑定的队列。用户可能已经对RabbitMQ有一定了解,但需要具体的操作步骤。 首先,应该确定用户的操作系统环境,但rabbitmq-c是C语言的库,可能在Linux下使用较多。不过具体步骤可能大同小异。用户需要安装rabbitmq-c库,可能还需要安装amqp-tools或者其他依赖。这点可能需要提到,比如通过包管理器安装或者源码编译。 接下来,发布者和订阅者的实现步骤。发布者需要创建连接、通道,声明交换机,然后发布消息到交换机,而不是直接到队列。订阅者则需要声明队列,将队列绑定到交换机,然后开始消费消息。需要强调交换机的类型为fanout,这样所有绑定的队列都会收到消息。 代码结构方面,可能需要分开发布者和订阅者的示例。在C语言中,使用rabbitmq-c的API,需要注意内存管理和错误处理,比如检查每个函数的返回值,释放资源等。例如,amqp_connection_close和amqp_destroy_connection的区别,确保正确关闭连接。 另外,队列声明时,如果是订阅者,可能需要让服务器生成随机队列名,并设置自动删除属性,这样当消费者断开时队列会被删除。或者在示例中简化,使用固定的队列名,但可能在实际应用中需要动态生成。 需要提醒用户编译时链接正确的库,比如使用-lrabbitmq的编译选项。可能还需要包含头文件路径,比如-I后跟安装路径。 可能用户会遇到的问题,比如交换机未声明导致消息无法路由,或者队列未正确绑定。这时候可以建议检查错误码,或者使用RabbitMQ的管理界面查看交换机是否存在,绑定情况如何。 最后,是否应该提供完整的代码示例?虽然之前的回复已经有了,但可能需要更详细的注释,或者分步骤解释每个函数的作用,比如amqp_basic_publish的参数意义,特别是routing_key在fanout交换机下是否可以为空,或者是否需要指定。 另外,确认rabbitmq-c的版本是否支持某些特性,但一般来说,fanout是基本功能,应该没问题。但如果有兼容性问题,可能需要用户注意版本。 总结步骤:安装库,编写发布者(连接、创建通道、声明fanout交换机、发送消息),编写订阅者(连接、通道、声明队列、绑定到交换机、消费消息)。强调交换机类型和绑定过程,以及错误处理。</think>在 RabbitMQ 中使用 C 语言客户端库 `rabbitmq-c` 实现 **发布/订阅(Pub/Sub)模式**,需通过 **Fanout 类型交换机**将消息广播给所有绑定的队列。以下是操作步骤和示例代码: --- ### 一、环境准备 1. **安装 rabbitmq-c 库** - Ubuntu/Debian: ```bash sudo apt-get install librabbitmq-dev ``` - 源码编译: 从 [GitHub](https://github.com/alanxz/rabbitmq-c) 下载并编译。 --- ### 二、发布者(Publisher)流程 1. **创建连接和通道** 2. **声明 Fanout 类型交换机** 3. **发送消息到交换机** ```c #include <amqp.h> #include <amqp_tcp_socket.h> int main() { // 1. 建立连接 amqp_connection_state_t 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"); // 2. 创建通道并声明 Fanout 交换机 amqp_channel_open(conn, 1); amqp_exchange_declare(conn, 1, amqp_cstring_bytes("logs"), amqp_cstring_bytes("fanout"), 0, 0, 0, 0, amqp_empty_table); // 3. 发布消息 amqp_basic_publish(conn, 1, amqp_cstring_bytes("logs"), amqp_cstring_bytes(""), // routing_key 留空 0, 0, NULL, amqp_cstring_bytes("Hello from C Publisher!")); // 清理资源 amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS); amqp_connection_close(conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(conn); return 0; } ``` --- ### 三、订阅者(Subscriber)流程 1. **创建连接和通道** 2. **声明匿名队列**(服务器生成唯一名称) 3. **将队列绑定到 Fanout 交换机** 4. **消费消息** ```c #include <amqp.h> #include <amqp_tcp_socket.h> #include <amqp_framing.h> int main() { // 1. 建立连接 amqp_connection_state_t 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"); // 2. 创建通道并声明队列(匿名队列) amqp_channel_open(conn, 1); amqp_queue_declare_ok_t *queue = amqp_queue_declare( conn, 1, amqp_empty_bytes, 0, 0, 1, 1, amqp_empty_table); amqp_bytes_t queue_name = queue->queue; // 3. 绑定队列到 Fanout 交换机 amqp_queue_bind(conn, 1, queue_name, amqp_cstring_bytes("logs"), // 交换机名 amqp_empty_bytes, // routing_key 留空 amqp_empty_table); // 4. 开始消费消息 amqp_basic_consume(conn, 1, queue_name, amqp_empty_bytes, 0, 1, 0, amqp_empty_table); while (1) { amqp_envelope_t envelope; amqp_maybe_release_buffers(conn); amqp_consume_message(conn, &envelope, NULL, 0); printf("Received: %.*s\n", (int)envelope.message.body.len, (char *)envelope.message.body.bytes); amqp_destroy_envelope(&envelope); } // 清理资源(此处不可达,实际需处理信号终止循环) amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS); amqp_connection_close(conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(conn); return 0; } ``` --- ### 四、编译与运行 1. **编译命令**(需链接 `rabbitmq` 库): ```bash gcc publisher.c -o publisher -lrabbitmq gcc subscriber.c -o subscriber -lrabbitmq ``` 2. **启动服务**: - 确保 RabbitMQ 服务已运行(`systemctl start rabbitmq-server`)。 3. **测试效果**: - 启动多个订阅者,它们会收到发布者的每条消息。 --- ### 五、关键点说明 - **交换机类型**:必须使用 `fanout` 类型以实现广播- **匿名队列**:订阅者声明队列时,不指定名称(`amqp_empty_bytes`)让服务器生成唯一名称。 - **绑定路由键**:Fanout 交换机忽略路由键,订阅者的 `routing_key` 参数可留空。 - **持久化**:示例未配置持久化,实际生产环境需根据需求设置 `durable` 参数。 遇到问题时,可通过 RabbitMQ 管理界面(默认端口 15672)检查交换机绑定状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值