বুকস্লিভের কানেকশন ইউটিস.কনেক্ট () ব্যবহার করে রেডিস মেসেজবাস ফেইলওভার সহ সিগন্যালআর ব্যবহার


112

আমি একটি সিগন্যালআর অ্যাপ্লিকেশন সহ একটি রেডিস বার্তা বাস ফেইলওভার দৃশ্য তৈরি করার চেষ্টা করছি।

প্রথমে, আমরা একটি সাধারণ হার্ডওয়্যার লোড-ব্যালেন্সার ব্যর্থতা চেষ্টা করেছিলাম, যা কেবল দুটি রেডিস সার্ভারকে পর্যবেক্ষণ করে। সিগন্যালআর অ্যাপ্লিকেশনটি সিঙ্গুলার এইচএলবি শেষ পয়েন্টে নির্দেশ করেছে। আমি তখন একটি সার্ভার ব্যর্থ হয়েছি, কিন্তু দ্বিতীয় রেডিস সার্ভারে সিগন্যালআর অ্যাপ পুলটি পুনর্ব্যবহার না করে সফলভাবে কোনও বার্তা পেতে সক্ষম হয়েছি। সম্ভবত এটি কারণ এটি নতুন রেডিস বার্তা বাসে সেটআপ কমান্ড জারি করা প্রয়োজন।

সিগন্যালআরসি 1 হিসাবে, পাব / সাবের জন্য একক Microsoft.AspNet.SignalR.Redis.RedisMessageBusরেডিসের RedisConnection()সাথে সংযোগ করতে বুকস্লিভগুলি ব্যবহার করে ।

আমি একটি নতুন ক্লাস তৈরি করেছি, RedisMessageBusCluster()যা 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);
        }
    }
}

মাস্টার নির্ধারণের জন্য Bookleeve এর নিজস্ব ব্যবস্থা আছে এবং স্বয়ংক্রিয়ভাবে অন্য সার্ভারে ব্যর্থ হবে এবং এখন এটি দিয়ে পরীক্ষা করছি 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");
        }

যেখানে বন্ধ কাতারের ব্যতিক্রম ছোঁড়া হচ্ছে।

আমি অন্য সমস্যার পূর্বে ধারণা করছি: রেডিস সংযোগটি যেহেতু তৈরি হয়েছে Application_Start()সেখানে অন্য সার্ভারের সাথে "পুনরায় সংযোগ স্থাপন" করার ক্ষেত্রে কিছু সমস্যা থাকতে পারে। যাইহোক, আমি মনে করি একক ব্যবহার করার সময় এটি বৈধ RedisConnection(), যেখানে চয়ন করার জন্য কেবল একটি সংযোগ রয়েছে। তবে ConnectionUtils.Connect()আমি @dfowlerকীভাবে সিগন্যালআর-এ এই পরিস্থিতিটি পরিচালনা করা হয় সে সম্পর্কে বা অন্যান্য সিগন্যালআর ছেলেদের কাছ থেকে শুনতে চাই ।


আমি একবার খেয়াল করব, তবে: প্রথমটি ঘটে যা হ'ল আপনার যে Openসংযোগটি রয়েছে তা ইতিমধ্যে উন্মুক্ত হওয়া থেকে আপনার কল করার দরকার নেই । আমি তাত্ক্ষণিকভাবে দেখতে সক্ষম হব না, যদিও আমি বিমানের জন্য প্রস্তুত হচ্ছি
মার্ক গ্র্যাভেল

আমি বিশ্বাস করি এখানে দুটি বিষয় আছে। 1) বুকসলেভ কীভাবে একটি ব্যর্থতার সাথে আচরণ করছে; 2) কীভাবে সিগন্যালআর ক্লায়েন্টদের নজর রাখার জন্য কার্সার ব্যবহার করে। যখন কোনও নতুন বার্তা বাস শুরু করা হয়, এমবি 1 থেকে সমস্ত কার্সার এমবি 2-তে প্রস্থান করে না। সুতরাং সিগন্যালআর অ্যাপ পুলটি পুনরায় সেট করার সময়, এটি কাজ শুরু করবে - এর আগে নয়, যা সম্ভবত কার্যকর একটি বিকল্প নয়।
এলহাইক্স

