2、RabbitMQ入门:RabbitMQ架构、安装与配置、.NET客户端库(RabbitMQ.Client)基础使用

好,咱们正式开始聊 RabbitMQ。说实话,消息队列这块,RabbitMQ 是我个人用得最多的中间件之一。它成熟、稳定,社区活跃,在 .NET 生态里也是首选。今天这一章,我带你把 RabbitMQ 的底裤扒开看看——架构怎么设计的,怎么装起来,以及 .NET 里怎么用代码跟它“对话”。

2.1 RabbitMQ 架构:核心概念与模型

先别急着装软件,咱们得先搞明白 RabbitMQ 到底是个什么玩意儿。说白了,它就是一个“邮局”。你(生产者)把信(消息)交给邮局,邮局根据地址(路由规则)把信送到收件人(消费者)手里。

这个模型里,有几个核心角色,我一个个说:

  • 生产者(Producer):发消息的一方。就是那个写信的人。
  • 消费者(Consumer):收消息的一方。就是收信的人。
  • 队列(Queue):消息的“信箱”。消息在队列里排队,等着被消费。
  • 交换机(Exchange):邮局的“分拣中心”。生产者把消息发给交换机,交换机根据规则把消息路由到不同的队列。
  • 绑定(Binding):交换机和队列之间的“连接线”。它告诉交换机,什么样的消息该送到哪个队列。
  • 虚拟主机(Virtual Host):相当于邮局里的不同“部门”。一个 RabbitMQ 服务器可以创建多个 vhost,每个 vhost 里可以有自己独立的交换机、队列、绑定。权限隔离用的,我建议你每个项目用一个 vhost。

核心流程:

生产者 → 交换机 → (根据绑定规则) → 队列 → 消费者

消息不会直接进队列,必须先经过交换机。这一点很多新手会搞混。

交换机有几种类型,我简单列一下,后面章节会细讲:

类型 路由规则 典型场景
Direct 完全匹配 Routing Key 点对点,比如日志级别
Topic 通配符匹配 Routing Key 按主题订阅,比如订单事件
Fanout 广播给所有绑定的队列 发布/订阅,比如系统通知
Headers 匹配消息的 Header 属性 复杂路由,用得少

我的经验: 刚开始用 RabbitMQ 时,我总喜欢用 Fanout 交换机,觉得简单。后来项目大了,发现 Topic 交换机才是真香——灵活,扩展性好。你想想看,以后加个新消费者,改个绑定规则就行,不用动生产者代码。

2.2 安装与配置:从零跑起来

安装 RabbitMQ 其实不复杂,但有几个坑。我踩过,所以给你指出来。

2.2.1 安装 Erlang

RabbitMQ 是用 Erlang 写的,所以得先装 Erlang。去官网下载对应你系统的版本就行。Windows 下直接 exe 安装,Linux 下用包管理器。

注意: Erlang 版本和 RabbitMQ 版本有对应关系。装错了启动不了。我曾经因为装了个最新的 Erlang,结果 RabbitMQ 不认,折腾了半天。建议去 RabbitMQ 官网看版本兼容表。

2.2.2 安装 RabbitMQ Server

同样去官网下载。Windows 下是 exe,Linux 下是 rpm 或 deb。装完后,服务会自动启动。你可以用浏览器访问 http://localhost:15672 看看管理界面出来没。

默认账号密码是 guest/guest。但注意,这个账号只能在 localhost 登录。远程访问的话,得新建一个用户。

2.2.3 启用管理插件

管理界面默认是启用的。如果没出来,手动启用一下:

rabbitmq-plugins enable rabbitmq_management

然后重启服务:

rabbitmq-service stop
rabbitmq-service start

小技巧: 我习惯在开发环境用 Docker 装 RabbitMQ,一条命令搞定:

docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management

这样管理界面和消息端口都暴露出来了,省事。

2.2.4 创建用户和虚拟主机

生产环境千万别用 guest 账号。我建议你创建一个专用用户:

rabbitmqctl add_user myuser mypassword
rabbitmqctl set_user_tags myuser administrator
rabbitmqctl add_vhost myvhost
rabbitmqctl set_permissions -p myvhost myuser ".*" ".*" ".*"

这样,你的应用就用 myuser 连接 myvhost,权限隔离,安全又清晰。

