From 80a4fb52b4ec79bf33c4a9def871deb201ac2030 Mon Sep 17 00:00:00 2001 From: biqian Date: Tue, 17 Oct 2023 11:24:48 +0800 Subject: [PATCH 1/3] Fix DI issue in EndpointRouterDecorator --- .../EndpointRouters/EndpointRouterDecorator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs b/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs index 851f6967e..ee801cc9e 100644 --- a/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs +++ b/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; namespace Microsoft.Azure.SignalR { @@ -10,9 +11,9 @@ public class EndpointRouterDecorator : IEndpointRouter { private readonly IEndpointRouter _inner; - public EndpointRouterDecorator(IEndpointRouter router = null) + public EndpointRouterDecorator(IEndpointRouter router = null, IOptions options = null) { - _inner = router ?? new DefaultEndpointRouter(null); + _inner = router ?? new DefaultEndpointRouter(options); } public virtual ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable endpoints) From 1f72879c63489e246cb6109cd94e036ea083845b Mon Sep 17 00:00:00 2001 From: biqian Date: Tue, 17 Oct 2023 14:32:59 +0800 Subject: [PATCH 2/3] Separate EndpointRouterDecorator ctors to avoid binary breaking change --- .../EndpointRouters/EndpointRouterDecorator.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs b/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs index ee801cc9e..59ce24c23 100644 --- a/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs +++ b/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs @@ -11,6 +11,11 @@ public class EndpointRouterDecorator : IEndpointRouter { private readonly IEndpointRouter _inner; + public EndpointRouterDecorator(IEndpointRouter router = null) + { + _inner = router ?? new DefaultEndpointRouter(null); + } + public EndpointRouterDecorator(IEndpointRouter router = null, IOptions options = null) { _inner = router ?? new DefaultEndpointRouter(options); From 78852c69d44dd04bd8d71229a561319fc5265cca Mon Sep 17 00:00:00 2001 From: biqian Date: Thu, 19 Oct 2023 11:15:11 +0800 Subject: [PATCH 3/3] Refactor DefaultEndpointRouter; Revert changes for different EndpointRoutingMode --- .../Endpoints/EndpointRoutingMode.cs | 26 --------- .../EndpointRouters/DefaultEndpointRouter.cs | 52 +---------------- .../EndpointRouterDecorator.cs | 8 +-- src/Microsoft.Azure.SignalR/ServiceOptions.cs | 6 -- .../EndpointRouterTests.cs | 57 +++++++++---------- ...EndpointServiceConnectionContainerTests.cs | 2 +- 6 files changed, 31 insertions(+), 120 deletions(-) delete mode 100644 src/Microsoft.Azure.SignalR.Common/Endpoints/EndpointRoutingMode.cs diff --git a/src/Microsoft.Azure.SignalR.Common/Endpoints/EndpointRoutingMode.cs b/src/Microsoft.Azure.SignalR.Common/Endpoints/EndpointRoutingMode.cs deleted file mode 100644 index aefa0202d..000000000 --- a/src/Microsoft.Azure.SignalR.Common/Endpoints/EndpointRoutingMode.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.Azure.SignalR -{ - public enum EndpointRoutingMode - { - /// - /// Choose endpoint randomly by weight. - /// The weight is defined as (the remaining connection quota / the connection capacity). - /// This is the default mode. - /// - Weighted, - - /// - /// Choose the endpoint with least connection count. - /// This mode distributes connections evenly among endpoints. - /// - LeastConnection, - - /// - /// Choose the endpoint randomly - /// - Random, - } -} \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR/EndpointRouters/DefaultEndpointRouter.cs b/src/Microsoft.Azure.SignalR/EndpointRouters/DefaultEndpointRouter.cs index c185fccb3..a00c726cd 100644 --- a/src/Microsoft.Azure.SignalR/EndpointRouters/DefaultEndpointRouter.cs +++ b/src/Microsoft.Azure.SignalR/EndpointRouters/DefaultEndpointRouter.cs @@ -6,21 +6,13 @@ using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.Azure.SignalR.Common; -using Microsoft.Extensions.Options; namespace Microsoft.Azure.SignalR { internal class DefaultEndpointRouter : DefaultMessageRouter, IEndpointRouter { - private readonly EndpointRoutingMode _mode; - - public DefaultEndpointRouter(IOptions options) - { - _mode = options?.Value.EndpointRoutingMode ?? EndpointRoutingMode.Weighted; - } - /// - /// Select an endpoint for negotiate request according to the mode + /// Select an endpoint for negotiate request /// /// The http context of the incoming request /// All the available endpoints @@ -28,12 +20,7 @@ public ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable GetEndpointRandomly(availableEndpoints), - EndpointRoutingMode.LeastConnection => GetEndpointWithLeastConnection(availableEndpoints), - _ => GetEndpointAccordingToWeight(availableEndpoints), - }; + return GetEndpointAccordingToWeight(availableEndpoints); } /// @@ -69,7 +56,7 @@ private ServiceEndpoint GetEndpointAccordingToWeight(ServiceEndpoint[] available if (availableEndpoints.Any(endpoint => endpoint.EndpointMetrics.ConnectionCapacity == 0) || availableEndpoints.Length == 1) { - return GetEndpointRandomly(availableEndpoints); + return availableEndpoints[StaticRandom.Next(availableEndpoints.Length)]; } var we = new int[availableEndpoints.Length]; @@ -89,38 +76,5 @@ private ServiceEndpoint GetEndpointAccordingToWeight(ServiceEndpoint[] available return availableEndpoints[Array.FindLastIndex(we, x => x <= index) + 1]; } - - /// - /// Choose endpoint with least connection count - /// - private ServiceEndpoint GetEndpointWithLeastConnection(ServiceEndpoint[] availableEndpoints) - { - //first check if weight is available or necessary - if (availableEndpoints.Any(endpoint => endpoint.EndpointMetrics.ConnectionCapacity == 0) || - availableEndpoints.Length == 1) - { - return GetEndpointRandomly(availableEndpoints); - } - - var leastConnectionCount = int.MaxValue; - var index = 0; - for (var i = 0; i < availableEndpoints.Length; i++) - { - var endpointMetrics = availableEndpoints[i].EndpointMetrics; - var connectionCount = endpointMetrics.ClientConnectionCount + endpointMetrics.ServerConnectionCount; - if (connectionCount < leastConnectionCount) - { - leastConnectionCount = connectionCount; - index = i; - } - } - - return availableEndpoints[index]; - } - - private static ServiceEndpoint GetEndpointRandomly(ServiceEndpoint[] availableEndpoints) - { - return availableEndpoints[StaticRandom.Next(availableEndpoints.Length)]; - } } } \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs b/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs index 59ce24c23..3618e9361 100644 --- a/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs +++ b/src/Microsoft.Azure.SignalR/EndpointRouters/EndpointRouterDecorator.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; namespace Microsoft.Azure.SignalR { @@ -13,12 +12,7 @@ public class EndpointRouterDecorator : IEndpointRouter public EndpointRouterDecorator(IEndpointRouter router = null) { - _inner = router ?? new DefaultEndpointRouter(null); - } - - public EndpointRouterDecorator(IEndpointRouter router = null, IOptions options = null) - { - _inner = router ?? new DefaultEndpointRouter(options); + _inner = router ?? new DefaultEndpointRouter(); } public virtual ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable endpoints) diff --git a/src/Microsoft.Azure.SignalR/ServiceOptions.cs b/src/Microsoft.Azure.SignalR/ServiceOptions.cs index f301a6909..9160b147a 100644 --- a/src/Microsoft.Azure.SignalR/ServiceOptions.cs +++ b/src/Microsoft.Azure.SignalR/ServiceOptions.cs @@ -121,11 +121,5 @@ public int ConnectionCount /// Gets or sets a function which accepts and returns a bitmask combining one or more values that specify what transports the service should use to receive HTTP requests. /// public Func TransportTypeDetector { get; set; } = null; - - /// - /// Gets or sets the default endpoint routing mode when using multiple endpoints. - /// by default. - /// - public EndpointRoutingMode EndpointRoutingMode { get; set; } = EndpointRoutingMode.Weighted; } } diff --git a/test/Microsoft.Azure.SignalR.Tests/EndpointRouterTests.cs b/test/Microsoft.Azure.SignalR.Tests/EndpointRouterTests.cs index e4e4e8ffc..e329a1e8c 100644 --- a/test/Microsoft.Azure.SignalR.Tests/EndpointRouterTests.cs +++ b/test/Microsoft.Azure.SignalR.Tests/EndpointRouterTests.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.Azure.SignalR.Tests @@ -14,7 +12,7 @@ public class EndpointRouterTests [Fact] public void TestDefaultEndpointRouterWeightedMode() { - var drt = GetEndpointRouter(EndpointRoutingMode.Weighted); + var drt = new DefaultEndpointRouter(); const int loops = 20; var context = new RandomContext(); @@ -37,46 +35,43 @@ public void TestDefaultEndpointRouterWeightedMode() context.Reset(); } - [Fact] - public void TestDefaultEndpointRouterLeastConnectionMode() + [Theory] + [InlineData(200)] + [InlineData(300)] + [InlineData(400)] + [InlineData(500)] + public void TestDefaultEndpointRouterWeightedModeWhenAutoScaleIsEnabled(int quotaOfScaleUpInstance) { - var drt = GetEndpointRouter(EndpointRoutingMode.LeastConnection); + var drt = new DefaultEndpointRouter(); - const int loops = 10; + var loops = 100 + (quotaOfScaleUpInstance / 5); var context = new RandomContext(); + const double quotaBarForScaleUp = 0.8; - const string small = "small_instance", large = "large_instance"; - var uSmall = GenerateServiceEndpoint(100, 0, 90, small); - var uLarge = GenerateServiceEndpoint(1000, 0, 200, large); - var el = new List() { uLarge, uSmall }; + var endpointA = GenerateServiceEndpoint(quotaOfScaleUpInstance, 0, 80, "a"); + var endpointB = GenerateServiceEndpoint(100, 0, 70, "b"); + var endpointC = GenerateServiceEndpoint(100, 0, 70, "c"); + var el = new List() {endpointA, endpointB, endpointC}; context.BenchTest(loops, () => { var ep = drt.GetNegotiateEndpoint(null, el); ep.EndpointMetrics.ClientConnectionCount++; + var percent = (ep.EndpointMetrics.ClientConnectionCount + ep.EndpointMetrics.ServerConnectionCount) / + (double)ep.EndpointMetrics.ConnectionCapacity; + if (percent > quotaBarForScaleUp) + { + ep.EndpointMetrics.ConnectionCapacity += 100; + } + return ep.Name; }); - var uLargeCount = context.GetCount(large); - var uSmallCount = context.GetCount(small); - Assert.Equal(0, uLargeCount); - Assert.Equal(10, uSmallCount); - context.Reset(); - } - private static IEndpointRouter GetEndpointRouter(EndpointRoutingMode mode) - { - var config = new ConfigurationBuilder().Build(); - var serviceProvider = new ServiceCollection() - .AddSignalR() - .AddAzureSignalR( - o => - { - o.EndpointRoutingMode = mode; - }) - .Services - .AddSingleton(config) - .BuildServiceProvider(); + Assert.Equal(context.GetCount("a") + context.GetCount("b") + context.GetCount("c"), loops); + Assert.Equal(quotaOfScaleUpInstance, endpointA.EndpointMetrics.ConnectionCapacity); + Assert.Equal(200, endpointB.EndpointMetrics.ConnectionCapacity); + Assert.Equal(200, endpointC.EndpointMetrics.ConnectionCapacity); - return serviceProvider.GetRequiredService(); + context.Reset(); } private static ServiceEndpoint GenerateServiceEndpoint(int capacity, int serverConnectionCount, diff --git a/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs b/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs index 1d009414d..cd2a1c60e 100644 --- a/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs +++ b/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs @@ -245,7 +245,7 @@ public async Task TestContainerWithOneEndpointWithAllDisconnectedConnectionThrow { var endpoint = new ServiceEndpoint(ConnectionString1); var sem = new TestServiceEndpointManager(endpoint); - var router = new DefaultEndpointRouter(null); + var router = new DefaultEndpointRouter(); var container = new TestMultiEndpointServiceConnectionContainer("hub", e => new TestServiceConnectionContainer(new List {