2
লিংক বর্ণনা কিভাবে SignalR এক্সিকিউটেবল-এর পাথ ব্যবহার করে: stackoverflow.com/questions/13054592/...
ElHaix

Redis বার্তা বাসের সর্বশেষতম সংস্করণটি ব্যবহার করে দেখুন। এটি কোনও সংযোগ কারখানায় যাওয়ার সমর্থন করে এবং সার্ভারটি ডাউন হয়ে যাওয়ার পরে সংযোগ করার জন্য পুনরায় চেষ্টা করে handle
ডেভিডফুল

প্রকাশের নোটগুলির জন্য আপনার কি কোনও লিঙ্ক আছে? ধন্যবাদ।
এলহাইক্স

উত্তর:


13

SignalR দল এখন একটি কাস্টম সংযোগ কারখানা জন্য সমর্থন বাস্তবায়ন করেছে StackExchange.Redis , BookSleeve উত্তরসূরি, যা ConnectionMultiplexer মাধ্যমে অপ্রয়োজনীয় Redis সংযোগ সমর্থন করে।

প্রাথমিক সমস্যার মুখোমুখি হ'ল সার্ভারের সংগ্রহ গ্রহণ করার জন্য বুকস্লিভে আমার নিজের এক্সটেনশন পদ্ধতি তৈরি করা সত্ত্বেও, ব্যর্থতা সম্ভব ছিল না।

এখন, বুকস্লিভ স্ট্যাকএক্সচেঞ্জ.রিডিসের বিবর্তনের সাথে সাথে, আমরা এখন আরম্ভের সূচনাতেই সার্ভার / পোর্ট সংগ্রহের কনফিগার করতে পারি Connect

একটি UseRedisClusterপদ্ধতি তৈরির ক্ষেত্রে আমি যে রাস্তায় যাচ্ছিলাম তার চেয়ে নতুন বাস্তবায়নটি অনেক সহজ and এবং ব্যাক-এন্ড প্লামিং এখন সত্য ব্যর্থতার পক্ষে সমর্থন করে:

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

স্ট্যাক এক্সচেঞ্জ.রিডিস Automatic and Manual Configurationডকুমেন্টেশনের বিভাগে বর্ণিত অতিরিক্ত ম্যানুয়াল কনফিগারেশনের জন্যও অনুমতি দেয় :

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

সংক্ষেপে, সার্ভারের সংগ্রহের সাথে আমাদের সিগন্যালআর স্কেল-আউট পরিবেশকে আরম্ভ করার ক্ষমতা এখন প্রাথমিক সমস্যা সমাধান করে।


আপনার উত্তরটি আমি 500 রিপ্রেস অনুগ্রহের সাথে পুরস্কৃত করব? ;)
নিকেল

ঠিক আছে, আপনি যদি বিশ্বাস করেন তবে এখনই
এটির

@ এলহাইক্স যেহেতু আপনি প্রশ্ন জিজ্ঞাসা করেছেন, আপনি সম্ভবত আপনার উত্তরটি চূড়ান্ত কিনা বা এটি ধাঁধার মধ্যে কেবল একটি টুকরো কিনা তা বলতে আপনি সবচেয়ে দক্ষ - আমি আপনার বাক্যটি কীভাবে সমাধান করেছে এবং সম্ভবত এটি কীভাবে সমাধান হয়েছে তা নির্দেশ করার জন্য একটি বাক্য যুক্ত করার পরামর্শ দিচ্ছি
লার্স হাপ্পনার

তাই? পুরষ্কার অনুগ্রহ? অথবা এটি আরও মনোযোগ আকর্ষণ না করা পর্যন্ত আমি অপেক্ষা করতে পারি।
নিকেল

আমি কি কিছু মিস করছি বা এটি কেবল কোনও বৈশিষ্ট্য শাখায় রয়েছে, মূল (২.১) নুগেট প্যাকেজে নেই? এছাড়াও, এটি বাগ- স্ট্যাকেক্সচেঞ্জের ( github.com/SignalR/SignalR/tree/bug-stackexchange/src/… ) শাখার মতো মনে হচ্ছে , আপনার নিজস্ব মাল্টিপ্লেক্সার সরবরাহ করার জন্য রেডিসক্লেআউট কনফিগারেশন ক্লাসে এখনও কোনও উপায় নেই।
স্টিভ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.