Skip to content

Commit

Permalink
Move EndpointRoutingMode to EndpointRouterDecorator
Browse files Browse the repository at this point in the history
  • Loading branch information
bjqian committed Oct 17, 2023
1 parent 1f72879 commit 85c9122
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,29 @@
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<ServiceOptions> options)
{
_mode = options?.Value.EndpointRoutingMode ?? EndpointRoutingMode.Weighted;
}

/// <summary>
/// Select an endpoint for negotiate request according to the mode
/// Select an endpoint for negotiate request
/// </summary>
/// <param name="context">The http context of the incoming request</param>
/// <param name="endpoints">All the available endpoints</param>
public ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable<ServiceEndpoint> endpoints)
public virtual ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable<ServiceEndpoint> endpoints)
{
// get primary endpoints snapshot
var availableEndpoints = GetNegotiateEndpoints(endpoints);
return _mode switch
{
EndpointRoutingMode.Random => GetEndpointRandomly(availableEndpoints),
EndpointRoutingMode.LeastConnection => GetEndpointWithLeastConnection(availableEndpoints),
_ => GetEndpointAccordingToWeight(availableEndpoints),
};
return GetEndpointAccordingToWeight(availableEndpoints);
}

/// <summary>
/// Only primary endpoints will be returned by client /negotiate
/// If no primary endpoint is available, promote one secondary endpoint
/// </summary>
/// <returns>The available endpoints</returns>
private ServiceEndpoint[] GetNegotiateEndpoints(IEnumerable<ServiceEndpoint> endpoints)
protected ServiceEndpoint[] GetNegotiateEndpoints(IEnumerable<ServiceEndpoint> endpoints)
{
var primary = endpoints.Where(s => s.Online && s.EndpointType == EndpointType.Primary).ToArray();
if (primary.Length > 0)
Expand Down Expand Up @@ -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];
Expand All @@ -89,38 +76,5 @@ private ServiceEndpoint GetEndpointAccordingToWeight(ServiceEndpoint[] available

return availableEndpoints[Array.FindLastIndex(we, x => x <= index) + 1];
}

/// <summary>
/// Choose endpoint with least connection count
/// </summary>
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)];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;

namespace Microsoft.Azure.SignalR
{
Expand All @@ -13,12 +12,23 @@ public class EndpointRouterDecorator : IEndpointRouter

public EndpointRouterDecorator(IEndpointRouter router = null)
{
_inner = router ?? new DefaultEndpointRouter(null);
_inner = router ?? new DefaultEndpointRouter();
}

public EndpointRouterDecorator(IEndpointRouter router = null, IOptions<ServiceOptions> options = null)
public EndpointRouterDecorator(EndpointRoutingMode endpointRoutingMode)
{
_inner = router ?? new DefaultEndpointRouter(options);
switch (endpointRoutingMode)
{
case EndpointRoutingMode.Random:
_inner = new RandomEndpointRouter();
break;
case EndpointRoutingMode.LeastConnection:
_inner = new LeastConnectionEndpointRouter();
break;
default:
_inner = new DefaultEndpointRouter();
break;
}
}

public virtual ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable<ServiceEndpoint> endpoints)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;

namespace Microsoft.Azure.SignalR
{
internal class LeastConnectionEndpointRouter : DefaultEndpointRouter
{
/// <summary>
/// Select an endpoint for negotiate request
/// </summary>
/// <param name="context">The http context of the incoming request</param>
/// <param name="endpoints">All the available endpoints</param>
public override ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable<ServiceEndpoint> endpoints)
{
// get primary endpoints snapshot
var availableEndpoints = GetNegotiateEndpoints(endpoints);
return GetEndpointWithLeastConnection(availableEndpoints);
}

/// <summary>
/// Choose endpoint with least connection count
/// </summary>
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 availableEndpoints[StaticRandom.Next(availableEndpoints.Length)];
}

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];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using Microsoft.AspNetCore.Http;

namespace Microsoft.Azure.SignalR
{
internal class RandomEndpointRouter : DefaultEndpointRouter
{
/// <summary>
/// Select an endpoint for negotiate request according to the mode
/// </summary>
/// <param name="context">The http context of the incoming request</param>
/// <param name="endpoints">All the available endpoints</param>
public override ServiceEndpoint GetNegotiateEndpoint(HttpContext context, IEnumerable<ServiceEndpoint> endpoints)
{
// get primary endpoints snapshot
var availableEndpoints = GetNegotiateEndpoints(endpoints);
return availableEndpoints[StaticRandom.Next(availableEndpoints.Length)];
}
}
}
7 changes: 2 additions & 5 deletions test/Microsoft.Azure.SignalR.Tests/EndpointRouterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,9 @@ private static IEndpointRouter GetEndpointRouter(EndpointRoutingMode mode)
{
var config = new ConfigurationBuilder().Build();
var serviceProvider = new ServiceCollection()
.AddSingleton<IEndpointRouter>(new EndpointRouterDecorator(mode))
.AddSignalR()
.AddAzureSignalR(
o =>
{
o.EndpointRoutingMode = mode;
})
.AddAzureSignalR()
.Services
.AddSingleton<IConfiguration>(config)
.BuildServiceProvider();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<IServiceConnection> {
Expand Down

0 comments on commit 85c9122

Please sign in to comment.