Как реализовано аннотированное ограничение тока

Java

Чем больше вы знаете, тем больше вы не знаете

  Вопрос часто приводит к череде вопросов, и слепое пятно знаний незаметно для себя открываю🤣. рут в сделай сам限流注解При столкновении с проблемами верно, что один больше, чем один:

  1. Какой алгоритм ограничения тока является более подходящим?
  2. Как реализовать ограничение тока с помощью аннотаций
  3. Как регулировать каждый метод по отдельности
  4. Как преобразовать длинные строки в короткие строки
  5. шестигранникorшестнадцатеричный
  6. Что такое LRU и как его реализовать с помощью простых структур данных

упражняться

Что такое ограничение тока

   накладывает ограничения на запросы, принимаемые сервером, только часть запросов может реально дойти до сервера, а остальные запросы могут задерживаться или отклоняться. Тем самым избегая всех запросов к БД и ломая БД.
Возьмите сценарий, с которым вы можете столкнуться в своей жизни, особенно в Пекине, Шанхае, Гуанчжоу и Шэньчжэне или в новых городах первого уровня, метро Ханчжоу, линия 1, станция Fengqi Road, когда пассажиропоток достигает определенного пика, дядя-полицейский 👮‍ ♀ Вам могут запретить садиться в метро и пользоваться другими видами транспорта.️. . . Это все слезы

Какой алгоритм ограничения тока является более подходящим?

   По поводу текущего алгоритма ограничения, в интернете много объяснений, алгоритм дырявого ведра, алгоритм ведра с токеном и т.д.,百度一下,你就知道, где рут реализован с помощью простейшего алгоритма счетчика.

Алгоритм счетчика

  1. Разделите одну секунду на 10 этапов по 100 мс каждый.
  2. Количество вызовов интерфейса записывается каждые 100 мс.
  3. Конечно, со временем этапов будет все больше и больше. В это время можно удалить первые n стадий, и останется только 10 стадий, то есть останется только 1с.
  4. Количество вызовов последнего минус первого — это количество вызовов интерфейса за 1 с.

Как реализовать ограничение тока с помощью аннотаций

С использованиемnginxКогда ток ограничен, он будетnginxПоскольку прокси-уровень перехватывает запрос, обрабатывает его, то вSpringСредний уровень агентаAOPЛа

AOP

На веб-сервере существует множество сценариев, которые могут быть реализованы с помощью АОП, например:

  1. Распечатать журнал, записать класс времени, метод, параметр
  2. Используйте отражение, чтобы установить значение по умолчанию для подкачки PageRow, PageNum
  3. Игровая сцена, чтобы судить, закончилась ли игра, не нужно судить по всем методам
  4. Расшифровка, проверка подписи и т.д.

задача на время

  В алгоритме счетчика мы упомянули, что количество обращений к интерфейсу нужно записывать каждые 100 мс и сохранять. Здесь пригодятся временные задачи.
Существует множество реализаций    задач с синхронизацией, таких как использование пулов потоков.ScheduledExecutorService,КонечноSpringизScheduledБез проблем.
  Во-вторых, какая структура данных используется для сохранения количества звонков -->LinkedList.
   Кроме того, нам нужно ограничить поток несколькими методами, как это решить? -->Каждый метод имеет уникальное соответствующее значение:package + class + methodName, поэтому мы используем это уникальное значение в качестве ключа, linkedList в качестве карты, следующий код

    /** 每个key 对应的调用次数**/
    private Map<String, Long> countMap = new ConcurrentHashMap<>();
    
    /** 每个key 对应的linkedlist**/
    private static Map<String, LinkedList<Long>> calListMap = new ConcurrentHashMap<>();

    ## 每s一次查询
    @Scheduled(cron = "*/1 * * * * ?")
    private void timeGet(){
        countMap.forEach((k,v)->{
            LinkedList<Long> calList = calListMap.get(k);
            if(calList == null){
                calList = new LinkedList<>();
            }
            # 每个方法的调用次数放入linkedList中
            calList.addLast(v);
            calListMap.put(k, calList);

            if (calList.size() > 10) {
                calList.removeFirst();
            }
        });
    }

инспекция АОП

определить аннотации

import java.lang.annotation.*;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CalLimitAnno {

    String value() default "" ;

    String methodName() default "" ;

    long count() default 100;
}

Проверка перед вызовом интерфейса

