RabbitMQ-集群

RabbitMQ集群----主备关系,在运行的时候,如果非主要节点宕机,程序操作 不受影响; 如果主节点宕机了, 程序会中断操作。 而Rabbitmq集群,会马上让没有宕机的节点参选,选出新的主要节点。 程序重试的时候,会进入到新的节点中执行。 历史消息不受影响的。

基于Docker构建RabbitMQ集群

1.启动多个RabbitMQ节点

使用Docker启动3个RabbitMQ节点,目标如下表所示:

2.命令启动

节点1:

docker run -d --hostname my-rabbit1 --name rabbit1 -p 5672:5672 -p 15672:15672 -e
RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:management

节点2:

docker run -d --hostname my-rabbit2 --name rabbit2 -p 5673:5672 -p 15673:15672 -e
RABBITMQ_ERLANG_COOKIE='rabbitcookie' --link rabbit1:my-rabbit1 rabbitmq:management

节点3:

docker run -d --hostname my-rabbit3 --name rabbit3 -p 5674:5672 -p 15674:15672 -e
RABBITMQ_ERLANG_COOKIE='rabbitcookie' --link rabbit1:my-rabbit1 --link rabbit2:my-rabbit2
rabbitmq:management

注意:由于Erlang节点间通过认证Erlang cookie的方式来允许互相通信,所以 RABBITMQ_ERLANG_COOKIE必须设置为相同的。 启动完成之后,使用docker ps命令查看运行情况,确保RabbitMQ都已经启动。

3.加入集群

内存节点和磁盘节点的选择:

每个RabbitMQ节点,要么是内存节点,要么是磁盘节点。内存节点将所有的队列、交换器、绑定、用 户等元数据定义都存储在内存中;而磁盘节点将元数据存储在磁盘中。单节点系统只允许磁盘类型的节 点,否则当节点重启以后,所有的配置信息都会丢失。如果采用集群的方式,可以选择至少配置一个节 点为磁盘节点,其余部分配置为内存节点,,这样可以获得更快的响应。所以本集群中配置节点1位磁 盘节点,节点2和节点3位内存节点。

集群中的第一个节点将初始元数据代入集群中,并且无须被告知加入。而第2个和之后加入的节点将加 入它并获取它的元数据。要加入节点,需要进入Docker容器,重启RabbitMQ。

设置节点1:

docker exec -it rabbit1 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit

设置节点2:

docker exec -it rabbit2 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@my-rabbit1
rabbitmqctl start_app
exit

设置节点3:

docker exec -it rabbit3 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@my-rabbit1
rabbitmqctl start_app
exit

节点设置完成之后,在浏览器访问43.142.250.100:15672、43.142.250.100:15673和 43.142.250.100:15674中任意一个,都会看到RabbitMQ集群已经创建成功。

4.配置镜像队列

镜像队列工作原理:在非镜像队列的集群中,消息会路由到指定的队列。当配置为镜像队列之后,消息 除了按照路由规则投递到相应的队列外,还会投递到镜像队列的拷贝。也可以想象在镜像队列中隐藏着 一个fanout交换器,将消息发送到镜像的队列的拷贝。

进入任意一个RabbitMQ节点,执行如下命令:

rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'

可以设置镜像队列,"^"表示匹配所有队列,即所有队列在各个节点上都会有备份。在集群中,只需要 在一个节点上设置镜像队列,设置操作会同步到其他节点。

rabbitmqctl cluster_status

案例代码:

ConnectionFactory factory = new ConnectionFactory();
factory.AutomaticRecoveryEnabled = true;//如果connection挂掉是否重新连接
factory.TopologyRecoveryEnabled = true;//连接恢复后,连接的交换机,队列等是否一同恢复

factory.UserName = UrlConfig.User;//用户名
factory.Password = UrlConfig.Password;//密码 

