diff --git a/Vostok.ClusterClient.Core/IClusterClientConfigurationExtensions_Throttling.cs b/Vostok.ClusterClient.Core/IClusterClientConfigurationExtensions_Throttling.cs index 8fcd829..3ec0a96 100644 --- a/Vostok.ClusterClient.Core/IClusterClientConfigurationExtensions_Throttling.cs +++ b/Vostok.ClusterClient.Core/IClusterClientConfigurationExtensions_Throttling.cs @@ -1,4 +1,6 @@ -using Vostok.Clusterclient.Core.Modules; +using System.Collections.Generic; +using Vostok.Clusterclient.Core.Model; +using Vostok.Clusterclient.Core.Modules; namespace Vostok.Clusterclient.Core { @@ -9,10 +11,10 @@ public static partial class IClusterClientConfigurationExtensions /// /// A configuration to be modified. /// See . - /// See . - /// See . - /// See . - /// See . + /// See . + /// See . + /// See . + /// See . public static void SetupAdaptiveThrottling( this IClusterClientConfiguration configuration, string storageKey, @@ -30,38 +32,16 @@ public static void SetupAdaptiveThrottling( configuration.AddRequestModule(new AdaptiveThrottlingModule(options), typeof(AbsoluteUrlSenderModule)); } - - /// - /// Sets up an adaptive client throttling mechanism with given parameters. - /// - /// A configuration to be modified. - /// See . - /// See . - /// See . - /// See . - public static void SetupAdaptiveThrottling( - this IClusterClientConfiguration configuration, - string storageKey, - AdaptiveThrottlingOptions criticalOptions, - AdaptiveThrottlingOptions ordinaryOptions, - AdaptiveThrottlingOptions sheddableOptions) - { - var defaultOptions = new AdaptiveThrottlingOptions(storageKey); - criticalOptions ??= defaultOptions; - ordinaryOptions ??= defaultOptions; - sheddableOptions ??= defaultOptions; - configuration.AddRequestModule(new AdaptiveThrottlingModule(storageKey, criticalOptions, ordinaryOptions, sheddableOptions), typeof(AbsoluteUrlSenderModule)); - } /// /// Sets up an adaptive client throttling mechanism with given parameters using and as a storage key. /// N.B. Ensure that and is set before calling this method. /// /// A configuration to be modified. - /// See . - /// See . - /// See . - /// See . + /// See . + /// See . + /// See . + /// See . public static void SetupAdaptiveThrottling( this IClusterClientConfiguration configuration, int minutesToTrack = ClusterClientDefaults.AdaptiveThrottlingMinutesToTrack, @@ -73,25 +53,34 @@ public static void SetupAdaptiveThrottling( SetupAdaptiveThrottling(configuration, storageKey, minutesToTrack, minimumRequests, criticalRatio, maximumRejectProbability); } - + /// - /// Sets up an adaptive client throttling mechanism with given parameters using and as a storage key. - /// N.B. Ensure that and is set before calling this method. + /// Configures default settings by request priority for adaptive client throttling mechanism. + /// + public static AdaptiveThrottlingOptions ConfigureAdaptiveThrottlingOptions(string storageKey, AdaptiveThrottlingParameters defaultParameters) + { + defaultParameters ??= new AdaptiveThrottlingParameters(); + + var parameters = new Dictionary + { + [RequestPriority.Critical] = defaultParameters, + [RequestPriority.Ordinary] = defaultParameters, + [RequestPriority.Sheddable] = defaultParameters + }; + + return new AdaptiveThrottlingOptions(storageKey, parameters); + } + + /// + /// Sets up an adaptive client throttling mechanism with given options. /// /// A configuration to be modified. - /// See . - /// See . - /// See . - /// See . + /// See public static void SetupAdaptiveThrottling( this IClusterClientConfiguration configuration, - AdaptiveThrottlingOptions criticalOptions, - AdaptiveThrottlingOptions ordinaryOptions, - AdaptiveThrottlingOptions sheddableOptions) + AdaptiveThrottlingOptions options) { - var storageKey = GenerateStorageKey(configuration); - - SetupAdaptiveThrottling(configuration, storageKey, criticalOptions, ordinaryOptions, sheddableOptions); + configuration.AddRequestModule(new AdaptiveThrottlingModule(options), typeof(AbsoluteUrlSenderModule)); } } } \ No newline at end of file diff --git a/Vostok.ClusterClient.Core/Modules/AdaptiveThrottlingModule.cs b/Vostok.ClusterClient.Core/Modules/AdaptiveThrottlingModule.cs index 3b7f822..20f7e48 100644 --- a/Vostok.ClusterClient.Core/Modules/AdaptiveThrottlingModule.cs +++ b/Vostok.ClusterClient.Core/Modules/AdaptiveThrottlingModule.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Vostok.Clusterclient.Core.Misc; using Vostok.Clusterclient.Core.Model; using Vostok.Commons.Threading; using Vostok.Logging.Abstractions; @@ -16,44 +17,16 @@ namespace Vostok.Clusterclient.Core.Modules /// internal class AdaptiveThrottlingModule : IRequestModule { + private static readonly RequestPriority DefaultPriority = RequestPriority.Sheddable; private static readonly ConcurrentDictionary Counters = new(); private static readonly Stopwatch Watch = Stopwatch.StartNew(); private readonly Func counterFactory; public AdaptiveThrottlingModule(AdaptiveThrottlingOptions options) - : this(options.StorageKey, options, options, options) { - } - - public AdaptiveThrottlingModule( - string storageKey, - AdaptiveThrottlingOptions criticalOptions, - AdaptiveThrottlingOptions ordinaryOptions, - AdaptiveThrottlingOptions sheddableOptions) - { - StorageKey = storageKey; - Options = new Dictionary - { - {RequestPriority.Critical, criticalOptions}, - {RequestPriority.Ordinary, ordinaryOptions}, - {RequestPriority.Sheddable, sheddableOptions} - }; - counterFactory = _ => new CountersByPriority( - criticalOptions.MinutesToTrack, - ordinaryOptions.MinutesToTrack, - sheddableOptions.MinutesToTrack - ); - } - - public AdaptiveThrottlingModule(Dictionary options) - { - this.Options = options; - counterFactory = _ => new CountersByPriority( - options[RequestPriority.Critical].MinutesToTrack, - options[RequestPriority.Ordinary].MinutesToTrack, - options[RequestPriority.Sheddable].MinutesToTrack - ); + StorageKey = options.StorageKey; + counterFactory = _ => new CountersByPriority(options.Parameters); } public static void ClearCache() @@ -61,22 +34,20 @@ public static void ClearCache() Counters.Clear(); } - public Dictionary Options { get; } - public int Requests(RequestPriority? priority) => GetCounter(priority).GetMetrics().Requests; public int Accepts(RequestPriority? priority) => GetCounter(priority).GetMetrics().Accepts; public double Ratio(RequestPriority? priority) => ComputeRatio(GetCounter(priority).GetMetrics()); - public double RejectionProbability(RequestPriority? priority) => ComputeRejectionProbability(GetCounter(priority).GetMetrics(), GetOptions(priority)); + public double RejectionProbability(RequestPriority? priority) => ComputeRejectionProbability(GetCounter(priority).GetMetrics(), GetCounter(priority).Parameters); public string StorageKey { get; } public async Task ExecuteAsync(IRequestContext context, Func> next) { var counter = GetCounter(context.Parameters.Priority); - var options = GetOptions(context.Parameters.Priority); + var options = counter.Parameters; counter.BeginRequest(); ClusterResult result; @@ -119,7 +90,7 @@ public async Task ExecuteAsync(IRequestContext context, Func 1.0 * metrics.Requests / Math.Max(1.0, metrics.Accepts); - private static double ComputeRejectionProbability(CounterMetrics metrics, AdaptiveThrottlingOptions options) + private static double ComputeRejectionProbability(CounterMetrics metrics, AdaptiveThrottlingParameters options) { var probability = 1.0 * (metrics.Requests - options.CriticalRatio * metrics.Accepts) / (metrics.Requests + 1); @@ -137,7 +108,7 @@ private static void UpdateCounter(Counter counter, ClusterResult result) private Counter GetCounter(RequestPriority? priority) { - priority ??= RequestPriority.Sheddable; + priority ??= DefaultPriority; var counters = Counters.GetOrAdd(StorageKey, counterFactory); return priority switch { @@ -148,22 +119,24 @@ private Counter GetCounter(RequestPriority? priority) }; } - private AdaptiveThrottlingOptions GetOptions(RequestPriority? priority) => Options[priority ?? RequestPriority.Sheddable]; - #region CountersByPriority private class CountersByPriority { - public CountersByPriority(int criticalBuckets, int ordinaryBuckets, int sheddableBuckets) + private readonly Dictionary requestCounters; + + public CountersByPriority(Dictionary options) { - CriticalRequestCounter = new Counter(criticalBuckets); - OrdinaryRequestCounter = new Counter(ordinaryBuckets); - SheddableRequestCounter = new Counter(sheddableBuckets); + requestCounters = new Dictionary(); + foreach (var (priority, parameters) in options) + { + requestCounters[priority] = new Counter(parameters); + } } - public Counter CriticalRequestCounter { get; } - public Counter OrdinaryRequestCounter { get; } - public Counter SheddableRequestCounter { get; } + public Counter CriticalRequestCounter => requestCounters[RequestPriority.Critical]; + public Counter OrdinaryRequestCounter => requestCounters[RequestPriority.Ordinary]; + public Counter SheddableRequestCounter => requestCounters[RequestPriority.Sheddable]; } #endregion @@ -193,14 +166,19 @@ private class Counter private readonly CounterBucket[] buckets; private int pendingRequests; - public Counter(int buckets) + public Counter(AdaptiveThrottlingParameters parameters) { - this.buckets = new CounterBucket[buckets]; + Parameters = parameters; + + var bucketsNumber = parameters.MinutesToTrack; + buckets = new CounterBucket[bucketsNumber]; - for (var i = 0; i < buckets; i++) - this.buckets[i] = new CounterBucket(); + for (var i = 0; i < bucketsNumber; i++) + buckets[i] = new CounterBucket(); } + public AdaptiveThrottlingParameters Parameters { get; } + public CounterMetrics GetMetrics() { var metrics = new CounterMetrics(); diff --git a/Vostok.ClusterClient.Core/Modules/AdaptiveThrottlingOptions.cs b/Vostok.ClusterClient.Core/Modules/AdaptiveThrottlingOptions.cs index 7e84c73..83adeb7 100644 --- a/Vostok.ClusterClient.Core/Modules/AdaptiveThrottlingOptions.cs +++ b/Vostok.ClusterClient.Core/Modules/AdaptiveThrottlingOptions.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using JetBrains.Annotations; +using Vostok.Clusterclient.Core.Model; namespace Vostok.Clusterclient.Core.Modules { @@ -11,7 +13,7 @@ public class AdaptiveThrottlingOptions { /// A key used to decouple statistics for different services. This parameter is REQUIRED /// How much minutes of statistics will be tracked. Must be >= 1. - /// A minimum requests count in minutes to reject any request. + /// A minimum requests count in minutes to reject any request. /// A minimum ratio of requests to accepts eligible for rejection. Must be > 1. /// A cap on the request rejection probability to prevent eternal rejection. /// is null. @@ -23,9 +25,100 @@ public AdaptiveThrottlingOptions( double criticalRatio = ClusterClientDefaults.AdaptiveThrottlingCriticalRatio, double maximumRejectProbability = ClusterClientDefaults.AdaptiveThrottlingRejectProbabilityCap) { - if (storageKey == null) - throw new ArgumentNullException(nameof(storageKey)); + StorageKey = storageKey ?? throw new ArgumentNullException(nameof(storageKey)); + + var defaultParameters = new AdaptiveThrottlingParameters( + minutesToTrack, + minimumRequests, + criticalRatio, + maximumRejectProbability + ); + + Parameters = new Dictionary + { + [RequestPriority.Critical] = defaultParameters, + [RequestPriority.Ordinary] = defaultParameters, + [RequestPriority.Sheddable] = defaultParameters + }; + } + + /// A key used to decouple statistics for different services. This parameter is REQUIRED + /// A Dictionary in which provide adaptive throttling parameters by priority + /// is null. + public AdaptiveThrottlingOptions( + [NotNull] string storageKey, + Dictionary parameters) + { + StorageKey = storageKey ?? throw new ArgumentNullException(nameof(storageKey)); + + Parameters = parameters == null + ? new Dictionary() + : new Dictionary(parameters); + + var defaultParameters = new AdaptiveThrottlingParameters(); + if (!Parameters.ContainsKey(RequestPriority.Critical)) + { + Parameters[RequestPriority.Critical] = defaultParameters; + } + + if (!Parameters.ContainsKey(RequestPriority.Ordinary)) + { + Parameters[RequestPriority.Ordinary] = defaultParameters; + } + + if (!Parameters.ContainsKey(RequestPriority.Sheddable)) + { + Parameters[RequestPriority.Sheddable] = defaultParameters; + } + StorageKey = storageKey; + } + + /// + /// A key used to decouple statistics for different services. + /// + [NotNull] + public string StorageKey { get; } + + /// + /// Dictionary in which stored adaptive throttling parameters by priority. + /// + public Dictionary Parameters { get; } + + /// + /// Produces a new instance where adaptive throttling parameters by priority will have given value. + /// See class documentation for details. + /// + /// Priority name for details + /// Throttling parameters by priority. + /// A new object with updated throttling parameters for given priority. + public AdaptiveThrottlingOptions WithPriorityParameters(RequestPriority priority, AdaptiveThrottlingParameters criticalRequestParameters) + { + var parameters = new Dictionary(Parameters) + { + [RequestPriority.Critical] = criticalRequestParameters + }; + return new AdaptiveThrottlingOptions(StorageKey, parameters); + } + } + + /// + /// Represents a parameters of instance by request priority. + /// + [PublicAPI] + public class AdaptiveThrottlingParameters + { + /// How much minutes of statistics will be tracked. Must be >= 1. + /// A minimum requests count in minutes to reject any request. + /// A minimum ratio of requests to accepts eligible for rejection. Must be > 1. + /// A cap on the request rejection probability to prevent eternal rejection. + /// , or does not lie in expected range. + public AdaptiveThrottlingParameters( + int minutesToTrack = ClusterClientDefaults.AdaptiveThrottlingMinutesToTrack, + int minimumRequests = ClusterClientDefaults.AdaptiveThrottlingMinimumRequests, + double criticalRatio = ClusterClientDefaults.AdaptiveThrottlingCriticalRatio, + double maximumRejectProbability = ClusterClientDefaults.AdaptiveThrottlingRejectProbabilityCap) + { if (minutesToTrack < 1) throw new ArgumentOutOfRangeException(nameof(minutesToTrack), "Minutes to track parameter must be >= 1."); @@ -35,19 +128,12 @@ public AdaptiveThrottlingOptions( if (maximumRejectProbability < 0.0 || maximumRejectProbability > 1.0) throw new ArgumentOutOfRangeException(nameof(maximumRejectProbability), "Maximum rejection probability must be in [0; 1] range."); - StorageKey = storageKey; MinutesToTrack = minutesToTrack; MinimumRequests = minimumRequests; CriticalRatio = criticalRatio; MaximumRejectProbability = maximumRejectProbability; } - /// - /// A key used to decouple statistics for different services. - /// - [NotNull] - public string StorageKey { get; } - /// /// How much minutes of statistics will be tracked. Must be >= 1. /// diff --git a/Vostok.ClusterClient.Core/PublicAPI.Unshipped.txt b/Vostok.ClusterClient.Core/PublicAPI.Unshipped.txt index 573ed58..89e26f9 100644 --- a/Vostok.ClusterClient.Core/PublicAPI.Unshipped.txt +++ b/Vostok.ClusterClient.Core/PublicAPI.Unshipped.txt @@ -1,2 +1,15 @@ -static Vostok.Clusterclient.Core.IClusterClientConfigurationExtensions.SetupAdaptiveThrottling(this Vostok.Clusterclient.Core.IClusterClientConfiguration configuration, string storageKey, Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions criticalOptions, Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions ordinaryOptions, Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions sheddableOptions) -> void -static Vostok.Clusterclient.Core.IClusterClientConfigurationExtensions.SetupAdaptiveThrottling(this Vostok.Clusterclient.Core.IClusterClientConfiguration configuration, Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions criticalOptions, Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions ordinaryOptions, Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions sheddableOptions) -> void \ No newline at end of file +static Vostok.Clusterclient.Core.IClusterClientConfigurationExtensions.ConfigureAdaptiveThrottlingOptions(string storageKey, Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingParameters defaultParameters) -> Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions +static Vostok.Clusterclient.Core.IClusterClientConfigurationExtensions.SetupAdaptiveThrottling(this Vostok.Clusterclient.Core.IClusterClientConfiguration configuration, Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions options) -> void +Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions.AdaptiveThrottlingOptions(string storageKey, System.Collections.Generic.Dictionary parameters) -> void +Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions.Parameters.get -> System.Collections.Generic.Dictionary +Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions.WithPriorityParameters(Vostok.Clusterclient.Core.Model.RequestPriority priority, Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingParameters criticalRequestParameters) -> Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions +*REMOVED*Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions.MinutesToTrack.get -> int +*REMOVED*Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions.MinimumRequests.get -> int +*REMOVED*Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions.CriticalRatio.get -> double +*REMOVED*Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingOptions.MaximumRejectProbability.get -> double +Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingParameters +Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingParameters.AdaptiveThrottlingParameters(int minutesToTrack = 2, int minimumRequests = 30, double criticalRatio = 2, double maximumRejectProbability = 0.8) -> void +Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingParameters.CriticalRatio.get -> double +Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingParameters.MaximumRejectProbability.get -> double +Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingParameters.MinimumRequests.get -> int +Vostok.Clusterclient.Core.Modules.AdaptiveThrottlingParameters.MinutesToTrack.get -> int \ No newline at end of file