The use and principle of distributed current limiting redission RRateLimiter

Time:2022-8-5

premise

Recently, the company is doing distributed current limiting if there is a demand. The current limiting framework of the research is probably

1. Spring cloud gateway integrates redis current limiting, but it belongs to gateway layer current limiting

2, Ali Sentinel, powerful, with monitoring platform

3. srping cloud hystrix, which belongs to the interface layer current limiting, provides two methods of thread pool and semaphore

4. Others: redission, hand code

The actual demand situation belongs to the business-side current limit, and the redission is more convenient and flexible to use. The following describes how to use the distributed current limit of the redission and the principle:

1. Use

It is very simple to use, as follows

// 1. Declare a current limiter
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
 
// 2. Set the rate, generate 3 tokens in 5 seconds
rateLimiter.trySetRate(RateType.OVERALL, 3, 5, RateIntervalUnit.SECONDS);
 
// 3. Try to get a token, get it and return true
rateLimiter.tryAcquire(1)

2. Principle

1、getRateLimiter

// Declare a current limiter name called key
redissonClient.getRateLimiter(key)

2、trySetRate

The trySetRate method follows up to the underlying implementation as follows:

@Override
public RFuture<Boolean> trySetRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            "redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);"
          + "redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);"
          + "return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);",
            Collections.<Object>singletonList(getName()), rate, unit.toMillis(rateInterval), type.ordinal());
}

For example, it is easier to understand:

For example, in the following code, 3 tokens are generated in 5 seconds, and all instances are shared (RateType.OVERALL is shared by all instances, and RateType.CLIENT is shared by single instance end)

trySetRate(RateType.OVERALL, 3, 5, RateIntervalUnit.SECONDS);

Then three parameters will be set in redis:

hsetnx,key,rate,3

hsetnx,key,interval,5

hsetnx,key,type,0

Then look at the tryAcquire(1) method: the underlying source code is as follows

private <T> RFuture<T> tryAcquireAsync(RedisCommand<T> command, Long value) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
            "local rate = redis.call('hget', KEYS[1], 'rate');"  //1
          + "local interval = redis.call('hget', KEYS[1], 'interval');"  //2
          + "local type = redis.call('hget', KEYS[1], 'type');" //3
          + "assert(rate ~= false and interval ~= false and type ~= false, 'RateLimiter is not initialized')" //4
          
          + "local valueName = KEYS[2];" //5
          + "if type == 1 then "
              + "valueName = KEYS[3];" //6
          + "end;"
          
          + "local currentValue = redis.call('get', valueName); " //7
          + "if currentValue ~= false then " 
                 + "if tonumber(currentValue) < tonumber(ARGV[1]) then " //8
                     + "return redis.call('pttl', valueName); "
                 + "else "
                     + "redis.call('decrby', valueName, ARGV[1]); " //9
                     + "return nil; "
                 + "end; "
          + "else " //10
                 + "redis.call('set', valueName, rate, 'px', interval); " 
                 + "redis.call('decrby', valueName, ARGV[1]); "
                 + "return nil; "
          + "end;",
            Arrays.<Object>asList(getName(), getValueName(), getClientValueName()), 
            value, commandExecutor.getConnectionManager().getId().toString());
}

The 1st, 2nd, and 3rd remark lines are to get the 3 values ​​of the previous set: rate, interval, type. If these 3 values ​​are not set, it will directly return that rateLimiter is not initialized.

The 5th remark line declares a variable called valueName with the value of KEYS[2], the corresponding value of KEYS[2] is the getValueName() method, and getValueName() returns the key we set in the first step of getRateLimiter above; if type=1, Indicates global sharing, then the value of valueName is changed to KEYS[3], and the corresponding value of KEYS[3] is getClientValueName(). Check the source code of getClientValueName():

String getClientValueName() {
        return suffixName(getValueName(), commandExecutor.getConnectionManager().getId().toString());
   }

ConnectionManager().getId() is as follows:

public interface ConnectionManager {
    
    UUID getId();
 
    omit...
}

This getId() is the UUID generated when each client is initialized, that is, the getId of each client is unique, which also verifies the role of RateType.ALL and RateType.PER_CLIENT in the trySetRate method.

  • Then look at the 7th standard line to get the value currentValue corresponding to valueName; the first time you get it must be empty, then look at the 10th standard line else logic
  • set valueName 3 px 5, set key=valueName value=3 expiration time is 5 seconds
  • decrby valueName 1, subtract 1 from the value of valueName above
  • Then if the second visit, the value returned by the 7th label line exists, the 8th label line will be taken, and then the following judgment will be performed.
  • If the value of the current valueName is 3, which is less than the number of tokens to be obtained (the input parameter in the tryAcquire method), it means that the number of tokens has been used up in the current time (within 5 seconds of the validity period of the key), and returns pttl (the remaining expiration time of the key); otherwise, it means that there are enough tokens in the bucket, after the acquisition, the number of tokens in the bucket will be decremented by 1, and this is the end.

Summarize

Redission distributed current limiting adopts the idea of ​​token bucket and fixed time window. The trySetRate method sets the size of the bucket, uses the redis key expiration mechanism to achieve the purpose of time window, and controls the amount of requests allowed to pass within the fixed time window.