Kong的限流是怎么做的?

Kong的限流从技术上来讲支持三种,分别是本地限流(local)、数据库限流(cluster)和Redis限流,这三种限流方式采用的限流算法都是计数器法。支持按照秒/分/小时/日/月/年等不同时间单位限流,并且可以组合,例如可以设置每秒最多100次并且每分钟最多1000次。

首先介绍一下本地限流(local),本地限流计数器采用的是nginx的缓存,缓存的Key是:

--[[
    second = math.floor(second:timestamp() * 1000),
    minute = minute:timestamp() * 1000,
    hour = hour:timestamp() * 1000,
    day = day:timestamp() * 1000,
    month = month:timestamp() * 1000,
    year = year:timestamp() * 1000
--]]
-- name:上面数据结构的Key
-- period_date:上面数据结构的Value
local get_local_key = function(api_id, identifier, period_date, name)
  return fmt("ratelimit:%s:%s:%s:%s", api_id, identifier, period_date, name)
end  

如果用户配置了每秒最多100次并且每分钟最多1000次,则Kong会相应地创建2个缓存分别对时间单位秒和分进行计数,单位秒的缓存失效时间是1秒,单位分的失效时间是1分钟。

计数器算法连个重要的方法是查询计数和增加计数,Kong里面分别是usage,increment。关键代码如下:

increment = function(conf, api_id, identifier, current_timestamp, value)
  local periods = timestamp.get_timestamps(current_timestamp)
  for period, period_date in pairs(periods) do
    local cache_key = get_local_key(api_id, identifier, period_date, period)
    if not cache.rawget(cache_key) then
      cache.rawset(cache_key, 0, EXPIRATIONS[period])
    end                                           
    local _, err = cache.incr(cache_key, value)   
  end                                         
  return true                
end,                         
usage = function(conf, api_id, identifier, current_timestamp, name)
  local periods = timestamp.get_timestamps(current_timestamp)      
  local cache_key = get_local_key(api_id, identifier, periods[name], name)
  local current_metric, err = cache.rawget(cache_key)                        
  return current_metric and current_metric or 0
end 

Redis限流和本地限流类似,只是将缓存放到了Redis上,除去连接Redis等代码外,关键代码如下:

increment = function(conf, api_id, identifier, current_timestamp, value)            
  local periods = timestamp.get_timestamps(current_timestamp)
  for period, period_date in pairs(periods) do
    local cache_key = get_local_key(api_id, identifier, period_date, period)
    local exists, err = red:exists(cache_key)            
                     
    red:init_pipeline((not exists or exists == 0) and 2 or 1)
    red:incrby(cache_key, value)
    if not exists or exists == 0 then
      red:expire(cache_key, EXPIRATIONS[period])
    end                     
                     
    local _, err = red:commit_pipeline()              
  end                
  return true        
end,                 
usage = function(conf, api_id, identifier, current_timestamp, name)
  local periods = timestamp.get_timestamps(current_timestamp)
  local cache_key = get_local_key(api_id, identifier, periods[name], name)
  local current_metric, err = red:get(cache_key)
  return current_metric and current_metric or 0
end

数据库限流(cluster)是将计数器保存在了数据库里,Kong支持两种数据库,PostgreSQL和Cassandra,对于PostgreSQL,为了提升性能,增加计数采用了存储过程:

CREATE OR REPLACE FUNCTION increment_rate_limits(
    a_id uuid, i text, p text, p_date timestamp with time zone, v integer) 
    RETURNS VOID AS $$
BEGIN
LOOP
  UPDATE ratelimiting_metrics 
  SET value = value + v 
  WHERE api_id = a_id AND identifier = i AND period = p AND period_date = p_date;
  IF found then
    RETURN;
  END IF;

  BEGIN
    INSERT INTO ratelimiting_metrics(api_id, period, period_date, identifier, value) 
    VALUES(a_id, p, p_date, i, v);
    RETURN;
  EXCEPTION WHEN unique_violation THEN

  END;
END LOOP;
END;

而Cassandra则是直接采用UPDATE语句。

Kong的这三种限流方式都没有考虑并发情况

  -- Load current metric for configured period
  local usage, stop, err = get_usage(conf, api_id, identifier, current_timestamp, {
    second = conf.second,
    minute = conf.minute,
    hour = conf.hour,
    day = conf.day,
    month = conf.month,
    year = conf.year
  }) 
  if usage then        
    -- If limit is exceeded, terminate the request
    if stop then
      return responses.send(429, "API rate limit exceeded")
    end  
  end
  -- Increment metrics for all periods if the request goes through
  local ok, err = ngx_timer_at(0, incr, conf, api_id, identifier, current_timestamp, 1)
  if not ok then
    ngx_log(ngx.ERR, "failed to create timer: ", err)
  end

当到达限流的临界值(max-1)时,此时有多条请求同时执行get_usage,计算结果全部通过,而我们期望的是仅有一条请求能通过。

转载于:https://my.oschina.net/chinamerp/blog/851613

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值