Рубрики
Uncategorized

Http -дросселя с использованием Lyft Global RateLimiting

Некоторое время назад для своего проекта я искал хороший сервис, ограничивающий ставку. Для прицела T … Tagged DevOps, Linux, Docker.

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

Nginx Plus и Kong, безусловно, имеют ограничивающие скорость особенности, но не OSS; Пока я более крупный поклонник OSS. Использование сервисной сетки iStio было бы излишним. Поэтому я решил использовать Envoy Proxy + Lyft RateLimiting Анкет

Цель этого блога-помочь вам начать работу с ограничивающим ставку службой и настраивать различные комбинации сценариев, ограничивающих скорость.

Давайте погрузимся в…

Конфигурация ratelimit состоит из

1. Домен : Домен — это контейнер для набора пределов скорости. Все домены, известные услуге RateLimit, должны быть глобально уникальными. Они служат способом для различных команд/проектов иметь ограничение на скорость конфигурации, которые не конфликтуют.

2. Дескриптор : Дескриптор — это список паров ключей/значения, принадлежащих домену, который использует служба RateLimit для выбора правильного ограничения скорости. Дескрипторы чувствительны к случаям. Примеры дескрипторов: * («База данных», «Пользователи») * («message_type», «Marketing»), («to_number», «2061234567») * («to_cluster», «service_a») * («to_cluster», «service_a»), («from_cluster», «service_b»)

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

Мы будем выполнять ограничение скорости на основе различных заголовков HTTP. Давайте посмотрим на файл конфигурации.

domain: apis
descriptors:
  - key: generic_key
    value: global
    rate_limit:
      unit: second
      requests_per_unit: 60
  - key: generic_key
    value: local
    rate_limit:
      unit: second
      requests_per_unit: 50
  - key: header_match
    value: "123"
    rate_limit:
      unit: second
      requests_per_unit: 40
  - key: header_match
    value: "456"
    rate_limit:
      unit: second
      requests_per_unit: 30
  - key: header_match
    value: post
    rate_limit:
      unit: second 
      requests_per_unit: 20
  - key: header_match
    value: get
    rate_limit:
      unit: second 
      requests_per_unit: 10
  - key: header_match
    value: path
    rate_limit:
      unit: second 
      requests_per_unit: 5
#Using nested descriptors
  - key: custom_header
    descriptors:
    - key: plan
      value: BASIC
      rate_limit:
        requests_per_unit: 2
        unit: second
    - key: plan
      value: PLUS
      rate_limit:
        requests_per_unit: 3
        unit: second

В приведенной выше конфигурации можно ясно видеть

1. Есть разные ключи с различными значениями RateLimit. 2. Мы можем использовать их во всем мире для всего Vhost в посланнике или даже на местном уровне для конкретного пути. 3. Мы также можем иметь вложенные значения дескрипторов.

