implemented a send to self pattern for sticky session timeouts rather than a normal timer

This commit is contained in:
Tom Pallister
2018-05-05 13:43:38 +01:00
parent c041d90e38
commit fb3af754ab
10 changed files with 965 additions and 1017 deletions

View File

@ -0,0 +1,15 @@
namespace Ocelot.Infrastructure
{
internal class DelayedMessage<T>
{
public DelayedMessage(T message, int delay)
{
Delay = delay;
Message = message;
}
public T Message { get; set; }
public int Delay { get; set; }
}
}

View File

@ -1,14 +1,10 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Ocelot.Infrastructure
{
public interface IBus<T>
{
void Subscribe(Action<T> action);
Task Publish(T message, int delay);
void Publish(T message, int delay);
}
}

View File

@ -8,15 +8,15 @@ namespace Ocelot.Infrastructure
{
public class InMemoryBus<T> : IBus<T>
{
private readonly BlockingCollection<T> _queue;
private readonly BlockingCollection<DelayedMessage<T>> _queue;
private readonly List<Action<T>> _subscriptions;
private Thread _processing;
public InMemoryBus()
{
_queue = new BlockingCollection<T>();
_queue = new BlockingCollection<DelayedMessage<T>>();
_subscriptions = new List<Action<T>>();
_processing = new Thread(Process);
_processing = new Thread(async () => await Process());
_processing.Start();
}
@ -25,19 +25,21 @@ namespace Ocelot.Infrastructure
_subscriptions.Add(action);
}
public async Task Publish(T message, int delay)
public void Publish(T message, int delay)
{
await Task.Delay(delay);
_queue.Add(message);
var delayed = new DelayedMessage<T>(message, delay);
_queue.Add(delayed);
}
private void Process()
private async Task Process()
{
foreach(var message in _queue.GetConsumingEnumerable())
foreach(var delayedMessage in _queue.GetConsumingEnumerable())
{
foreach(var subscription in _subscriptions)
await Task.Delay(delayedMessage.Delay);
foreach (var subscription in _subscriptions)
{
subscription(message);
subscription(delayedMessage.Message);
}
}
}

View File

@ -3,7 +3,6 @@ namespace Ocelot.LoadBalancer.LoadBalancers
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Ocelot.Infrastructure;
@ -11,13 +10,13 @@ namespace Ocelot.LoadBalancer.LoadBalancers
using Responses;
using Values;
public class CookieStickySessions : ILoadBalancer, IDisposable
public class CookieStickySessions : ILoadBalancer
{
private readonly int _keyExpiryInMs;
private readonly string _key;
private readonly ILoadBalancer _loadBalancer;
private readonly ConcurrentDictionary<string, StickySession> _stored;
private IBus<StickySession> _bus;
private readonly IBus<StickySession> _bus;
private readonly object _lock = new object();
public CookieStickySessions(ILoadBalancer loadBalancer, string key, int keyExpiryInMs, IBus<StickySession> bus)
@ -27,12 +26,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers
_keyExpiryInMs = keyExpiryInMs;
_loadBalancer = loadBalancer;
_stored = new ConcurrentDictionary<string, StickySession>();
_bus.Subscribe(ss => {
if(_stored.TryGetValue(ss.Key, out var stickySession))
_bus.Subscribe(ss =>
{
//todo - get test coverage for this.
if (_stored.TryGetValue(ss.Key, out var stickySession))
{
lock(_lock)
lock (_lock)
{
if(stickySession.Expiry < DateTime.Now)
if (stickySession.Expiry < DateTime.UtcNow)
{
_stored.Remove(stickySession.Key, out _);
_loadBalancer.Release(stickySession.HostAndPort);
@ -42,26 +43,24 @@ namespace Ocelot.LoadBalancer.LoadBalancers
});
}
public void Dispose()
{
_timer?.Dispose();
}
public async Task<Response<ServiceHostAndPort>> Lease(DownstreamContext context)
{
var key = context.HttpContext.Request.Cookies[_key];
if (!string.IsNullOrEmpty(key) && _stored.ContainsKey(key))
lock (_lock)
{
var cached = _stored[key];
if (!string.IsNullOrEmpty(key) && _stored.ContainsKey(key))
{
var cached = _stored[key];
var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key);
var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key);
_stored[key] = updated;
_stored[key] = updated;
await _bus.Publish(updated, _keyExpiryInMs);
_bus.Publish(updated, _keyExpiryInMs);
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort);
return new OkResponse<ServiceHostAndPort>(updated.HostAndPort);
}
}
var next = await _loadBalancer.Lease(context);
@ -71,11 +70,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers
return new ErrorResponse<ServiceHostAndPort>(next.Errors);
}
if (!string.IsNullOrEmpty(key) && !_stored.ContainsKey(key))
lock (_lock)
{
var ss = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key);
_stored[key] = ss;
await _bus.Publish(ss, _keyExpiryInMs);
if (!string.IsNullOrEmpty(key) && !_stored.ContainsKey(key))
{
var ss = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key);
_stored[key] = ss;
_bus.Publish(ss, _keyExpiryInMs);
}
}
return new OkResponse<ServiceHostAndPort>(next.Data);

View File

@ -99,7 +99,7 @@ namespace Ocelot.Raft
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content).GetAwaiter().GetResult();
if(response.IsSuccessStatusCode)
{
var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings);
var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings);
return new OkResponse<T>((T)okResponse.Command);
}
else