Sử dụng SignalR với chuyển đổi dự phòng messagebus Redis bằng BookSleeve's ConnectionUtils.Connect ()


112

Tôi đang cố gắng tạo một kịch bản chuyển đổi dự phòng bus thông báo Redis bằng ứng dụng SignalR.

Lúc đầu, chúng tôi đã thử chuyển đổi dự phòng bộ cân bằng tải phần cứng đơn giản, chỉ đơn giản là giám sát hai máy chủ Redis. Ứng dụng SignalR đã chỉ đến điểm cuối HLB số ít. Sau đó, tôi đã làm hỏng một máy chủ nhưng không thể nhận thành công bất kỳ thư nào thông qua máy chủ Redis thứ hai mà không tái chế nhóm ứng dụng SignalR. Có lẽ điều này là do nó cần đưa ra các lệnh thiết lập cho bus thông báo Redis mới.

Đối với SignalR RC1, Microsoft.AspNet.SignalR.Redis.RedisMessageBussử dụng Booksleeve RedisConnection()để kết nối với một Redis duy nhất cho quán rượu / phụ.

Tôi đã tạo một lớp mới, RedisMessageBusCluster()sử dụng Booksleeve ConnectionUtils.Connect()để kết nối với một lớp trong một cụm máy chủ 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 có cơ chế riêng để xác định chủ và sẽ tự động bị lỗi khi chuyển sang máy chủ khác và hiện đang thử nghiệm điều này với SignalR.Chat.

Trong web.config, tôi đặt danh sách các máy chủ có sẵn:

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

Sau đó trong 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);

Tôi đã thêm hai phương pháp bổ sung vào 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;
}

Bây giờ vấn đề là khi tôi đã bật một số điểm ngắt, cho đến sau khi tên người dùng đã được thêm vào, sau đó tắt tất cả các điểm ngắt, ứng dụng hoạt động như mong đợi. Tuy nhiên, với việc tắt các điểm ngắt ngay từ đầu, dường như có một số điều kiện chạy đua có thể bị lỗi trong quá trình kết nối.

Do đó, trong 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);
        }
    });

Tôi đã thử thêm cả a Task.Waitvà thậm chí là một bổ sung Sleep()(không được hiển thị ở trên) - đang chờ / vv, nhưng vẫn gặp lỗi.

Lỗi lặp lại dường như nằm trong 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");
        }

Trường hợp ngoại lệ hàng đợi đã đóng đang được ném.

Tôi thấy trước một vấn đề khác: Vì kết nối Redis được thực hiện nên Application_Start()có thể có một số vấn đề khi "kết nối lại" với máy chủ khác. Tuy nhiên, tôi nghĩ rằng điều này hợp lệ khi sử dụng số ít RedisConnection(), nơi chỉ có một kết nối để lựa chọn. Tuy nhiên, với sự giới thiệu của ConnectionUtils.Connect()tôi, tôi muốn nghe từ @dfowlerhoặc những người SignalR khác về cách xử lý tình huống này trong SignalR.


Tôi sẽ xem xét, nhưng: điều đầu tiên xảy ra là bạn không cần phải gọi Openvì kết nối bạn có đã được mở. Tuy nhiên, tôi sẽ không thể nhìn ngay lập tức vì tôi đang chuẩn bị cho chuyến bay
Marc Gravell

Tôi tin rằng có hai vấn đề ở đây. 1) cách Booksleeve đối phó với chuyển đổi dự phòng; 2) Cách SignalR sử dụng con trỏ để theo dõi khách hàng. Khi một bus thông báo mới được khởi tạo, tất cả các con trỏ từ mb1 sẽ không thoát trên mb2. Do đó, khi đặt lại nhóm ứng dụng SignalR, nó sẽ bắt đầu hoạt động - chứ không phải trước đó, điều này rõ ràng không phải là một lựa chọn khả thi.
ElHaix

2
Liên kết mô tả cách SignalR sử dụng con trỏ: stackoverflow.com/questions/13054592/…
ElHaix

Hãy thử sử dụng phiên bản mới nhất của xe buýt thông báo redis. Nó hỗ trợ truyền trong nhà máy kết nối và xử lý việc thử lại kết nối khi máy chủ gặp sự cố.
davidfowl

Bạn có một liên kết cho các ghi chú phát hành? Cảm ơn.
ElHaix

Câu trả lời:


13

Nhóm SignalR hiện đã triển khai hỗ trợ cho nhà máy sản xuất kết nối tùy chỉnh với StackExchange.Redis , người kế nhiệm BookSleeve, hỗ trợ các kết nối Redis dự phòng thông qua ConnectionMultiplexer.

Vấn đề ban đầu gặp phải là mặc dù đã tạo các phương thức mở rộng của riêng tôi trong BookSleeve để chấp nhận một tập hợp các máy chủ, nhưng không thể vượt qua lỗi.

Giờ đây, với sự phát triển của BookSleeve thành StackExchange.Redis, giờ đây chúng ta có thể cấu hình bộ sưu tập các máy chủ / cổng ngay trong quá trình Connectkhởi tạo.

Việc triển khai mới đơn giản hơn nhiều so với con đường mà tôi đã trải qua, trong việc tạo ra một UseRedisClusterphương thức và việc giảm mạnh phần back-end hiện hỗ trợ việc thực hiện fail-over:

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

StackExchange.Redis cũng cho phép cấu hình thủ công bổ sung như được nêu trong Automatic and Manual Configuration phần của tài liệu:

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"
};

Về bản chất, khả năng khởi tạo môi trường mở rộng quy mô SignalR của chúng tôi với một tập hợp các máy chủ hiện giải quyết được vấn đề ban đầu.


Tôi có nên thưởng cho câu trả lời của bạn với tiền thưởng 500 đại diện không? ;)
nicael

Vâng, nếu bạn tin rằng bây giờ là câu trả lời :)
ElHaix

@ElHaix vì bạn đã đặt câu hỏi nên có lẽ bạn đủ điều kiện nhất để nói liệu câu trả lời của bạn có kết luận hay chỉ là một phần trong câu đố - Tôi khuyên bạn nên thêm một câu để cho biết liệu và có thể nó đã giải quyết vấn đề của bạn như thế nào
Lars Höppner

Vì thế? Tiền thưởng? Hoặc tôi có thể đợi cho đến khi nó thu hút nhiều sự chú ý hơn.
nicael

Tôi có thiếu thứ gì đó không hay chỉ có trong một nhánh tính năng, không phải trong gói nuget chính (2.1)? Ngoài ra, có vẻ như trong nhánh bug-stackexchange ( github.com/SignalR/SignalR/tree/bug-stackexchange/src/… ), chưa có cách nào trong lớp RedisScaleoutConfiguration để cung cấp bộ ghép kênh của riêng bạn.
Steve,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.