2.3 .NET 客户端库(RabbitMQ.Client)基础使用

好,环境搭好了,咱们开始写代码。.NET 下最常用的库就是 RabbitMQ.Client。NuGet 上直接装:

Install-Package RabbitMQ.Client

2.3.1 建立连接

第一步,创建连接工厂。说白了就是告诉客户端,RabbitMQ 服务器在哪,用什么账号登录。

var factory = new ConnectionFactory()
{
    HostName = "localhost",
    UserName = "myuser",
    Password = "mypassword",
    VirtualHost = "myvhost"
};

using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

这里要注意,Connection 是长连接,Channel 是轻量级的“会话”。一个连接可以创建多个 Channel。我建议你复用 Connection,但每个线程或任务用独立的 Channel。

避坑指南: 我曾经在项目里每个请求都创建新的 Connection,结果 RabbitMQ 服务器连接数暴涨,直接 OOM 了。记住,Connection 是昂贵的资源,一定要复用。

2.3.2 声明队列和交换机

在发消息之前,得先声明队列和交换机。如果队列不存在,RabbitMQ 会自动创建。

// 声明一个队列
channel.QueueDeclare(queue: "hello",
                     durable: false,
                     exclusive: false,
                     autoDelete: false,
                     arguments: null);

// 声明一个交换机
channel.ExchangeDeclare(exchange: "my_exchange",
                        type: ExchangeType.Direct);

durable 参数表示队列是否持久化。如果设为 true,RabbitMQ 重启后队列还在。生产环境我建议都设为 true,除非你明确知道自己在做什么。

2.3.3 发送消息

发消息很简单,把消息转成字节数组,然后发布到交换机。

string message = "Hello, RabbitMQ!";
var body = Encoding.UTF8.GetBytes(message);

channel.BasicPublish(exchange: "my_exchange",
                     routingKey: "hello",
                     basicProperties: null,
                     body: body);

Console.WriteLine($" [x] Sent {message}");

这里 routingKey 就是队列的名字(如果是 Direct 交换机)。如果是 Fanout 交换机,routingKey 会被忽略。

2.3.4 消费消息

消费者这边,需要订阅队列,然后处理消息。

var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
    var body = ea.Body.ToArray();
    var message = Encoding.UTF8.GetString(body);
    Console.WriteLine($" [x] Received {message}");

    // 手动确认消息已处理
    channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
};

channel.BasicConsume(queue: "hello",
                     autoAck: false,
                     consumer: consumer);

Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();

我的习惯: 我一般把 autoAck 设为 false,然后手动调用 BasicAck。这样能确保消息处理成功后才从队列里移除。如果处理失败,消息会重新入队,不会丢。你想想看,如果自动确认,消费者挂了,消息就丢了,多坑。

2.3.5 完整示例:生产者 + 消费者

把上面代码拼起来,就是一个完整的“Hello World”了。我建议你动手跑一遍,感受一下消息从发到收的完整流程。

// 生产者
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

channel.QueueDeclare("hello", durable: false, exclusive: false, autoDelete: false, arguments: null);

string message = "Hello World!";
var body = Encoding.UTF8.GetBytes(message);

channel.BasicPublish(exchange: "", routingKey: "hello", basicProperties: null, body: body);
Console.WriteLine($" [x] Sent {message}");

// 消费者(另一个控制台应用)
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

channel.QueueDeclare("hello", durable: false, exclusive: false, autoDelete: false, arguments: null);

var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
    var body = ea.Body.ToArray();
    var message = Encoding.UTF8.GetString(body);
    Console.WriteLine($" [x] Received {message}");
};

channel.BasicConsume(queue: "hello", autoAck: true, consumer: consumer);

Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();

注意: 生产者和消费者要分别运行在两个控制台应用里。别在一个进程里又发又收,那样看不出消息队列的异步效果。

2.4 本章小结

这一章,咱们把 RabbitMQ 的架构、安装、以及 .NET 客户端的基础用法都过了一遍。说白了,就是搞清楚了“邮局”怎么运作,怎么搭起来,以及怎么用 C# 寄信和收信。

下一章,我会带你深入交换机类型和路由策略。到时候你会发现,RabbitMQ 的灵活之处远不止于此。嗯,先消化一下今天的内容,动手跑跑代码,有问题随时翻回来看看。


公众号:蓝海资料掘金营,微信deep3321