Skip to content

Commit

Permalink
Basic load balancing (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tratcher authored Apr 23, 2020
1 parent 9dbb4a2 commit fee1ada
Show file tree
Hide file tree
Showing 21 changed files with 222 additions and 190 deletions.
6 changes: 6 additions & 0 deletions samples/ReverseProxy.Sample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@
"ReverseProxy": {
"Backends": {
"backend1": {
"LoadBalancing": {
"Mode": "Random"
},
"Endpoints": {
"backend1/endpoint1": {
"Address": "https://localhost:10000/"
},
"backend1/endpoint2": {
"Address": "http://localhost:10010/"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion samples/SampleServer/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:10000;http://localhost:10001"
"applicationUrl": "https://localhost:10000;https://localhost:10001;http://localhost:10010;http://localhost:10011"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public sealed class Backend : IDeepCloneable<Backend>
/// <summary>
/// Load balancing options.
/// </summary>
public LoadBalancingOptions LoadBalancingOptions { get; set; }
public LoadBalancingOptions LoadBalancing { get; set; }

/// <summary>
/// Active health checking options.
Expand All @@ -57,7 +57,7 @@ Backend IDeepCloneable<Backend>.DeepClone()
CircuitBreakerOptions = CircuitBreakerOptions?.DeepClone(),
QuotaOptions = QuotaOptions?.DeepClone(),
PartitioningOptions = PartitioningOptions?.DeepClone(),
LoadBalancingOptions = LoadBalancingOptions?.DeepClone(),
LoadBalancing = LoadBalancing?.DeepClone(),
HealthCheckOptions = HealthCheckOptions?.DeepClone(),
Endpoints = Endpoints.DeepClone(StringComparer.Ordinal),
Metadata = Metadata?.DeepClone(StringComparer.Ordinal),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.ReverseProxy.Core.Abstractions
{
/// <summary>
/// Load balancing strategies for endpoint selection.
/// </summary>
public enum LoadBalancingMode
{
/// <summary>
/// Select two random endpoints and then select the one with the least assigned requests.
/// This avoids the overhead of LeastRequests and the worst case for Random where it selects a busy endpoint.
/// </summary>
PowerOfTwoChoices,
/// <summary>
/// Select the endpoint with the least assigned requests. This requires examining all nodes.
/// </summary>
LeastRequests,
/// <summary>
/// Select an endpoint randomly.
/// </summary>
Random,
/// <summary>
/// Select the first endpoint without considering load. This is useful for dual endpoint fail-over systems.
/// </summary>
First,
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.ReverseProxy.Core.Abstractions
Expand All @@ -8,10 +8,13 @@ namespace Microsoft.ReverseProxy.Core.Abstractions
/// </summary>
public sealed class LoadBalancingOptions
{
public LoadBalancingMode Mode { get; set; }

internal LoadBalancingOptions DeepClone()
{
return new LoadBalancingOptions
{
Mode = Mode,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.ReverseProxy.Common.Abstractions.Telemetry;
using Microsoft.ReverseProxy.Common.Abstractions.Time;
Expand All @@ -12,6 +13,7 @@
using Microsoft.ReverseProxy.Core.Service.Metrics;
using Microsoft.ReverseProxy.Core.Service.Proxy;
using Microsoft.ReverseProxy.Core.Service.Proxy.Infra;
using Microsoft.ReverseProxy.Utilities;

namespace Microsoft.ReverseProxy.Core.Configuration.DependencyInjection
{
Expand Down Expand Up @@ -71,6 +73,7 @@ public static IReverseProxyBuilder AddProxy(this IReverseProxyBuilder builder)
{
builder.Services.AddSingleton<IProxyHttpClientFactoryFactory, ProxyHttpClientFactoryFactory>();
builder.Services.AddSingleton<ILoadBalancer, LoadBalancer>();
builder.Services.AddSingleton<IRandomFactory, RandomFactory>();
builder.Services.AddSingleton<IHttpProxy, HttpProxy>();
return builder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ public Task Invoke(HttpContext context)
var endpoints = endpointsFeature?.Endpoints
?? throw new InvalidOperationException("The AvailableBackendEndpoints collection was not set.");

// TODO: Set defaults properly
var loadBalancingOptions = backend.Config.Value?.LoadBalancingOptions ?? default;

var endpoint = _operationLogger.Execute(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private void UpdateRuntimeBackends(DynamicConfigRoot config)
port: configBackend.HealthCheckOptions?.Port ?? 0,
path: configBackend.HealthCheckOptions?.Path ?? string.Empty),
new BackendConfig.BackendLoadBalancingOptions(
mode: BackendConfig.BackendLoadBalancingOptions.LoadBalancingMode.First));
mode: configBackend.LoadBalancing?.Mode ?? default));

var currentBackendConfig = backend.Config.Value;
if (currentBackendConfig == null ||
Expand Down
53 changes: 43 additions & 10 deletions src/ReverseProxy.Core/Service/Proxy/LoadBalancer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using Microsoft.ReverseProxy.Core.Abstractions;
using Microsoft.ReverseProxy.Core.RuntimeModel;
using Microsoft.ReverseProxy.Utilities;

namespace Microsoft.ReverseProxy.Core.Service.Proxy
{
Expand All @@ -13,24 +14,56 @@ namespace Microsoft.ReverseProxy.Core.Service.Proxy
/// </summary>
internal class LoadBalancer : ILoadBalancer
{
private readonly IRandomFactory _randomFactory;

public LoadBalancer(IRandomFactory randomFactory)
{
_randomFactory = randomFactory;
}

public EndpointInfo PickEndpoint(
IReadOnlyList<EndpointInfo> endpoints,
in BackendConfig.BackendLoadBalancingOptions loadBalancingOptions)
{
var endpointCount = endpoints.Count;
if (endpointCount == 0)
{
return null;
}

if (endpointCount == 1)
{
return endpoints[0];
}

switch (loadBalancingOptions.Mode)
{
case BackendConfig.BackendLoadBalancingOptions.LoadBalancingMode.First:
// TODO: Remove, this is a silly load balancing mode
if (endpoints.Count == 0)
case LoadBalancingMode.First:
return endpoints[0];
case LoadBalancingMode.Random:
var random = _randomFactory.CreateRandomInstance();
return endpoints[random.Next(endpointCount)];
case LoadBalancingMode.PowerOfTwoChoices:
// Pick two, and then return the least busy. This avoids the effort of searching the whole list, but
// still avoids overloading a single endpoint.
var random1 = _randomFactory.CreateRandomInstance();
var firstEndpoint = endpoints[random1.Next(endpointCount)];
var secondEndpoint = endpoints[random1.Next(endpointCount)];
return (firstEndpoint.ConcurrencyCounter.Value <= secondEndpoint.ConcurrencyCounter.Value) ? firstEndpoint : secondEndpoint;
case LoadBalancingMode.LeastRequests:
var leastRequestsEndpoint = endpoints[0];
var leastRequestsCount = leastRequestsEndpoint.ConcurrencyCounter.Value;
for (var i = 1; i < endpointCount; i++)
{
return null;
var endpoint = endpoints[i];
var endpointRequestCount = endpoint.ConcurrencyCounter.Value;
if (endpointRequestCount < leastRequestsCount)
{
leastRequestsEndpoint = endpoint;
leastRequestsCount = endpointRequestCount;
}
}

return endpoints[0];
case BackendConfig.BackendLoadBalancingOptions.LoadBalancingMode.Random:
throw new NotImplementedException();
case BackendConfig.BackendLoadBalancingOptions.LoadBalancingMode.PowerOfTwoChoices:
throw new NotImplementedException();
return leastRequestsEndpoint;
default:
throw new NotSupportedException($"Load balancing mode '{loadBalancingOptions.Mode}' is not supported.");
}
Expand Down
8 changes: 1 addition & 7 deletions src/ReverseProxy.Core/Service/RuntimeModel/BackendConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using Microsoft.ReverseProxy.Core.Abstractions;

namespace Microsoft.ReverseProxy.Core.RuntimeModel
{
Expand Down Expand Up @@ -80,13 +81,6 @@ public BackendLoadBalancingOptions(LoadBalancingMode mode)
Mode = mode;
}

public enum LoadBalancingMode
{
First,
Random,
PowerOfTwoChoices,
}

public LoadBalancingMode Mode { get; }
}
}
Expand Down
28 changes: 0 additions & 28 deletions src/ReverseProxy.Utilities/Utils/IRandom.cs

This file was deleted.

6 changes: 4 additions & 2 deletions src/ReverseProxy.Utilities/Utils/IRandomFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace Microsoft.ReverseProxy.Utilities
{
/// <summary>
Expand All @@ -12,6 +14,6 @@ public interface IRandomFactory
/// <summary>
/// Create a instance of random class.
/// </summary>
IRandom CreateRandomInstance();
Random CreateRandomInstance();
}
}
6 changes: 4 additions & 2 deletions src/ReverseProxy.Utilities/Utils/RandomFactory.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace Microsoft.ReverseProxy.Utilities
{
/// <inheritdoc/>
public class RandomFactory : IRandomFactory
{
/// <inheritdoc/>
public IRandom CreateRandomInstance()
public Random CreateRandomInstance()
{
return ThreadStaticRandom.Instance;
}
Expand Down
42 changes: 0 additions & 42 deletions src/ReverseProxy.Utilities/Utils/RandomWrapper.cs

This file was deleted.

42 changes: 3 additions & 39 deletions src/ReverseProxy.Utilities/Utils/ThreadStaticRandom.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
Expand All @@ -10,44 +10,8 @@ namespace Microsoft.ReverseProxy.Utilities
/// </summary>
public class ThreadStaticRandom
{
/// <summary>
/// This is the shared instance of <see cref="RandomWrapper"/> that would be used to generate a seed value for the Thread static instance.
/// </summary>
private static readonly Lazy<RandomWrapper> _globalRandom = new Lazy<RandomWrapper>(() => new RandomWrapper(new Random()));

/// <summary>
/// This instance of <see cref="RandomWrapper"/> is unique to each thread.
/// </summary>
[ThreadStatic]
private static RandomWrapper _threadLocalRandom = null;

/// <summary>
/// Gets the a thread safe instance of <see cref="RandomWrapper"/>.
/// </summary>
public static IRandom Instance
{
get
{
var currentInstance = _threadLocalRandom;

// Check if for the current thread the seed has already been established. If not then lock on the global random instance to generate a seed value
if (currentInstance == null)
{
int seedForThreadLocalInstance;

lock (_globalRandom.Value)
{
seedForThreadLocalInstance = _globalRandom.Value.Next();
}

// Initialize the current instance with the seed
var random = new Random(seedForThreadLocalInstance);
currentInstance = new RandomWrapper(random);
_threadLocalRandom = currentInstance;
}

return currentInstance;
}
}
private static Random t_inst;
public static Random Instance => t_inst ??= new Random();
}
}
Loading

0 comments on commit fee1ada

Please sign in to comment.