Skip to content

Commit 59af924

Browse files
committed
Merge branch 'DavidLievrouw-CustomLoadBalancers'
2 parents d58f314 + 7408060 commit 59af924

31 files changed

+1149
-69
lines changed

docs/features/loadbalancer.rst

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,112 @@ subsequent requests. This means the sessions will be stuck across ReRoutes.
108108
Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul
109109
and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the
110110
moment but could be changed.
111+
112+
Custom Load Balancers
113+
^^^^^^^^^^^^^^^^^^^^
114+
115+
`DavidLievrouw <https://github.com/DavidLievrouw`_ implemented a way to provide Ocelot with custom load balancer in `PR 1155 <https://github.com/ThreeMammals/Ocelot/pull/1155`_.
116+
117+
In order to create and use a custom load balancer you can do the following. Below we setup a basic load balancing config and not the Type is CustomLoadBalancer this is the name of a class we will
118+
setup to do load balancing.
119+
120+
.. code-block:: json
121+
122+
{
123+
"DownstreamPathTemplate": "/api/posts/{postId}",
124+
"DownstreamScheme": "https",
125+
"DownstreamHostAndPorts": [
126+
{
127+
"Host": "10.0.1.10",
128+
"Port": 5000,
129+
},
130+
{
131+
"Host": "10.0.1.11",
132+
"Port": 5000,
133+
}
134+
],
135+
"UpstreamPathTemplate": "/posts/{postId}",
136+
"LoadBalancerOptions": {
137+
"Type": "CustomLoadBalancer"
138+
},
139+
"UpstreamHttpMethod": [ "Put", "Delete" ]
140+
}
141+
142+
143+
Then you need to create a class that implements the ILoadBalancer interface. Below is a simple round robin example.
144+
145+
.. code-block:: csharp
146+
147+
private class CustomLoadBalancer : ILoadBalancer
148+
{
149+
private readonly Func<Task<List<Service>>> _services;
150+
private readonly object _lock = new object();
151+
152+
private int _last;
153+
154+
public CustomLoadBalancer(Func<Task<List<Service>>> services)
155+
{
156+
_services = services;
157+
}
158+
159+
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext downstreamContext)
160+
{
161+
var services = await _services();
162+
lock (_lock)
163+
{
164+
if (_last >= services.Count)
165+
{
166+
_last = 0;
167+
}
168+
169+
var next = services[_last];
170+
_last++;
171+
return new OkResponse<ServiceHostAndPort>(next.HostAndPort);
172+
}
173+
}
174+
175+
public void Release(ServiceHostAndPort hostAndPort)
176+
{
177+
}
178+
}
179+
180+
Finally you need to register this class with Ocelot. I have used the most complex example below to show all of the data / types that can be passed into the factory that creates load balancers.
181+
182+
.. code-block:: csharp
183+
184+
Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, CustomLoadBalancer> loadBalancerFactoryFunc = (serviceProvider, reRoute, serviceDiscoveryProvider) => new CustomLoadBalancer(serviceDiscoveryProvider.Get);
185+
186+
s.AddOcelot()
187+
.AddCustomLoadBalancer(loadBalancerFactoryFunc);
188+
189+
However there is a much simpler example that will work the same.
190+
191+
.. code-block:: csharp
192+
193+
s.AddOcelot()
194+
.AddCustomLoadBalancer<CustomLoadBalancer>();
195+
196+
There are numerous extension methods to add a custom load balancer and the interface is as follows.
197+
198+
.. code-block:: csharp
199+
200+
IOcelotBuilder AddCustomLoadBalancer<T>()
201+
where T : ILoadBalancer, new();
202+
203+
IOcelotBuilder AddCustomLoadBalancer<T>(Func<T> loadBalancerFactoryFunc)
204+
where T : ILoadBalancer;
205+
206+
IOcelotBuilder AddCustomLoadBalancer<T>(Func<IServiceProvider, T> loadBalancerFactoryFunc)
207+
where T : ILoadBalancer;
208+
209+
IOcelotBuilder AddCustomLoadBalancer<T>(
210+
Func<DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
211+
where T : ILoadBalancer;
212+
213+
IOcelotBuilder AddCustomLoadBalancer<T>(
214+
Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
215+
where T : ILoadBalancer;
216+
217+
When you enable custom load balancers Ocelot looks up your load balancer by its class name when it decides if it should do load balancing. If it finds a match it will use your load balaner to load balance. If Ocelot cannot match the load balancer type in your configuration with the name of registered load balancer class then you will receive a HTTP 500 internal server error. If your load balancer factory throw an exception when Ocelot calls it you will receive a HTTP 500 internal server error.
218+
219+
Remember if you specify no load balancer in your config Ocelot will not try and load balance.

src/Ocelot/DependencyInjection/IOcelotBuilder.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
using Ocelot.Middleware.Multiplexer;
44
using System;
55
using System.Net.Http;
6+
using Ocelot.Configuration;
7+
using Ocelot.LoadBalancer.LoadBalancers;
8+
using Ocelot.ServiceDiscovery.Providers;
69

710
namespace Ocelot.DependencyInjection
811
{
@@ -25,6 +28,23 @@ IOcelotBuilder AddSingletonDefinedAggregator<T>()
2528
IOcelotBuilder AddTransientDefinedAggregator<T>()
2629
where T : class, IDefinedAggregator;
2730

31+
IOcelotBuilder AddCustomLoadBalancer<T>()
32+
where T : ILoadBalancer, new();
33+
34+
IOcelotBuilder AddCustomLoadBalancer<T>(Func<T> loadBalancerFactoryFunc)
35+
where T : ILoadBalancer;
36+
37+
IOcelotBuilder AddCustomLoadBalancer<T>(Func<IServiceProvider, T> loadBalancerFactoryFunc)
38+
where T : ILoadBalancer;
39+
40+
IOcelotBuilder AddCustomLoadBalancer<T>(
41+
Func<DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
42+
where T : ILoadBalancer;
43+
44+
IOcelotBuilder AddCustomLoadBalancer<T>(
45+
Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
46+
where T : ILoadBalancer;
47+
2848
IOcelotBuilder AddConfigPlaceholders();
2949
}
3050
}

src/Ocelot/DependencyInjection/OcelotBuilder.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Ocelot.ServiceDiscovery.Providers;
2+
13
using Ocelot.Configuration.ChangeTracking;
24

35
namespace Ocelot.DependencyInjection
@@ -87,6 +89,10 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo
8789
Services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();
8890
Services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();
8991
Services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
92+
Services.AddSingleton<ILoadBalancerCreator, NoLoadBalancerCreator>();
93+
Services.AddSingleton<ILoadBalancerCreator, RoundRobinCreator>();
94+
Services.AddSingleton<ILoadBalancerCreator, CookieStickySessionsCreator>();
95+
Services.AddSingleton<ILoadBalancerCreator, LeastConnectionCreator>();
9096
Services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
9197
Services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
9298
Services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
@@ -169,6 +175,47 @@ public IOcelotBuilder AddTransientDefinedAggregator<T>()
169175
return this;
170176
}
171177

178+
public IOcelotBuilder AddCustomLoadBalancer<T>()
179+
where T : ILoadBalancer, new()
180+
{
181+
AddCustomLoadBalancer((provider, reRoute, serviceDiscoveryProvider) => new T());
182+
return this;
183+
}
184+
185+
public IOcelotBuilder AddCustomLoadBalancer<T>(Func<T> loadBalancerFactoryFunc)
186+
where T : ILoadBalancer
187+
{
188+
AddCustomLoadBalancer((provider, reRoute, serviceDiscoveryProvider) =>
189+
loadBalancerFactoryFunc());
190+
return this;
191+
}
192+
193+
public IOcelotBuilder AddCustomLoadBalancer<T>(Func<IServiceProvider, T> loadBalancerFactoryFunc)
194+
where T : ILoadBalancer
195+
{
196+
AddCustomLoadBalancer((provider, reRoute, serviceDiscoveryProvider) =>
197+
loadBalancerFactoryFunc(provider));
198+
return this;
199+
}
200+
201+
public IOcelotBuilder AddCustomLoadBalancer<T>(Func<DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
202+
where T : ILoadBalancer
203+
{
204+
AddCustomLoadBalancer((provider, reRoute, serviceDiscoveryProvider) =>
205+
loadBalancerFactoryFunc(reRoute, serviceDiscoveryProvider));
206+
return this;
207+
}
208+
209+
public IOcelotBuilder AddCustomLoadBalancer<T>(Func<IServiceProvider, DownstreamReRoute, IServiceDiscoveryProvider, T> loadBalancerFactoryFunc)
210+
where T : ILoadBalancer
211+
{
212+
Services.AddSingleton<ILoadBalancerCreator>(provider =>
213+
new DelegateInvokingLoadBalancerCreator<T>(
214+
(reRoute, serviceDiscoveryProvider) =>
215+
loadBalancerFactoryFunc(provider, reRoute, serviceDiscoveryProvider)));
216+
return this;
217+
}
218+
172219
private void AddSecurity()
173220
{
174221
Services.TryAddSingleton<ISecurityOptionsCreator, SecurityOptionsCreator>();

src/Ocelot/Errors/OcelotErrorCode.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,7 @@ public enum OcelotErrorCode
4141
QuotaExceededError = 36,
4242
RequestCanceled = 37,
4343
ConnectionToDownstreamServiceError = 38,
44+
CouldNotFindLoadBalancerCreator = 39,
45+
ErrorInvokingLoadBalancerCreator = 40,
4446
}
4547
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace Ocelot.LoadBalancer.LoadBalancers
2+
{
3+
using System.Threading.Tasks;
4+
using Ocelot.Configuration;
5+
using Ocelot.Infrastructure;
6+
using Ocelot.ServiceDiscovery.Providers;
7+
using Ocelot.Responses;
8+
9+
public class CookieStickySessionsCreator : ILoadBalancerCreator
10+
{
11+
public Response<ILoadBalancer> Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider)
12+
{
13+
var loadBalancer = new RoundRobin(async () => await serviceProvider.Get());
14+
var bus = new InMemoryBus<StickySession>();
15+
return new OkResponse<ILoadBalancer>(new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key,
16+
reRoute.LoadBalancerOptions.ExpiryInMs, bus));
17+
}
18+
19+
public string Type => nameof(CookieStickySessions);
20+
}
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Ocelot.LoadBalancer.LoadBalancers
2+
{
3+
using Errors;
4+
5+
public class CouldNotFindLoadBalancerCreator : Error
6+
{
7+
public CouldNotFindLoadBalancerCreator(string message)
8+
: base(message, OcelotErrorCode.CouldNotFindLoadBalancerCreator)
9+
{
10+
}
11+
}
12+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace Ocelot.LoadBalancer.LoadBalancers
2+
{
3+
using System;
4+
using Ocelot.Configuration;
5+
using Ocelot.ServiceDiscovery.Providers;
6+
using Ocelot.Responses;
7+
8+
public class DelegateInvokingLoadBalancerCreator<T> : ILoadBalancerCreator
9+
where T : ILoadBalancer
10+
{
11+
private readonly Func<DownstreamReRoute, IServiceDiscoveryProvider, ILoadBalancer> _creatorFunc;
12+
13+
public DelegateInvokingLoadBalancerCreator(
14+
Func<DownstreamReRoute, IServiceDiscoveryProvider, ILoadBalancer> creatorFunc)
15+
{
16+
_creatorFunc = creatorFunc;
17+
}
18+
19+
public Response<ILoadBalancer> Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider)
20+
{
21+
try
22+
{
23+
return new OkResponse<ILoadBalancer>(_creatorFunc(reRoute, serviceProvider));
24+
25+
}
26+
catch (Exception e)
27+
{
28+
return new ErrorResponse<ILoadBalancer>(new ErrorInvokingLoadBalancerCreator(e));
29+
}
30+
}
31+
32+
public string Type => typeof(T).Name;
33+
}
34+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Ocelot.LoadBalancer.LoadBalancers
2+
{
3+
using System;
4+
using Errors;
5+
6+
public class ErrorInvokingLoadBalancerCreator : Error
7+
{
8+
public ErrorInvokingLoadBalancerCreator(Exception e) : base($"Error when invoking user provided load balancer creator function, Message: {e.Message}, StackTrace: {e.StackTrace}", OcelotErrorCode.ErrorInvokingLoadBalancerCreator)
9+
{
10+
}
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Ocelot.LoadBalancer.LoadBalancers
2+
{
3+
using Ocelot.Responses;
4+
using Ocelot.Configuration;
5+
using Ocelot.ServiceDiscovery.Providers;
6+
7+
public interface ILoadBalancerCreator
8+
{
9+
Response<ILoadBalancer> Create(DownstreamReRoute reRoute, IServiceDiscoveryProvider serviceProvider);
10+
string Type { get; }
11+
}
12+
}

src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
{
33
using Ocelot.Configuration;
44
using Ocelot.Responses;
5-
using System.Threading.Tasks;
65

76
public interface ILoadBalancerFactory
87
{
9-
Task<Response<ILoadBalancer>> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
8+
Response<ILoadBalancer> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config);
109
}
1110
}

0 commit comments

Comments
 (0)