架构:服务限流。

什么叫限流?

即限制流量进入

  • 缓存,是用来增加系统吞吐量,提升访问速度提供高并发。
  • 降级,是在系统某些服务组件不可用的时候、流量暴增、资源耗尽等情况下,暂时屏蔽掉出问题的服务,继续提供降级服务,给用户尽可能的友好提示,返回兜底数据,不会影响整体业务流程,待问题解决再重新上线服务
  • 限流,是指在使用缓存和降级无效的场景。比如当达到阈值后限制接口调用频率,访问次数,库存个数等,在出现服务不可用之前,提前把服务降级。只服务好一部分用户。

在我们使用微信、支付宝、短信等等这些api的时候,每个接口都会有调用上的限流。

限流是对某一时间窗口内的请求数进行限制,保持系统的可用性、稳定性和安全性,防止因流量暴增而导致的系统运行缓慢或宕机。

限流算法

计数器算法

  • 简单粗暴
  • 比如线程池大小,数据库连接池大小、nginx连接数等都属于计数器算法。
  • 全局或某段时间范围达到阈值则限流。

漏桶算法

  • 削峰
  • 缓冲
  • 消费速度固定 因为计算性能固定
  • 保证桶不能忙

令牌桶算法

  • 平滑的流入速率限制,消费/秒。
  • 可以用于对外服务接口,内部集群调用

区别

  • 令牌桶是按照固定速率从桶里拿令牌消费,如果令牌为0,则拒绝新请求

  • 漏桶是按照固定速率流出请求,流入速率不控制,当桶内请求达到阈值,新请求则被拒绝。

  • 令牌桶支持每次拿多个令牌,平均流入速率,并支持突发流入,还可以支持缓慢提升流入速度

并发限流

设置系统阈值总的qps个数

Tomcat中配置的

  • acceptCount 响应连接数

  • maxConnections 瞬时最大连接数

  • maxThreads 最大线程数

接口限流

接口总数

可以使用atomic类或者semaphore进行限流

这种方式简单粗暴。没有平滑处理。使用限制某个接口的总并发数,或限制某账号服务调用总次数。

比如某些开放平台限制试用账号。

if (atomic.incrementAndGet() > 100){
    // 拒绝
}finally{
	atomic.decrementAndGet();

}

接口时间窗口

此时可以使用Guava Cache,类似于一个ConcurrentMap,但并不完全一样。

最基础的不同是ConcurrentMap保存所有的元素知道它们被明确删除,Guava Cache可以配置自动过期

//计数器
counter;
// 限制数量
limit;
// 限制单位 1000=秒
unit;
// 获得当前时间
current = system.currentTimeMillis() / unit
//判断时间窗内是否限制访问

if (counter.get(current).incrementAndGet() > limit){
    // 拒绝
}

使用guava实现令牌桶

稳定模式(SmoothBursty:令牌生成速度恒定)

RateLimiter.create(2) //容量和突发量,令牌桶算法允许将一段时间内没有消费的令牌暂存到令牌桶中,用来突发消费。

渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值)

// 平滑限流,从冷启动速率(满的)到平均消费速率的时间间隔
RateLimiter limiter = RateLimiter.create(2,1000l,TimeUnit.MILLISECONDS);

分布式系统限流

Nginx + Lua

可以使用resty.lock保持原子特性,请求之间不会产生锁的重入

https://github.com/openresty/lua-resty-lock

使用lua_shared_dict存储数据

local locks = require "resty.lock"

local function acquire()
    local lock =locks:new("locks")
    local elapsed, err =lock:lock("limit_key") --互斥锁 保证原子特性
    local limit_counter =ngx.shared.limit_counter --计数器

    local key = "ip:" ..os.time()
    local limit = 5 --限流大小
    local current =limit_counter:get(key)

    if current ~= nil and current + 1> limit then --如果超出限流大小
       lock:unlock()
       return 0
    end
    if current == nil then
       limit_counter:set(key, 1, 1) --第一次需要设置过期时间,设置key的值为1,
过期时间为1秒
    else
        limit_counter:incr(key, 1) --第二次开始加1即可
    end
    lock:unlock()
    return 1
end
ngx.print(acquire())

猜你喜欢

转载自blog.csdn.net/en_joker/article/details/108096541