Давайте посмотрим, как мы можем использовать их в конфигурации Envoy. Посмотрите на конфигурацию ниже:

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:        
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          stat_prefix: ingress_http
          codec_type: AUTO
          route_config:
            name: local_route
            virtual_hosts:
            - name: nginx
              domains:
              - "*"
              rate_limits:
                - stage: 0
                  actions:
                    - generic_key: 
                        descriptor_value: "global"
              routes:
              - match:
                  prefix: "/nginx_1"
                route:
                  cluster: nginx_1
                  include_vh_rate_limits: true
                  rate_limits:
                    - actions:
                        - generic_key:
                            descriptor_value: "local"
                    - actions:
                        - header_value_match:
                            descriptor_value: "get"
                            headers: 
                              - name: ":method"
                                prefix_match: "GET" 
                    - actions:  #This will be triggered if `X-CustomHeader` is present AND the X-CustomPlan header has a value of either BASIC or PLUS
                      - requestHeaders:
                          descriptor_key: "custom_header"
                          header_name: "X-CustomHeader"
                      - requestHeaders:
                          descriptor_key: "plan"
                          header_name: "X-CustomPlan"                                                                                    
              - match:
                  prefix: "/nginx_2"
                route:
                  cluster: nginx_2
                  include_vh_rate_limits: true
                  rate_limits:  
                    - actions:
                        - generic_key:
                            descriptor_value: "local"
                    - actions:
                        - header_value_match:
                            descriptor_value: "123"
                            headers: 
                              - name: "X-MyHeader"
                                prefix_match: "123"
                    - actions:
                        - header_value_match:
                            descriptor_value: "456"
                            headers: 
                              - name: "X-MyHeader"
                                prefix_match: "456"
                    - actions:
                        - header_value_match:
                            descriptor_value: "post"
                            headers: 
                              - name: ":method"
                                prefix_match: "POST"
                    - actions:
                        - header_value_match:
                            descriptor_value: "path"
                            headers: 
                              - name: ":path"
                                prefix_match: "/nginx"
          http_filters:
          - name: envoy.rate_limit
            config:
              domain: apis
              failure_mode_deny: false
              rate_limit_service:
                grpc_service:
                  envoy_grpc:
                    cluster_name: rate_limit_cluster
                  timeout: 0.25s

          - name: envoy.router

  clusters:
  - name: nginx_1
    connect_timeout: 1s
    type: strict_dns
    lb_policy: round_robin
    hosts:
    - socket_address:
        address: nginx1
        port_value: 80

  - name: nginx_2
    connect_timeout: 1s
    type: strict_dns
    lb_policy: round_robin
    hosts:
    - socket_address:
        address: nginx2
        port_value: 80

  - name: rate_limit_cluster
    type: strict_dns
    connect_timeout: 0.25s
    lb_policy: round_robin
    http2_protocol_options: {}
    hosts:
    - socket_address:
        address: ratelimit
        port_value: 8081

admin:
  access_log_path: "/dev/null"
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901

Вот как это работает:

1. Мы определили Одиночный Vhost названный nginx который соответствует всем доменам. 2. Для этого Vhost определяется глобальная ограничение ставки. Значение дескриптора — глобальное Анкет 3.next, у нас есть 2 кластера Под этим Vhost. А именно, nginx1 и nginx2 Анкет Маршруты для PATH/NGINX1 направляются в nginx1 кластер и аналогично Nginx2. 4. Для Nginx1 существует общий предел скорости, определяемый значением дескриптора локальным И тогда у нас есть ограничения по скорости для разных значений Стандартные заголовки HTTP, такие как метод, путь и т. Д., И некоторые пользовательские заголовки HTTP, такие как X-Customheader Анкет 5. У нас есть аналогичный набор ограничивающих скоростей для кластера Nginx2. 6. Эти 2 кластеры Nginx, определенные здесь, на самом деле относятся к 2 различным контейнерам Nginx, работающим в рамках стека Docker-Compose.

Общая архитектура может быть визуализирована как

Все файлы конфигурации для этой настройки можно найти Здесь Анкет Поскольку они работают в той же сети, что и прокси -сервер RateLimiter и Envoy, к ним можно легко получить доступ, используя имя контейнера.

Чтобы запустить настройку, просто клонировать репо и

docker-compose up 

Как только стек встал, у вас будет

1.2 nginx контейнеры, работающие на порту 9090 и 9091 местный хост. 2. Прокси -сервера для перехвата и ретрансляции на серверы Nginx. Консоль администратора Endoy может быть достигнута в Localhost: 9901 Анкет 3.envoy будет слушать как Localhost: 10000 Анкет 4. Служебный контейнер с настройками с настройками скорости, которые будут использоваться для послужного заведения. 5. Redis Container, который используется услугами RateLimiting.

Важно понимать, что все применимые действия для конкретного пути в кластере агрегированы Ratelimiter для результата, то есть

Логично ИЛИ из всех применимых пределов

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

brew update && brew install vegeta

Тестовый сценарий 1

