So stoppen Sie die DDoS-Verknüpfung der API eines anderen und beginnen zu leben

Lassen Sie uns über Möglichkeiten sprechen, die Anzahl der ausgehenden Anforderungen in einer verteilten Anwendung zu begrenzen. Dies ist erforderlich, wenn Sie über die externe API nicht jederzeit darauf zugreifen können.





Einleitend

.   . , - , 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);
	}
}
      
      



, .





:

















:













  1. Redis-









  Redis  ,         .       . - , ,     . . , Redis , , SaaS.





– . , . , .





Ich denke, es ist möglich, die Drosselung ausgehender Anforderungen auf Infrastrukturebene zu implementieren, aber ich finde es praktisch, die Kontrolle über den Sperrstatus im Code zu haben. Auch das Einrichten in fremden Wolken wird wahrscheinlich schwierig sein. Mussten Sie jemals die Anzahl der ausgehenden Anfragen begrenzen? Wie machst du das?








All Articles