From e9bdeb6f134446b43e13315c34f26d67784472e8 Mon Sep 17 00:00:00 2001 From: dsanz Date: Fri, 31 Mar 2017 14:20:03 +0200 Subject: [PATCH] Make resilient policies configurable. --- .../Policies/CircuitBreakerPolicy.cs | 18 +++++ .../HttpResilience/Policies/RetryPolicy.cs | 20 +++++ .../HttpResilience/ResilientHttpClient.cs | 76 +++++++++++++------ .../HttpResilience/ResilientPolicy.cs | 10 +++ .../HttpResilience/ResilientPolicyFactory.cs | 20 +++++ src/Web/WebMVC/Startup.cs | 8 +- 6 files changed, 129 insertions(+), 23 deletions(-) create mode 100644 src/BuildingBlocks/Resilience/HttpResilience/Policies/CircuitBreakerPolicy.cs create mode 100644 src/BuildingBlocks/Resilience/HttpResilience/Policies/RetryPolicy.cs create mode 100644 src/BuildingBlocks/Resilience/HttpResilience/ResilientPolicy.cs create mode 100644 src/BuildingBlocks/Resilience/HttpResilience/ResilientPolicyFactory.cs diff --git a/src/BuildingBlocks/Resilience/HttpResilience/Policies/CircuitBreakerPolicy.cs b/src/BuildingBlocks/Resilience/HttpResilience/Policies/CircuitBreakerPolicy.cs new file mode 100644 index 000000000..ed9ca0d15 --- /dev/null +++ b/src/BuildingBlocks/Resilience/HttpResilience/Policies/CircuitBreakerPolicy.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.HttpResilience.Policies +{ + internal class CircuitBreakerPolicy : ResilientPolicy + { + public CircuitBreakerPolicy(int exceptionsAllowedBeforeBreaking, int durationOfBreakInMinutes) + { + ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + DurationOfBreakInMinutes = durationOfBreakInMinutes; + } + + public int ExceptionsAllowedBeforeBreaking { get; } + public int DurationOfBreakInMinutes { get; } + } +} diff --git a/src/BuildingBlocks/Resilience/HttpResilience/Policies/RetryPolicy.cs b/src/BuildingBlocks/Resilience/HttpResilience/Policies/RetryPolicy.cs new file mode 100644 index 000000000..c33232596 --- /dev/null +++ b/src/BuildingBlocks/Resilience/HttpResilience/Policies/RetryPolicy.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.HttpResilience.Policies +{ + internal class RetryPolicy : ResilientPolicy + { + public RetryPolicy(int retries, int backoffSeconds, bool exponentialBackoff) + { + Retries = retries; + BackoffSeconds = backoffSeconds; + ExponentialBackoff = exponentialBackoff; + } + + public int Retries { get; } + public int BackoffSeconds { get; } + public bool ExponentialBackoff { get; } + } +} diff --git a/src/BuildingBlocks/Resilience/HttpResilience/ResilientHttpClient.cs b/src/BuildingBlocks/Resilience/HttpResilience/ResilientHttpClient.cs index 0701a6c30..5f88c4638 100644 --- a/src/BuildingBlocks/Resilience/HttpResilience/ResilientHttpClient.cs +++ b/src/BuildingBlocks/Resilience/HttpResilience/ResilientHttpClient.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Logging; +using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.HttpResilience.Policies; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Polly; using Polly.Wrap; using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -19,26 +21,58 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.HttpResilience private PolicyWrap _policyWrapper; private ILogger _logger; public HttpClient Inst => _client; - public ResilientHttpClient(ILogger logger) + + public ResilientHttpClient(List policies, ILogger logger) { _client = new HttpClient(); _logger = logger; // Add Policies to be applied - _policyWrapper = Policy.WrapAsync( - CreateRetryPolicy(), - CreateCircuitBreakerPolicy() - ); + _policyWrapper = Policy.WrapAsync(policies.ToArray()); + } + + public ResilientHttpClient(List policies, ILogger logger) + { + _client = new HttpClient(); + _logger = logger; + + // Add Policies to be applied + _policyWrapper = Policy.WrapAsync(GeneratePolicies(policies)); + } + + private Policy[] GeneratePolicies(IList policies) + { + var pollyPolicies = new List(); + + foreach (var policy in policies) + { + switch (policy) + { + case RetryPolicy retryPolicy: + pollyPolicies.Add( + CreateRetryPolicy( + retryPolicy.Retries, + retryPolicy.BackoffSeconds, + retryPolicy.ExponentialBackoff)); + break; + + case CircuitBreakerPolicy circuitPolicy: + pollyPolicies.Add( + CreateCircuitBreakerPolicy( + circuitPolicy.ExceptionsAllowedBeforeBreaking, + circuitPolicy.DurationOfBreakInMinutes)); + break; + } + } + + return pollyPolicies.ToArray(); } - private Policy CreateRetryPolicy() => + private Policy CreateRetryPolicy(int retries, int backoffSeconds, bool exponentialBackoff) => Policy.Handle() - .WaitAndRetryAsync( - // number of retries - 6, - // exponential backofff - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - // on retry + .WaitAndRetryAsync( + retries, + retryAttempt => exponentialBackoff ? TimeSpan.FromSeconds(Math.Pow(backoffSeconds, retryAttempt)) : TimeSpan.FromSeconds(backoffSeconds), (exception, timeSpan, retryCount, context) => { var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " + @@ -50,22 +84,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.HttpResilience } ); - private Policy CreateCircuitBreakerPolicy() => + private Policy CreateCircuitBreakerPolicy(int exceptionsAllowedBeforeBreaking, int durationOfBreakInMinutes) => Policy.Handle() .CircuitBreakerAsync( - // number of exceptions before breaking circuit - 5, - // time circuit opened before retry - TimeSpan.FromMinutes(1), + exceptionsAllowedBeforeBreaking, + TimeSpan.FromMinutes(durationOfBreakInMinutes), (exception, duration) => { - // on circuit opened - _logger.LogTrace("Circuit breaker opened"); + // on circuit opened + _logger.LogTrace("Circuit breaker opened"); }, () => { - // on circuit closed - _logger.LogTrace("Circuit breaker reset"); + // on circuit closed + _logger.LogTrace("Circuit breaker reset"); } ); diff --git a/src/BuildingBlocks/Resilience/HttpResilience/ResilientPolicy.cs b/src/BuildingBlocks/Resilience/HttpResilience/ResilientPolicy.cs new file mode 100644 index 000000000..de2eab218 --- /dev/null +++ b/src/BuildingBlocks/Resilience/HttpResilience/ResilientPolicy.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.HttpResilience +{ + public class ResilientPolicy + { + } +} diff --git a/src/BuildingBlocks/Resilience/HttpResilience/ResilientPolicyFactory.cs b/src/BuildingBlocks/Resilience/HttpResilience/ResilientPolicyFactory.cs new file mode 100644 index 000000000..a3d90c459 --- /dev/null +++ b/src/BuildingBlocks/Resilience/HttpResilience/ResilientPolicyFactory.cs @@ -0,0 +1,20 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.HttpResilience.Policies; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.HttpResilience +{ + public static class ResilientPolicyFactory + { + public static ResilientPolicy CreateRetryPolicy(int retries, int backoffSeconds, bool exponentialBackoff) + { + return new RetryPolicy(retries, backoffSeconds, exponentialBackoff); + } + + public static ResilientPolicy CreateCiscuitBreakerPolicy(int exceptionsAllowedBeforeBreaking, int durationOfBreakInMinutes) + { + return new CircuitBreakerPolicy(exceptionsAllowedBeforeBreaking, durationOfBreakInMinutes); + } + } +} diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index 6cbdd2086..3b265a360 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -58,8 +58,14 @@ namespace Microsoft.eShopOnContainers.WebMVC services.AddTransient(); services.AddTransient, IdentityParser>(); - if(Configuration.GetValue("ActivateCircuitBreaker") == bool.TrueString) + if (Configuration.GetValue("ActivateCircuitBreaker") == bool.TrueString) { + services.AddSingleton( + new List + { + ResilientPolicyFactory.CreateRetryPolicy(6, 2, true), + ResilientPolicyFactory.CreateCiscuitBreakerPolicy(5, 1) + }); services.AddTransient(); } else