Случай : Получить запрос на /nginx_1/ по 100 запросам в секунду ожидаемый результат : 10% запросов успешно. (Логично Или из "descriptor_value" : "Global" , "descriptor_value": "Local" и "descriptor_value": "Get" ) Команда : Echo "Get http://localhost: 10000/nginx_1/" | Вегета атака | Вегета отчет Фактический результат

$ echo "GET http://localhost:10000/nginx_1/" | vegeta attack -rate=100 -duration=0 | vegeta report
          Requests      [total, rate, throughput]  1008, 100.12, 10.92
          Duration      [total, attack, wait]      10.071526192s, 10.06832056s, 3.205632ms
          Latencies     [mean, 50, 95, 99, max]    4.718253ms, 4.514212ms, 7.426103ms, 9.089064ms, 17.916071ms
          Bytes In      [total, mean]              68640, 68.10
          Bytes Out     [total, mean]              0, 0.00
          Success       [ratio]                    10.91%
          Status Codes  [code:count]               200:110  429:898  
          Error Set:                               429 Too Many Requests

— —

Тестовый сценарий 2 :

Случай : Пост запрос на /nginx_1/ при 100 запросах в секунду. WederSult : 50% запросов успешно. (Логично Или из "descriptor_value" : "Global" и "descriptor_value": "Local" ) Команда : Echo "post http://localhost: 10000/nginx_1/" | Вегета атака | Вегета отчет Фактическое развлечение :

$ echo "POST http://localhost:10000/nginx_1/" | vegeta attack -rate=100 -duration=0 | vegeta report
 Requests [total, rate, throughput] 4344, 100.02, 50.56
 Duration [total, attack, wait] 43.434227783s, 43.429286664s, 4.941119ms
 Latencies [mean, 50, 95, 99, max] 5.190485ms, 5.224978ms, 7.862512ms, 10.340628ms, 20.573212ms
 Bytes In [total, mean] 1370304, 315.45
 Bytes Out [total, mean] 0, 0.00
 Success [ratio]  50.55%
 Status Codes [code:count] 200:2196 429:2148 
 Error Set:                429 Too Many Requests

— —

Тестовый сценарий 3 :

Случай : Получить запрос на /nginx_2/ по 100 запросам в секунду с X-Myheader: 123 Ожидаемый результат : 5% запросов успешно (логично Или из "descriptor_value" : "Global" , "descriptor_value": "Local" , "descriptor_value": "123" и "descriptor_value": "path" ) Команда : Echo "Get http://localhost: 10000/nginx_2/" | Вегета атака -Хедер "X -Myheader: 123" | Вегета отчет Фактический результат :

$ echo "GET http://localhost:10000/nginx_2/" | vegeta attack -rate=100 -duration=0 -header "X-MyHeader: 123" | vegeta report
          Requests      [total, rate, throughput]  3861, 100.03, 5.18
          Duration      [total, attack, wait]      38.604406747s, 38.597776398s, 6.630349ms
          Latencies     [mean, 50, 95, 99, max]    4.96685ms, 4.673049ms, 7.683458ms, 9.713522ms, 16.875025ms
          Bytes In      [total, mean]              124800, 32.32
          Bytes Out     [total, mean]              0, 0.00
          Success       [ratio]                    5.18%
          Status Codes  [code:count]               200:200  429:3661  
          Error Set:                               429 Too Many Requests

— — — Тестовый сценарий 4 :

Случай : Пост запрос на /nginx_2/ по 100 запросам в секунду с X-Myheader: 456 Ожидаемый результат : 5% запросов успешно (логично Или из "descriptor_value": "Global" , "descriptor_value": "Local" , "descriptor_value": "post" , "descriptor_value": "456" и "descriptor_value": "path" ) Команда : Echo "post http://localhost: 10000/nginx_2/" | Вегета атака -Хедер "X -Myheader: 456" | Вегета отчет Фактический результат :

