gpt4 book ai didi

c# - 使用 BookSleeve 的 ConnectionUtils.Connect() 将 SignalR 与 Redis 消息总线故障转移结合使用

转载 作者:IT王子 更新时间:2023-10-29 03:34:34 26 4
gpt4 key购买 nike

我正在尝试使用 SignalR 应用程序创建 Redis 消息总线故障转移方案。

首先,我们尝试了一个简单的硬件负载平衡器故障转移,它只监控两个 Redis 服务器。 SignalR 应用程序指向单个 HLB 端点。然后我使一台服务器失败,但在不回收 SignalR 应用程序池的情况下无法在第二台 Redis 服务器上成功获取任何消息。据推测,这是因为它需要向新的 Redis 消息总线发出设置命令。

自 SignalR RC1 起,Microsoft.AspNet.SignalR.Redis.RedisMessageBus使用 Booksleeve 的 RedisConnection()连接到单个 Redis 以进行发布/订阅。

我创建了一个新类,RedisMessageBusCluster()使用 Booksleeve 的 ConnectionUtils.Connect()连接到 Redis 服务器集群中的一个服务器。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BookSleeve;
using Microsoft.AspNet.SignalR.Infrastructure;

namespace Microsoft.AspNet.SignalR.Redis
{
/// <summary>
/// WIP: Getting scaleout for Redis working
/// </summary>
public class RedisMessageBusCluster : ScaleoutMessageBus
{
private readonly int _db;
private readonly string[] _keys;
private RedisConnection _connection;
private RedisSubscriberConnection _channel;
private Task _connectTask;

private readonly TaskQueue _publishQueue = new TaskQueue();

public RedisMessageBusCluster(string serverList, int db, IEnumerable<string> keys, IDependencyResolver resolver)
: base(resolver)
{
_db = db;
_keys = keys.ToArray();

// uses a list of connections
_connection = ConnectionUtils.Connect(serverList);

//_connection = new RedisConnection(host: server, port: port, password: password);

_connection.Closed += OnConnectionClosed;
_connection.Error += OnConnectionError;


// Start the connection - TODO: can remove this Open as the connection is already opened, but there's the _connectTask is used later on
_connectTask = _connection.Open().Then(() =>
{
// Create a subscription channel in redis
_channel = _connection.GetOpenSubscriberChannel();

// Subscribe to the registered connections
_channel.Subscribe(_keys, OnMessage);

// Dirty hack but it seems like subscribe returns before the actual
// subscription is properly setup in some cases
while (_channel.SubscriptionCount == 0)
{
Thread.Sleep(500);
}
});
}


protected override Task Send(Message[] messages)
{
return _connectTask.Then(msgs =>
{
var taskCompletionSource = new TaskCompletionSource<object>();

// Group messages by source (connection id)
var messagesBySource = msgs.GroupBy(m => m.Source);

SendImpl(messagesBySource.GetEnumerator(), taskCompletionSource);

return taskCompletionSource.Task;
},
messages);
}

private void SendImpl(IEnumerator<IGrouping<string, Message>> enumerator, TaskCompletionSource<object> taskCompletionSource)
{
if (!enumerator.MoveNext())
{
taskCompletionSource.TrySetResult(null);
}
else
{
IGrouping<string, Message> group = enumerator.Current;

// Get the channel index we're going to use for this message
int index = Math.Abs(group.Key.GetHashCode()) % _keys.Length;

string key = _keys[index];

// Increment the channel number
_connection.Strings.Increment(_db, key)
.Then((id, k) =>
{
var message = new RedisMessage(id, group.ToArray());

return _connection.Publish(k, message.GetBytes());
}, key)
.Then((enumer, tcs) => SendImpl(enumer, tcs), enumerator, taskCompletionSource)
.ContinueWithNotComplete(taskCompletionSource);
}
}

private void OnConnectionClosed(object sender, EventArgs e)
{
// Should we auto reconnect?
if (true)
{
;
}
}

private void OnConnectionError(object sender, BookSleeve.ErrorEventArgs e)
{
// How do we bubble errors?
if (true)
{
;
}
}

private void OnMessage(string key, byte[] data)
{
// The key is the stream id (channel)
var message = RedisMessage.Deserialize(data);

_publishQueue.Enqueue(() => OnReceived(key, (ulong)message.Id, message.Messages));
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_channel != null)
{
_channel.Unsubscribe(_keys);
_channel.Close(abort: true);
}

if (_connection != null)
{
_connection.Close(abort: true);
}
}

base.Dispose(disposing);
}
}
}

