分散アプリケーションでアウトバウンド要求の数を制限する方法について説明しましょう。これは、外部APIでいつでもアクセスできない場合に必要です。
入門
. . , - , 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 .
, , 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.
– . , . , .
インフラストラクチャレベルで発信要求のスロットリングを実装することは可能だと思いますが、コードでロック状態を制御できると便利だと思います。また、外国の雲の中でそれを設定することはおそらくトリッキーでしょう。送信リクエストの数を制限する必要があったことはありますか?どうやってやるの?