$ echo "POST http://localhost:10000/nginx_2/" | vegeta attack -rate=100 -duration=0 -header "X-MyHeader: 456" | vegeta report
 Requests [total, rate, throughput] 2435, 100.04, 5.13
 Duration [total, attack, wait] 24.346703709s, 24.339554311s, 7.149398ms
 Latencies [mean, 50, 95, 99, max] 5.513994ms, 5.255698ms, 8.239173ms, 10.390515ms, 20.287931ms
 Bytes In [total, mean] 78000, 32.03
 Bytes Out [total, mean] 0, 0.00
 Success [ratio] 5.13%
 Status Codes [code:count] 200:125 429:2310 
 Error Set: 429 Too Many Requests

— — — Тестовый сценарий 5 : Случай : Получить запрос на /nginx_1/ по 100 запросам в секунду с X-Customheader: xyz и X-custplan: Плюс Ожидаемый результат : 3% запросов успешно (логично Или из "descriptor_value": "Global" , "descriptor_value": "Local" , "descriptor_value": "Get" , "descriptor_key": "custom_header" и "descriptor_key": "план" ) Команда : Echo "Get http://localhost: 10000/nginx_1/" | Вегета атака -Хедер "X -Customheader: xyz" -Header "x -custplan: плюс" | Вегета отчет Фактический результат :

$ echo "GET http://localhost:10000/nginx_1/" | vegeta attack -rate=100 -duration=0 -header "X-CustomHeader: XYZ" -header "X-CustomPlan: PLUS" | vegeta report 
 Requests [total, rate, throughput] 2372, 100.05, 3.16
 Duration [total, attack, wait] 23.71156424s, 23.707710415s, 3.853825ms
 Latencies [mean, 50, 95, 99, max] 5.396743ms, 5.179951ms, 7.981171ms, 10.084753ms, 15.086778ms
 Bytes In [total, mean] 46800, 19.73
 Bytes Out [total, mean] 0, 0.00
 Success [ratio] 3.16%
 Status Codes [code:count] 200:75 429:2297 
 Error Set: 429 Too Many Requests

— — — Тестовый сценарий 6 : Случай : Получить запрос на /nginx_1/ по 100 запросам в секунду с X-Header: xyz и x-custmplan: Плюс Ожидаемый результат : 10% запросов успешно (логично Или из "descriptor_value": "Global" , "descriptor_value": "Local" , "descriptor_value": "Get" ) Команда : Echo "Get http://localhost: 10000/nginx_1/" | Вегета атака -Хедер "x -Header: xyz" -header "x -custplan: плюс" | Вегета отчет Фактический результат:

$ echo "GET http://localhost:10000/nginx_1/" | vegeta attack -rate=100 -duration=0 -header "X-Header: XYZ" -header "X-CustomPlan: PLUS" | vegeta report
 Requests [total, rate, throughput] 1578, 100.07, 10.78
 Duration [total, attack, wait] 15.773500478s, 15.769158977s, 4.341501ms
 Latencies [mean, 50, 95, 99, max] 4.748516ms, 4.514902ms, 7.076671ms, 8.518779ms, 16.077828ms
 Bytes In [total, mean] 106080, 67.22
 Bytes Out [total, mean] 0, 0.00
 Success [ratio] 10.77%
 Status Codes [code:count] 200:170 429:1408 
 Error Set: 429 Too Many Requests

В сценарии производства вы можете запустить несколько экземпляров вашего прокси, которые могут относиться к одному и тому же кластеру RateLimit. Передняя прокси в основном без сохранения состояния.

Что касается услуги RateLimiting, я бы порекомендовал масштабировать его горизонтально и перемещать кэш Redis в облачный сервис, такой как Redislabs или AWS Elastic-Cache.

Кроме того, используя отдельное Redis за пределы в секунду настоятельно рекомендуется. Все, что вам нужно сделать, это:

  1. Установите Env var, Redis_persecond : "истинный"
  2. Установить Redis Endpoint, Redis_persecond_url

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

Оригинал: «https://dev.to/appfleet/http-throttling-using-lyft-global-ratelimiting-4c5i»