var amqpTcpEndpointList = new List<AmqpTcpEndpoint>()
{
    new AmqpTcpEndpoint() { HostName="43.142.250.100", Port=5672},
    new AmqpTcpEndpoint() { HostName="43.142.250.100", Port=5673},
    new AmqpTcpEndpoint() { HostName="43.142.250.100", Port=5674},
};
using (var connection = factory.CreateConnection(amqpTcpEndpointList))
{
    using (IModel channel = connection.CreateModel())
    {
        #region 声明路由和队列  
        //支持持久化队列:durable: true
        channel.QueueDeclare(queue: "JoinClusterQueue", durable: true,
            exclusive: false, autoDelete: false, arguments: null);

        //支持持久化交换机durable: true
        channel.ExchangeDeclare(type: ExchangeType.Fanout, exchange: "JoinClusterExChange",
            durable: true, autoDelete: false, arguments: null);
        channel.QueueBind(queue: "JoinClusterQueue", exchange: "JoinClusterExChange",
            routingKey: string.Empty);
        #endregion

        //表达发送的是持久化消息
        var props = channel.CreateBasicProperties();
        props.Persistent = true;

        for (int i = 1; true; i++)
        {
            string msg = $"持久化消息--持久化队列===消息入队确认=={i}";
            byte[] bytes = Encoding.UTF8.GetBytes(msg);
            channel.BasicPublish("JoinClusterExChange", string.Empty, props, bytes);

            Console.WriteLine($"已发送:{msg}");

            Thread.Sleep(200);
        }
    }
}
2025-12-09 02:33:33.1639|Error|Channel action timed out Unhandled exception. System.TimeoutException: The operation requested on PersistentChannel timed out at EasyNetQ.Producer.PersistentChannel.InvokeChannelAction(Action`1 channelAction) at EasyNetQ.Producer.ClientCommandDispatcherSingleton.<>c__DisplayClass6_0`1.<InvokeAsync>b__0() --- End of stack trace from previous location where exception was thrown --- at EasyNetQ.Producer.ClientCommandDispatcherSingleton.Invoke(Action`1 channelAction) at EasyNetQ.Producer.ClientCommandDispatcher.Invoke(Action`1 channelAction) at EasyNetQ.RabbitAdvancedBus.ExchangeDeclare(String name, String type, Boolean passive, Boolean durable, Boolean autoDelete, Boolean internal, String alternateExchange, Boolean delayed) at AntiUAV.DeviceServer.Hosted.MqRpcHosted..ctor(ILogger`1 logger, IBus bus, IMemoryCache memory, IServiceOpt opt, IDeviceService device) in G:\anti-uav\Server\AntiUAV.DeviceServer\AntiUAV.DeviceServer\Hosted\MqRpcHosted.cs:line 25 at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitNoCache(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider) at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken) at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) at AntiUAV.DeviceServer.Program.Main(String[] args) in G:\anti-uav\Server\AntiUAV.DeviceServer\AntiUAV.DeviceServer\Program.cs:line 84 at AntiUAV.DeviceServer.Program.<Main>(String[] args) Aborted (core dumped) 2025-12-09 02:33:45.2976|Info|loaded plugin for resource AntiUAV.DevicePlugin.Obstruct14.dll. 2025-12-09 02:33:47.4623|Info|device server initialization finished. (devId:17,devCategory:30203) 2025-12-09 02:33:47.5017|Info|devServ udp listion start.(devId:17,ip:127.0.0.1,port:9991) 2025-12-09 02:33:47.6800|Debug|Trying to connect 2025-12-09 02:33:47.7230|Error|Failed to connect to broker "127.0.0.1", port 5672, vhost "/" 2025-12-09 02:33:47.7564|Error|Failed to connect to any Broker. Retrying in 00:00:05
最新发布
12-10
你提供的日志再次确认了问题的根源: > ❌ **RabbitMQ 服务未运行或无法访问,导致 EasyNetQ 在初始化时因连接超时而崩溃。** 更严重的是:**异常发生在 `MqRpcHosted` 的构造函数中(第25行),直接导致 `IHostedService` 初始化失败 → 机启动失败 → 整个 .NET 应用程序崩溃退出。** --- ## ✅ 最终解决方案:优雅处理 RabbitMQ 不可用的情况 我们需要做两件事: 1. ✅ 确保 RabbitMQ 正在运行; 2. ✅ 修改代码结构,**避免在构造函数中执行阻塞式 AMQP 操作**(如声明 Exchange),否则会破坏依赖注入容器的稳定性。 --- ### 🔧 第一步:启动 RabbitMQ 服务(关键前提) #### 如果你在本地开发环境,请立即执行以下操作之一: ##### ✅ 方式一:使用 Docker 启动 RabbitMQ(推荐) ```bash docker run -d \ --name rabbitmq \ -p 5672:5672 \ -p 15672:15672 \ rabbitmq:3-management ``` - `5672` 是 AMQP 协议端口(用于程序连接) - `15672` 是管理界面端口(可通过浏览器访问) 访问 [http://localhost:15672](http://localhost:15672) 默认账号密码:`guest / guest` ✅ 验证是否成功: ```bash telnet 127.0.0.1 5672 # 或 Linux/Mac nc -zv 127.0.0.1 5672 ``` 如果连接成功,则说明 RabbitMQ 已就绪。 --- ### 🛠 第二步:修复代码 —— 将 Exchange 声明移到 `StartAsync` 当前错误堆栈指出问题出在: ``` at AntiUAV.DeviceServer.Hosted.MqRpcHosted..ctor(...) in MqRpcHosted.cs:line 25 ``` 这表示你在 **构造函数里调用了 `_bus.Advanced.ExchangeDeclare(...)`**,但此时 RabbitMQ 可能还没连上,EasyNetQ 的 `PersistentChannel` 还未建立,于是抛出 `TimeoutException`。 #### ❌ 错误写法(禁止!) ```csharp public class MqRpcHosted { public MqRpcHosted(IBus bus, ...) { var advanced = bus.Advanced; // ⚠️ 构造函数内同步调用,可能导致 DI 容器崩溃 var exchange = advanced.ExchangeDeclare("device.cmd", "topic"); // ← Line 25 } } ``` #### ✅ 正确做法:延迟到 `StartAsync` 中异步重试 ```csharp using Microsoft.Extensions.Hosting; using EasyNetQ; using Microsoft.Extensions.Logging; public class MqRpcHosted : IHostedService { private readonly ILogger<MqRpcHosted> _logger; private readonly IBus _bus; private readonly IMemoryCache _memory; private readonly IServiceOpt _opt; private readonly IDeviceService _device; public MqRpcHosted( ILogger<MqRpcHosted> logger, IBus bus, IMemoryCache memory, IServiceOpt opt, IDeviceService device) { _logger = logger; _bus = bus; _memory = memory; _opt = opt; _device = device; } public async Task StartAsync(CancellationToken cancellationToken) { const int maxRetries = 20; // 最多重试 20 次 const int delayMs = 3000; // 每次间隔 3 秒 for (int i = 0; i < maxRetries; i++) { try { var advanced = _bus.Advanced; // 声明交换机 var exchange = await advanced.ExchangeDeclareAsync( name: "device.cmd.exchange", type: "topic", durable: true, autoDelete: false); _logger.LogInformation("✅ 成功声明交换机: {ExchangeName}", exchange.Name); // TODO: 声明队列并绑定 var queue = await advanced.QueueDeclareAsync("device.server.queue"); await advanced.BindAsync(exchange, queue, "device.cmd.*"); // 开始消费消息 await advanced.ConsumeAsync(queue, (body, properties, info) => { _logger.LogInformation("收到设命令: {Message}", System.Text.Encoding.UTF8.GetString(body)); // 处理业务逻辑... return Task.CompletedTask; }); break; // 成功则退出重试循环 } catch (Exception ex) { _logger.LogError(ex, "❌ 初始化 MQ 资源失败 (尝试 {Attempt}/{Max}),{Delay}s 后重试...", i + 1, maxRetries, delayMs / 1000); if (i >= maxRetries - 1) { _logger.LogCritical("❗ 经过 {Max} 次重试仍无法连接 RabbitMQ,应用程序将退出。", maxRetries); throw; // 最终仍失败,再抛出异常终止程序(可选) } await Task.Delay(delayMs, cancellationToken); } } } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } ``` > 💡 提示:你可以根据需求选择“持续重试”或“达到最大次数后退出”。 --- ### ✅ 第三步:注册托管服务(确保已添加) 在 `Program.cs` 中: ```csharp builder.Services.AddHostedService<MqRpcHosted>(); ``` 这样才能让 `StartAsync` 被正确调用。 --- ### ✅ 第四步:检查连接字符串配置 确保 `appsettings.json` 中配置无误: ```json { "RabbitMQ": { "ConnectionString": "host=127.0.0.1;port=5672;virtualHost=/;username=guest;password=guest;timeout=15;heartbeat=30" } } ``` 并在 `Program.cs` 注册 EasyNetQ: ```csharp builder.Services.RegisterEasyNetQ(builder.Configuration.GetConnectionString("RabbitMQ")); ``` --- ### ✅ 第五步:理解 EasyNetQ 的自动恢复机制 EasyNetQ 支持: - 自动重连 - 自动恢复 Exchange/Queue/Consumer 但它 **不能保证在第一次连接前就能完成所有操作**。因此你的应用必须容忍“初始阶段 MQ 不可用”的情况。 > ✅ 所以:不要怕“启动时连不上”,要怕“不重试、直接炸”。 --- ### ✅ 总结 | 问题 | 解决方案 | |------|----------| | `Failed to connect to broker "127.0.0.1"` | 启动 RabbitMQ 服务(Docker 推荐) | | `Channel action timed out` | 不要在构造函数中执行 AMQP 操作 | | 应用启动即崩溃 | 改为在 `IHostedService.StartAsync` 中异步+重试初始化资源 | | 缺乏容错 | 添加最多 N 次重试机制,并记录详细日志 | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值