BookSleeve'in ConnectionUtils.Connect () kullanarak SignalR'yi Redis messagebus yük devretme ile kullanma


112

Bir SignalR uygulamasıyla Redis mesaj veri yolu yük devretme senaryosu oluşturmaya çalışıyorum.

İlk olarak, iki Redis sunucusunu izleyen basit bir donanım yük dengeleyici yük devretmeyi denedik. SignalR uygulaması, tekil HLB uç noktasına işaret etti. Daha sonra bir sunucuda başarısız oldum, ancak SignalR uygulama havuzunu geri dönüştürmeden ikinci Redis sunucusundan herhangi bir mesajı başarılı bir şekilde alamadım. Muhtemelen bunun nedeni kurulum komutlarını yeni Redis mesaj veriyoluna yayınlaması gerekmesidir.

SignalR RC1'den itibaren, pub / sub için tek bir Microsoft.AspNet.SignalR.Redis.RedisMessageBusRedis'e RedisConnection()bağlanmak için Booksleeve's kullanır .

Redis sunucuları kümesindeki birine bağlanmak için RedisMessageBusCluster()Booksleeve's kullanan yeni bir sınıf oluşturdum ConnectionUtils.Connect().

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'nin bir ana sunucu belirlemek için kendi mekanizması vardır ve otomatik olarak başka bir sunucuya yük devredecek ve şimdi bunu ile test ediyorum SignalR.Chat.

İçinde web.configmevcut sunucuların listesini ayarlıyorum:

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

Sonra içinde 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);

İki ek yöntem ekledim 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;
}

Şimdi sorun şu ki, birkaç kesme noktası etkinleştirdiğimde, bir kullanıcı adı eklenene kadar ve ardından tüm kesme noktalarını devre dışı bırakın, uygulama beklendiği gibi çalışıyor. Bununla birlikte, başlangıçtan itibaren devre dışı bırakılan kesme noktaları ile, bağlantı işlemi sırasında başarısız olabilecek bazı yarış koşulları var gibi görünüyor.

Böylece, içinde 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);
        }
    });

Hem a hem de Task.Waitbir ek Sleep()(yukarıda gösterilmemiştir) eklemeyi denedim - bunlar / etc bekliyor, ancak hala hata alıyorlar.

Yinelenen hata Booksleeve.MessageQueue.cs71'de görünüyor :

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

Kapalı bir sıra istisnasının atıldığı yer.

Başka bir sorun öngörüyorum: Redis bağlantısı yapıldığı Application_Start()için başka bir sunucuya "yeniden bağlanma" konusunda bazı sorunlar olabilir. Bununla birlikte, RedisConnection()seçim yapabileceğiniz tek bir bağlantının olduğu tekil kullanıldığında bunun geçerli olduğunu düşünüyorum . Bununla birlikte, bu senaryonun SignalR'de nasıl işlendiğine dair diğer SignalR çalışanlarından veya diğerlerinden ConnectionUtils.Connect()duymak istiyorum @dfowler.


Bir göz atacağım, ancak: İlk olarak aramanıza gerek kalmıyor, Opençünkü bağlantınız zaten açık olmalı . Yine de uçuşa hazırlanırken hemen bakamayacağım
Marc Gravell

Burada iki sorun olduğuna inanıyorum. 1) Booksleeve'in bir yük devretme ile nasıl başa çıktığı; 2) SignalR, istemcileri takip etmek için imleçleri nasıl kullanır? Yeni bir mesaj veriyolu başlatıldığında, mb1'deki tüm imleçler mb2'de bulunmaz. Bu nedenle, SignalR uygulama havuzunu sıfırlarken, çalışmaya başlayacaktır - daha önce değil, bu açıkça uygun bir seçenek değildir.
ElHaix

2
SignalR'nin imleçleri nasıl kullandığını açıklayan bağlantı: stackoverflow.com/questions/13054592/…
ElHaix

Redis mesaj veriyolunun en son sürümünü kullanmayı deneyin. Bir bağlantı fabrikasında geçişi destekler ve sunucu çöktüğünde bağlanmak için yeniden denemeyi gerçekleştirir.
davidfowl

Sürüm notları için bir bağlantınız var mı? Teşekkürler.
ElHaix

Yanıtlar:


13

SignalR ekibi şimdi ile özel bir bağlantısı fabrikası için destek uygulamıştır StackExchange.Redis , ConnectionMultiplexer yoluyla gereksiz Redis bağlantılarını destekleyen BookSleeve halefi.

Karşılaşılan ilk sorun, bir sunucu koleksiyonunu kabul etmek için BookSleeve'de kendi uzantı yöntemlerimi oluşturmama rağmen, yük devretmenin mümkün olmamasıydı.

Şimdi, BookSleeve'in StackExchange.Redis'e evrilmesiyle birlikte, artık sunucuların / bağlantı noktalarının koleksiyonunu başlangıçta yapılandırabilirizConnect .

Yeni uygulama, bir UseRedisClusteryöntem oluştururken gittiğim yoldan çok daha basit ve arka uç tüyleri artık gerçek yük devretmeyi destekliyor:

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

StackExchange.Redis Automatic and Manual Configuration, dokümantasyon bölümünde belirtildiği gibi ek manuel yapılandırmaya da izin verir :

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

Temelde, SignalR genişleme ortamımızı bir sunucu koleksiyonuyla başlatma yeteneği, artık ilk sorunu çözüyor.


Cevabınızı 500 temsilci ödülü ile ödüllendirmeli miyim? ;)
nicael

Peki, şimdi cevabın bu olduğuna inanıyorsanız :)
ElHaix

@ElHaix soruyu sorduğunuzdan beri, muhtemelen cevabınızın kesin mi yoksa sadece bulmacanın bir parçası mı olduğunu söyleyebilecek en nitelikli kişisiniz - probleminizi çözüp çözmediğini ve muhtemelen nasıl çözdüğünü belirtmek için bir cümle eklemenizi öneririm
Lars Höppner

Yani? Ödül ikramiyesi mi? Ya da daha fazla dikkat çekene kadar bekleyebilirim.
nicael

Bir şey mi eksik yoksa bu ana (2.1) nuget paketinde değil, yalnızca bir özellik dalında mı? Ayrıca, bug- stackexchange ( github.com/SignalR/SignalR/tree/bug-stackexchange/src/… ) dalında, RedisScaleoutConfiguration sınıfında kendi çoklayıcınızı sağlamanın bir yolu yok gibi görünüyor .
Steve
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.