Сценарии для услуг с ограниченной частотой

задняя часть

Обзор

При разработке сервисов мы столкнемся с различными требованиями к скорости и частоте. В соответствии с различными сценариями спроса у нас будут соответствующие решения.

Ограничение скорости обслуживания

сценарий спроса

  • Одному компьютеру необходимо ограничить количество запросов в секунду для некоторых блоков кода, таких как операции вставки в базу данных, операции записи Redis и т. д.;
  • Автоматически блокировать поток или автоматически отбрасывать указанную логику блока кода при превышении указанного количества запросов в секунду;
  • Вместо реализованной самостоятельно схемы ограничения скорости через сон;

решение

Используйте класс инструментов RateLimiter от Guava, чтобы реализовать ограничение трафика на основе алгоритма корзины токенов.

Как правило, RateLimiter представляет собой автономное решение для ограничения тока. Если вам нужно реализовать общее управление потоком запросов в секунду для службы, вы можете реализовать алгоритм корзины маркеров на основе Redis. Вы также можете разделить общий QPS на QPS доступа одной машины, а затем соответствующим образом управлять им (управление QPS таким образом не очень точно, особенно когда лимит сервисного QPS относительно низок).

инструкции

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

// 堵塞限制QPS
void foo1() {
  rateLimiter.acquire(); // 在这里有可能发生堵塞
  // 实际执行的逻辑块
}
 
 
// 不堵塞限制QPS
void foo2() {
  if (rateLimiter.tryAcquire()) { // 这里不会发生任何堵塞行为
     // QPS之内允许执行的代码路径
  } else {
     // 超过指定QPS时执行的代码路径
  }
}

Меры предосторожности

Как правило, не рекомендуется использовать блокирующую версию RateLimter для ограничения скорости в синхронных бизнес-запросах.В потребительских сценариях используется больше сценариев использования (например, потребление Kafka, запланированные задачи и т. д.).

ограничение временного интервала

сценарий спроса

  • высокочастотная сцена

    Например, количество запросов интерфейса за один день;

  • низкочастотная сцена

    Например, пользователь может отправить только 5 фидов в течение часа;

решение

  • высокочастотные сцены (Redis Counter)

    В высокочастотных сценариях мы, как правило, не уделяем слишком много внимания конкретным временам ограничения частоты, поэтому от подсчета не требуется особой точности.

    Мы можем посчитать частоту через счетчик Redis. Например, количество запросов к интерфейсу за один день, мы можем спроектировать 20190403_appkey следующим образом.Все обращения к интерфейсу в день 20190403 добавят 1 к этому ключу, так что мы можем грубо подсчитать все количество запросов в этот день, и выполнить соответствующую предельную логику.

        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);
        }
    
  • низкочастотная сцена

    В низкочастотных сценариях требуется точный подсчет, поскольку количество раз ограничено.

    Redis SortedSet

    Мы можем использовать Redis SortedSet для поддержки коллекции фидов, member — это соответствующий динамический идентификатор фида, а score — это время динамической публикации соответствующего фида. Когда пользователь публикует динамику фида, сначала вычисляется количество допустимых элементов, и, наконец, выполняется соответствующая логика ограничения.

    Здесь произвольные временные интервалы, а не фиксированные временные интервалы. Если пользователь публикует 5 фидов в 00:59, пользователь не может опубликовать их в 1:00, и ограничение может быть снято в 1:59.

        @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());
        }
    

    Redis Hash

    Если наши требования станут более сложными, нам нужно ограничить пользователей только динамикой N фидов на основе динамического типа.

    Мы можем поддерживать N фидов, недавно опубликованных пользователем, через хеш-структуру Redis, где поле — это динамический тип, а значение — это динамический список фидов. Когда пользователь публикует динамику фидов, необходимо получить соответствующий тип опубликованной коллекции фидов, отфильтровать коллекцию фидов с истекшим сроком действия и определить, разрешено ли пользователю публиковать. Если публикация разрешена, добавьте новый канал в соответствующую коллекцию. (Примечание. Коллекция фидов с истекшим сроком действия была отфильтрована, поэтому данные не будут увеличиваться.)

    Меры предосторожности

    • Обратите внимание на параллелизм, вы можете решить проблему параллельного доступа через распределенные блокировки. Как правило, это низкочастотная операция, и вероятность параллелизма невелика;

    • Его сложнее использовать, чем Sorted Set, но он может занимать меньше места;

резюме

Выше приведены наши общие требования к ограничениям скорости и частоты.Эта статья кратко представляет некоторые основные идеи. В сценариях практического применения могут быть более оригинальные решения, которые можно сообщать вместе.