@Around(value = "@annotation(around)")
    public Object initBean(ProceedingJoinPoint point, CalLimitAnno around) throws Throwable {
        /** 获取类名和方法名 **/
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String[] classNameArray = method.getDeclaringClass().getName().split("\\.");
        String methodName = classNameArray[classNameArray.length - 1] + "." + method.getName();
        String classZ = signature.getDeclaringTypeName();
        String countMapKey =  classZ + "|" + methodName;


        LinkedList<Long> calList = calListMap.get(countMapKey);
        if(calList != null){
            /** 调用次数判断是否已经超过注解设置的值 **/
            if ((calList.peekLast() - calList.peekFirst()) > Long.valueOf(around.count())) {
                throw new RuntimeException("被限流了");
            }
            /** 存放**/
            countMap.putIfAbsent(countMapKey,0L);
            countMap.put(countMapKey,countMap.get(countMapKey) + 1);
        }
        Object object = point.proceed();
        return object;
    }

метод

Учитывая, что частота задач, выполняемых по времени, не может быть слишком маленькой, наши задачи, выполняемые по времени, выполняются один раз в секунду, и здесь нам нужно установить текущее предельное значение в 10 секунд, что приведет к большей детализации.

@CalLimitAnno(count = 1000)
    public void testPageAnno(){
        System.out.println("成功执行");
    }

Оптимизация карты

   Выше мыpackage + className + methodNameВ качестве единственного ключа длина ключа становится особенно большой. Должны ли мы найти способ уменьшить длину ключа?
  Некоторые учащиеся думают о сжатии, но это совершенно нереально. По конкретным причинам см.Ссылка на сайт.
  Это нельзя использовать, и это нельзя использовать, и это будет мешать людям жить🥺. Вы когда-нибудь думали, что текстовые сообщения, которые вы обычно получаете, иногда содержат короткую ссылку, и эти короткие ссылки на самом деле являются используемым передатчиком чисел --> Получите уникальный идентификатор с автоинкрементом из службы, а затем преобразуйте этот идентификатор. Например, в это время оно увеличивается до 100000, а затем преобразуется из десятичного числа 100000 в 62.q0U. Это похоже на ссылку в текстовом сообщении, не так ли?

Сохранение карты

Поскольку    самоувеличивается, одни и те же длинные символы, преобразованные в короткие строки при вызове службы, различаются. В некоторых бизнес-сценариях он может вызываться часто, что требует хранилища kv. В противном случае нет необходимости делать хранилище, делать все больше и больше ошибок~

kv оптимизация хранения

   Предполагая, что нам нужно сделать хранилище кв, то, что может придумать детская обувь, вероятно,jvmпамять илиredis. Поскольку эта корреспонденция обычно долго не хранится, ее обычно используют как запрос в горячем событии. еслиredis, вы можете установить время истечения срока действия как выселение. затем вjvmВ памяти надо учитывать, чтоLRU. последний использованный

  1. Используемый ключ необходимо поместить в начало очереди
  2. Наименее часто используемые должны быть удалены после превышения лимита длины очереди.
    Итак, какая структура данных нам нужна для реализации этой условной очереди?

GET

  1. Предполагая, что ключ не существует, вернуть null
  2. Предполагая, что ключ существует, когда вам нужно вернуть значение, вам нужно удалить соответствующий ключ и поместить ключ в начало очереди.

   В приведенном выше сценарии это неприменимо к коллекции, базовым слоем которой является массив, такой как ArrayList. Не говорите, что вы не можете понять это. .
   Тогда есть только связанный список, такой как LinkedList, но LinkedList необходимо пройти при запросе. Если мы сохраним на карте одновременно с LinkedList, этого достаточно? Конечно. . . . не этоmapУ меня есть запрос,nodeПредыдущий узел необходимо сохранить. Таким образом, когда значение найдено, можно получить предыдущий узел, а соответствующий узел удалить из связанного списка.

PUT

  1. Предполагая, что ключ не существует, поместите его в начало очереди.
  2. Предполагая, что ключ существует, удалите ключ и поместите его в начало очереди.

проходить черезGetПредвосхищение, само собой разумеется

Окончательный результат: LinedHashMap

LinkedHashMapКонкретная колея здесь не навязана, или百度一下,你就知道

конец

   Небезопасность потока, вызванная параллелизмом, здесь не рассматривается, просто ссылка~~~
   После долгого разговора у вас все еще должно быть некоторое замешательство. Пожалуйста, оставьте сообщение ниже. Ни в коем случае, бедный язык.