Booksleeve 有自己的确定主服务器的机制,并且会自动故障转移到另一台服务器,现在正在使用 SignalR.Chat 进行测试.

web.config ,我设置可用服务器列表:

<add key="redis.serverList" value="dbcache1.local:6379,dbcache2.local:6379"/>

然后在Application_Start() :

        // Redis cluster server list
string redisServerlist = ConfigurationManager.AppSettings["redis.serverList"];

List<string> eventKeys = new List<string>();
eventKeys.Add("SignalR.Redis.FailoverTest");
GlobalHost.DependencyResolver.UseRedisCluster(redisServerlist, eventKeys);

我向 Microsoft.AspNet.SignalR.Redis.DependencyResolverExtensions 添加了两个额外的方法:

public static IDependencyResolver UseRedisCluster(this IDependencyResolver resolver, string serverList, IEnumerable<string> eventKeys)
{
return UseRedisCluster(resolver, serverList, db: 0, eventKeys: eventKeys);
}

public static IDependencyResolver UseRedisCluster(this IDependencyResolver resolver, string serverList, int db, IEnumerable<string> eventKeys)
{
var bus = new Lazy<RedisMessageBusCluster>(() => new RedisMessageBusCluster(serverList, db, eventKeys, resolver));
resolver.Register(typeof(IMessageBus), () => bus.Value);

return resolver;
}

现在的问题是,当我启用多个断点时,直到添加了用户名,然后禁用所有断点,应用程序才能按预期运行。但是,由于从一开始就禁用了断点,因此在连接过程中似乎存在一些可能会失败的竞争条件。

因此,在 RedisMessageCluster() :

    // Start the connection
_connectTask = _connection.Open().Then(() =>
{
// Create a subscription channel in redis
_channel = _connection.GetOpenSubscriberChannel();

// Subscribe to the registered connections
_channel.Subscribe(_keys, OnMessage);

// Dirty hack but it seems like subscribe returns before the actual
// subscription is properly setup in some cases
while (_channel.SubscriptionCount == 0)
{
Thread.Sleep(500);
}
});

我尝试同时添加 Task.Wait ,甚至还有一个 Sleep() (上面未显示)- 正在等待/等等,但仍然出现错误。

重复出现的错误似乎在 Booksleeve.MessageQueue.cs 中~ln 71:

