限流的場景:服務提供的能力是有限的。爲了防止大量的請求將服務拖垮,能夠經過網關對服務的請求作限流。例如:某個服務1s只能處理100個請求,超過限流閾值的請求丟棄。nginx
Kong網關提供的限流插件:rate-limiting
redis
從插件參數中能夠得到這些信息:算法
consumer
、credential
、ip
,缺省是consumerlocal
、cluster
、redis
,缺省是cluster。存儲的內容是:以 fmt("ratelimit:%s:%s:%s:%s", api_id, identifier, period_date, name)
爲key,持續訪問的次數爲value。rate-limiting
表中 - 支持集羣訪問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 },
},
複製代碼
當限制類型(限制粒度)爲consumer
和identifier
的時候,限流插件須要一些會在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
複製代碼
一般的限流的作法是一段時間內的請求次數。但若是隻經過一段時間來限時會遇到流量分佈不均的問題。例如:某個服務限制一小時的請求次數是3600次,那麼若是在某一刻一會兒來了一堆請求超過的閾值。爲了解決這個問題,kong將一段時間拆細,將閾值設成每分鐘60每秒鐘1次。api
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
複製代碼
而後減一ide
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
複製代碼
參考:工具