An overview of the

When designing the service, we will encounter different requirements of speed limit and frequency limit. According to different demand scenarios, we will have corresponding solutions.

Service speed limit

Demand scenarios

  • The single machine needs to limit the QPS of some code blocks, such as database insert operation, Redis write operation, etc.
  • When the specified QPS is exceeded, the thread is automatically blocked or the specified code block logic is automatically discarded.
  • Instead of their own speed limit through the way of sleep;

The solution

Use Guava’s RateLimiter tool class to implement traffic limiting based on the token bucket algorithm.

Generally, RateLimiter provides a single-machine traffic limiting solution. If the overall QPS flow control of the service is needed, the token bucket algorithm can be implemented based on Redis. It is also possible to break down the entire QPS into access QPS for individual machines and control them accordingly (this approach is not very accurate, especially if the service QPS limit is low).

Method of use

private final RateLimiter rateLimiter = RateLimiter.create(100); // 100QPS

// Block limit QPS
void foo1(a) {
  rateLimiter.acquire(); // There may be a blockage here
  // The actual logical block executed
}
 
 
// No blocking limit QPS
void foo2(a) {
  if (rateLimiter.tryAcquire()) { // There will be no blocking
     // The code path allowed to execute in QPS
  } else {
     // The code path to execute when the specified QPS is exceeded}}Copy the code

Matters needing attention

The blocking version of RateLimter is generally not recommended for speed limiting in synchronous business requests, and is more commonly used in consumer scenarios (such as Kafka consumption, scheduled tasks, and so on).

The number of times the interval is limited

Demand scenarios

  • High frequency scenario

    For example, the number of interface requests in one day.

  • Low-frequency scene

    For example, users can only send 5 feeds within an hour.

The solution

  • High frequency scenarios (Redis Counter)

    In high frequency scenarios, we generally do not pay much attention to the specific frequency limit, so we do not require special accuracy in counting.

    We can count the frequency through Redis Counter. For example, we can design 20190403_appkey in a similar way. On 20190403, all interface access operations will add 1 to this key. In this way, we can roughly count all the request times of the day and perform the corresponding limit logic.

        private final FastDateFormat fastDateFormat = FastDateFormat.getInstance("MMddHH");
    
        @Resource
        private RedisClient rc;
    
        public long incrCount(String appkey, long time) {
            String key = key(appkey, time);
            return rc.incr(key);
        }
    
        private String key(String appkey, long time) {
            return appkey + "_" + fastDateFormat.format(time);
        }
    Copy the code
  • Low-frequency scene

    In low-frequency scenes, accurate counting is required because the number of times is limited.

    Redis SortedSet

    We can use Redis SortedSet to maintain the feed set. Member is the corresponding dynamic ID of the feed, and score is the corresponding dynamic release time of the feed. When a user posts a feed dynamic, the number of valid elements is counted and the corresponding limit logic is applied.

    This is an arbitrary time interval, not a fixed time interval. If a user posts 5 feeds at 0:59, the user can’t post at 1:0 and will have to wait until 1:59 before the restriction is lifted.

        @Resource
        private RedisClient rc;
    
        public void add(User user, FeedItem feedItem) {
            rc.zadd(key(user), feedItem.getCreateTime(), feedItem.getFeedId());
            double min = 0;
            double max = (double) System.currentTimeMillis()
                    - TimeUnit.HOURS.toMillis(1);
            rc.zremrangeByScore(key(user), min, max);
        }
    
        public List<String> get(User user, long curTime) {
            double max = curTime;
            double min = (double) curTime
                    - TimeUnit.HOURS.toMillis(1);
            Set<byte[]> value = rc.zrevrangeByScore(key(user), max, min);
            return value.stream().map(RedisUtils::toString).collect(Collectors.toList());
        }
    
        private String key(User user) {
            return String.valueOf(user.getUid());
        }
    Copy the code

    Redis Hash

    If our requirements became more complex, we would need to limit users to Posting N feed feeds based on the dynamic type.

    We can use Redis Hash structure to maintain N feed dynamics recently published by users, where field is dynamic type and value is feed dynamic list. When users publish feed dynamics, they need to obtain the corresponding type of publish feed set, and filter out the expired feed set to determine whether users allow to publish. If publishing is allowed, add the new feed to the corresponding collection. (Note: The collection of expired feeds is filtered, so there won’t be more data)

    Matters needing attention

    • Beware of concurrency. You can solve the problem of concurrent access with distributed locks. In this case, the operation is generally low frequency, and the possibility of concurrency is low;

    • Using a Sorted Set is more complex than using a Sorted Set, but reduces the space footprint.

summary

The above is our common speed limit frequency requirements, this article briefly introduces some basic ideas. In practical application scenarios, there may be more ingenious solutions that can be communicated together.