A first chance exception of type 'System.InvalidOperationException' occurred in BookSleeve.dll
iisexpress.exe Error: 0 : SignalR exception thrown by Task: System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: The queue is closed
at BookSleeve.MessageQueue.Enqueue(RedisMessage item, Boolean highPri) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\MessageQueue.cs:line 71
at BookSleeve.RedisConnectionBase.EnqueueMessage(RedisMessage message, Boolean queueJump) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\RedisConnectionBase.cs:line 910
at BookSleeve.RedisConnectionBase.ExecuteInt64(RedisMessage message, Boolean queueJump) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\RedisConnectionBase.cs:line 826
at BookSleeve.RedisConnection.IncrementImpl(Int32 db, String key, Int64 value, Boolean queueJump) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\IStringCommands.cs:line 277
at BookSleeve.RedisConnection.BookSleeve.IStringCommands.Increment(Int32 db, String key, Int64 value, Boolean queueJump) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\IStringCommands.cs:line 270
at Microsoft.AspNet.SignalR.Redis.RedisMessageBusCluster.SendImpl(IEnumerator`1 enumerator, TaskCompletionSource`1 taskCompletionSource) in c:\Projects\Frameworks\SignalR\SignalR.1.0RC1\SignalR\src\Microsoft.AspNet.SignalR.Redis\RedisMessageBusCluster.cs:line 90
at Microsoft.AspNet.SignalR.Redis.RedisMessageBusCluster.<Send>b__2(Message[] msgs) in c:\Projects\Frameworks\SignalR\SignalR.1.0RC1\SignalR\src\Microsoft.AspNet.SignalR.Redis\RedisMessageBusCluster.cs:line 67
at Microsoft.AspNet.SignalR.TaskAsyncHelper.GenericDelegates`4.<>c__DisplayClass57.<ThenWithArgs>b__56() in c:\Projects\Frameworks\SignalR\SignalR.1.0RC1\SignalR\src\Microsoft.AspNet.SignalR.Core\TaskAsyncHelper.cs:line 893
at Microsoft.AspNet.SignalR.TaskAsyncHelper.TaskRunners`2.<>c__DisplayClass42.<RunTask>b__41(Task t) in c:\Projects\Frameworks\SignalR\SignalR.1.0RC1\SignalR\src\Microsoft.AspNet.SignalR.Core\TaskAsyncHelper.cs:line 821
--- End of inner exception stack trace ---
---> (Inner Exception #0) System.InvalidOperationException: The queue is closed
at BookSleeve.MessageQueue.Enqueue(RedisMessage item, Boolean highPri) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\MessageQueue.cs:line 71
at BookSleeve.RedisConnectionBase.EnqueueMessage(RedisMessage message, Boolean queueJump) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\RedisConnectionBase.cs:line 910
at BookSleeve.RedisConnectionBase.ExecuteInt64(RedisMessage message, Boolean queueJump) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\RedisConnectionBase.cs:line 826
at BookSleeve.RedisConnection.IncrementImpl(Int32 db, String key, Int64 value, Boolean queueJump) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\IStringCommands.cs:line 277
at BookSleeve.RedisConnection.BookSleeve.IStringCommands.Increment(Int32 db, String key, Int64 value, Boolean queueJump) in c:\Projects\Frameworks\BookSleeve-1.2.0.5\BookSleeve\IStringCommands.cs:line 270
at Microsoft.AspNet.SignalR.Redis.RedisMessageBusCluster.SendImpl(IEnumerator`1 enumerator, TaskCompletionSource`1 taskCompletionSource) in c:\Projects\Frameworks\SignalR\SignalR.1.0RC1\SignalR\src\Microsoft.AspNet.SignalR.Redis\RedisMessageBusCluster.cs:line 90
at Microsoft.AspNet.SignalR.Redis.RedisMessageBusCluster.<Send>b__2(Message[] msgs) in c:\Projects\Frameworks\SignalR\SignalR.1.0RC1\SignalR\src\Microsoft.AspNet.SignalR.Redis\RedisMessageBusCluster.cs:line 67
at Microsoft.AspNet.SignalR.TaskAsyncHelper.GenericDelegates`4.<>c__DisplayClass57.<ThenWithArgs>b__56() in c:\Projects\Frameworks\SignalR\SignalR.1.0RC1\SignalR\src\Microsoft.AspNet.SignalR.Core\TaskAsyncHelper.cs:line 893
at Microsoft.AspNet.SignalR.TaskAsyncHelper.TaskRunners`2.<>c__DisplayClass42.<RunTask>b__41(Task t) in c:\Projects\Frameworks\SignalR\SignalR.1.0RC1\SignalR\src\Microsoft.AspNet.SignalR.Core\TaskAsyncHelper.cs:line 821<---



public void Enqueue(RedisMessage item, bool highPri)
{
lock (stdPriority)
{
if (closed)
{
throw new InvalidOperationException("The queue is closed");
}

抛出关闭队列异常的地方。

我预见到另一个问题:由于 Redis 连接是在 Application_Start() 中建立的“重新连接”到另一台服务器时可能会出现一些问题。但是,我认为这在使用单数 RedisConnection() 时有效。 ,其中只有一个连接可供选择。但是,随着 ConnectionUtils.Connect() 的引入我想听听 @dfowler或其他 SignalR 人员了解如何在 SignalR 中处理这种情况。

最佳答案

SignalR 团队现已通过 StackExchange.Redis 实现了对自定义连接工厂的支持。 ,BookSleeve 的继承者,它通过 ConnectionMultiplexer 支持冗余的 Redis 连接。

最初遇到的问题是,尽管在 BookSleeve 中创建了我自己的扩展方法来接受服务器集合,但无法进行故障转移。

现在,随着 BookSleeve 演变为 StackExchange.Redis,我们现在可以 configureConnect 初始化中收集服务器/端口。

新的实现比我走的路要简单得多,创建一个 UseRedisCluster 方法,后端 pluming 现在支持真正的故障转移:

var conn = ConnectionMultiplexer.Connect("redisServer1:6380,redisServer2:6380,redisServer3:6380,allowAdmin=true");

StackExchange.Redis 还允许进行额外的手动配置,如文档的自动和手动配置部分所述:

ConfigurationOptions config = new ConfigurationOptions
{
EndPoints =
{
{ "redis0", 6379 },
{ "redis1", 6380 }
},
CommandMap = CommandMap.Create(new HashSet<string>
{ // EXCLUDE a few commands
"INFO", "CONFIG", "CLUSTER",
"PING", "ECHO", "CLIENT"
}, available: false),
KeepAlive = 180,
DefaultVersion = new Version(2, 8, 8),
Password = "changeme"
};

本质上,使用一组服务器初始化我们的 SignalR 横向扩展环境的能力现在解决了最初的问题。

关于c# - 使用 BookSleeve 的 ConnectionUtils.Connect() 将 SignalR 与 Redis 消息总线故障转移结合使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14902572/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com