阅读 3921

Kong 限流插件

限流的场景:服务提供的能力是有限的。为了防止大量的请求将服务拖垮,可以通过网关对服务的请求做限流。例如:某个服务1s只能处理100个请求,超过限流阈值的请求丢弃。

一、rate-limiting

Kong网关提供的限流插件:rate-limiting

1.1 配置的参数

从插件参数中可以获得这些信息:

  1. 限流支持的粒度有三种:consumercredentialip,缺省是consumer
  2. 存储策略支持三种:localclusterredis,缺省是cluster。存储的内容是:以 fmt("ratelimit:%s:%s:%s:%s", api_id, identifier, period_date, name) 为key,持续访问的次数为value。
  • local:存储在Nginx共享内容 - 支持多work进程间访问
  • cluster:数据库中rate-limiting表中 - 支持集群访问
  • redis:插件配置的Redis中 - 支持集群访问
  1. 支持各种时间单位的限流
  fields = {
    second = { type = "number" },
    minute = { type = "number" },
    hour = { type = "number" },
    day = { type = "number" },
    month = { type = "number" },
    year = { type = "number" },
    limit_by = { type = "string", enum = {"consumer", "credential", "ip"}, default = "consumer" },
    policy = { type = "string", enum = {"local", "cluster", REDIS}, default = "cluster" },
    fault_tolerant = { type = "boolean", default = true },
    redis_host = { type = "string" },
    redis_port = { type = "number", default = 6379 },
    redis_password = { type = "string" },
    redis_timeout = { type = "number", default = 2000 },
    redis_database = { type = "number", default = 0 },
    hide_client_headers = { type = "boolean", default = false },
  },
复制代码

1.2 限流粒度

当限制类型(限制粒度)为consumeridentifier的时候,限流插件需要一些会在nginx.ctx中注入认证信息的插件,比如:jwt、key-auth、oauth2

  if conf.limit_by == "consumer" then
    identifier = ngx.ctx.authenticated_consumer and ngx.ctx.authenticated_consumer.id
    if not identifier and ngx.ctx.authenticated_credential then -- Fallback on credential
      identifier = ngx.ctx.authenticated_credential.id
    end
  elseif conf.limit_by == "credential" then
    identifier = ngx.ctx.authenticated_credential and ngx.ctx.authenticated_credential.id
  end
  # 如果请求中不带认证,粒度会退化到IP限流
  if not identifier then
    identifier = ngx.var.remote_addr
  end
  
复制代码

1.3 限流算法

通常的限流的做法是一段时间内的请求次数。但如果只通过一段时间来限时会遇到流量分布不均的问题。例如:某个服务限制一小时的请求次数是3600次,那么如果在某一刻一下子来了一堆请求超过的阈值。为了解决这个问题,kong将一段时间拆细,将阈值设成每分钟60每秒钟1次。

  local limits = {
    second = conf.second,
    minute = conf.minute,
    hour = conf.hour,
    day = conf.day,
    month = conf.month,
    year = conf.year
  }
复制代码

计算remaining。每次请求进来,插件从存储中获取到当前服务remaining(剩余)的访问次数,返回的usage的格式(其中name是上面提到的limits定义的Metric):

    -- Recording usage
    usage[name] = {
      limit = limit,
      remaining = remaining
    }
    -- 标记那个Metric停止了
    if remaining <= 0 then
      stop = name
    end
复制代码

然后减一

  if usage then
    -- Adding headers
    if not conf.hide_client_headers then
      for k, v in pairs(usage) do
        ngx.header[RATELIMIT_LIMIT .. "-" .. k] = v.limit
        -- -increment_value for this current request
        ngx.header[RATELIMIT_REMAINING .. "-" .. k] = math.max(0, (stop == nil or stop == k) and v.remaining - 1 or v.remaining) 
      end
    end
复制代码

二、谈限流

参考:

  1. 谈谈服务限流算法的几种实现
  2. Java并发工具类(信号量Semaphore)
  3. Redis 深度历险:核心原理与应用实践
文章分类
后端
文章标签