Bagaimana cara menghentikan DDoS-ing API orang lain dan mulai hidup

Mari membahas tentang cara membatasi jumlah permintaan keluar dalam aplikasi terdistribusi. Ini diperlukan jika API eksternal tidak mengizinkan Anda mengaksesnya kapan pun Anda mau.





Pengantar

.   . , - , 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.





– . , . , .





Saya pikir adalah mungkin untuk mengimplementasikan pelambatan permintaan keluar di tingkat infrastruktur, tetapi saya merasa nyaman untuk memiliki kendali atas status kunci dalam kode. Selain itu, menyiapkannya di cloud asing mungkin akan rumit. Pernahkah Anda membatasi jumlah permintaan keluar? Bagaimana Anda melakukannya?








All Articles