
Parlons des moyens de limiter le nombre de requêtes sortantes dans une application distribuée. Cela est nécessaire si l'API externe ne vous permet pas d'y accéder quand vous le souhaitez.
Introduction
. . , - , API , . , . . , — . , : . . , , ID . . , — .

, , , . : 1000 .
— . , N . — . - - .
(1000/20) 50 .
.NET
private const int RequestsLimit = 50;
private static readonly SemaphoreSlim Throttler =
new SemaphoreSlim(RequestsLimit);
async Task<HttpResponseMessage> InvokeServiceAsync(HttpClient client)
{
try
{
await Throttler.WaitAsync().ConfigureAwait(false);
return await client.GetAsync("todos/1").ConfigureAwait(false);
}
finally
{
Throttler.Release();
}
}
.NET Core HttpClient, , , . , .
, .

, , . , , . — , . — , - . , , .
:
:
:
, . . — -throttler-. , , — , . ? , Redis.
Redis ( ). , .
Redis , .
Lua. Lua Redis, , . , , .
, . , , , . - - . , . , , , API- .
Redis
--[[
KEYS[1] -
ARGV[1] -
ARGV[2] - ,
ARGV[3] -
]]--
-- ,
-- Redis-
redis.replicate_commands()
local unix_time = redis.call('TIME')[1]
-- TTL
redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', unix_time - ARGV[1])
--
local count = redis.call('zcard', KEYS[1])
if count < tonumber(ARGV[3]) then
-- ,
-- ( )
redis.call('ZADD', KEYS[1], unix_time, ARGV[2])
-- (, )
return count
end
return nil
. . , .. .NET .
Redis .
, , 1000 . Redis .
, , .
public sealed class RedisSemaphore
{
private static readonly string AcquireScript = "...";
private static readonly int TimeToLiveInSeconds = 300;
private readonly Func<ConnectionMultiplexer> _redisFactory;
public RedisSemaphore(Func<ConnectionMultiplexer> redisFactory)
{
_redisFactory = redisFactory;
}
public async Task<LockHandler> AcquireAsync(string name, int limit)
{
var handler = new LockHandler(this, name);
do
{
var redisDb = _redisFactory().GetDatabase();
var rawResult = await redisDb
.ScriptEvaluateAsync(AcquireScript, new RedisKey[] { name },
new RedisValue[] { TimeToLiveInSeconds, handler.Id, limit })
.ConfigureAwait(false);
var acquired = !rawResult.IsNull;
if (acquired)
break;
await Task.Delay(10).ConfigureAwait(false);
} while (true);
return handler;
}
public async Task ReleaseAsync(LockHandler handler, string name)
{
var redis = _redisFactory().GetDatabase();
await redis.SortedSetRemoveAsync(name, handler.Id)
.ConfigureAwait(false);
}
}
public sealed class LockHandler : IAsyncDisposable
{
private readonly RedisSemaphore _semaphore;
private readonly string _name;
public LockHandler(RedisSemaphore semaphore, string name)
{
_semaphore = semaphore;
_name = name;
Id = Guid.NewGuid().ToString();
}
public string Id { get; }
public async ValueTask DisposeAsync()
{
await _semaphore.ReleaseAsync(this, _name).ConfigureAwait(false);
}
}
, .
:
:
Redis-
Redis , . . - , , . . , Redis , , SaaS.
– . , . , .
Je pense qu'il est possible d'implémenter la limitation des demandes sortantes au niveau de l'infrastructure, mais je trouve pratique de contrôler l'état de verrouillage dans le code. De plus, sa configuration dans des nuages ​​étrangers sera probablement délicate. Avez-vous déjà dû limiter le nombre de demandes sortantes? Comment faites-vous?