开放API网关实践(三) —— 限流

如何设计实现一个轻量的开放API网关之限流java

文章地址: blog.piaoruiqing.com/2019/08/26/…redis

前言

开发高并发系统时有多重系统保护手段, 如缓存、限流、降级等. 在网关层, 限流的应用比较普遍. 不少状况下咱们能够认为网关上的限流与业务没有很强的关联(与系统的承载能力有关), 且各个子系统都有限流这种需求, 将部分限流功能放到网关会比较合适.算法

什么是限流

众所周知, 服务器、网站应用的处理能力是有上限的, 不论配置有多高总会有一个极限, 超过极限若是听任继续接收请求, 可能会发生不可控的后果.api

举个栗子🌰, 节假日网上购票, 经常会遇到排队中系统繁忙请稍后再试等提示, 这即是服务端对单位时间处理请求的数量进行了限制, 超出限制就会排队、降级甚至拒绝服务, 不然若是把系统搞崩了, 你们都买不到票了╮( ̄▽ ̄)╭.缓存

12306系统繁忙

咱们先给出限流的定义: 限流是高并发系统保护保护手段之一, 在网关层的应用很普遍. 其目的是对并发请求进行限速或限制一个时间窗口内请求的数量, 一旦达到阈值就排队等待或降级甚至拒绝服务.bash

其最终目的是: 在扛不住太高并发的状况下作到有损服务而不是不服务.服务器

经常使用限流玩法

令牌桶

令牌桶算法, 是一个存放固定数量令牌的桶按照固定速率添加令牌. 如图:markdown

令牌桶算法

  • 按照固定速率向桶中添加令牌.
  • 桶满时拒绝增长新令牌.
  • 每次请求消耗一个令牌(也可根据数据包大小来消耗对应的令牌数).
  • 当令牌不足时, 拒绝请求(或等待).
  • 特色: 能够应对必定程度的突发.

举个现实生活中比较常见的例子来理解, 电影院售票, 每场电影所售出的票数是必定的, 若是来晚了(后面的请求)就没票了, 要么等待下一场(等待新的令牌发放), 要么不看了(被拒绝).并发

漏桶

漏桶是一个底部破洞的桶, 水能够匀速流出(这时候不考虑压强, 不要杠( ̄. ̄)), 因此与令牌桶不同的是, 漏桶算法是匀速消费, 能够用来进行流量整形流量控制. 如图:分布式

漏桶算法

  • 固定容量的漏桶, 按照固定速率流出水(不要杠水深和压强的问题).
  • 流入水的速率固定, 溢出则被丢弃.
  • 特色: 平滑处理速率.
[版权声明]
本文发布于 朴瑞卿的博客, 容许非商业用途转载, 但转载必须保留原做者 朴瑞卿 及连接: blog.piaoruiqing.com. 若有受权方面的协商或合做, 请联系邮箱: piaoruiqing@gmail.com.

应用级限流

一个单体的应用程序有其承受极限, 在高并发状况下, 有必要进行过载保护, 以防过多的请求将系统弄崩. 最简单粗暴的方式就是使用计数器进行控制, 处理请求时+1, 处理完毕后-1, 除此以外咱们还能够利用前文提到的令牌桶和漏桶来进行更精细的限流.若是网关是单体应用, 咱们彻底能够不借助其余介质, 直接在应用级别进行限流.

计数器

这种方式实现最简单粗暴,

try {
    if (counter.incrementAndGet() > limit) {
        throw new SomeException();
    }
    // do something
} finally {
    counter.decrementAndGet();
}
复制代码

令牌桶

Guava提供了令牌桶算法的实现.

@Test
public void testGuavaRateLimiter() throws InterruptedException {
    RateLimiter limiter = RateLimiter.create(5);
    TimeUnit.SECONDS.sleep(1);	// 等待一秒钟发几个令牌
    for (int index = 0; index < 10; index++) {
        System.out.println(limiter.acquire()); // 打印等待时间
    }
}
复制代码

输出为:

0.0
0.0
0.0
0.0
0.0
0.0
0.196108
0.194372
0.19631
0.198373
复制代码

在令牌用尽后, 后面的请求都要等待有新的令牌后才能继续执行.

应用级限流实现简单, 但其局限性在于没法进行全局限流, 对于集群就无能为力了.

分布式限流

想要在集群中进行全局限流, 其关键在于将限流信息记录在共享介质中, 如Redismemcached等. 为了将限流作的精确, 写必须是原子操做.

分布式限流

Redis+Lua是一个不错的选择, 示例Lua脚本以下:

local key = KEYS[1] -- 限流的KEY
local limit = tonumber(ARGV[1])	-- 限流大小
local current = tonumber(redis.call('get', key) or '0')
if current + 1 > limit then
    return 0
else
    redis.call('INCRBY', key,'1')
    redis.call('expire', key,ARGV[2])	-- 过时时间
    return current + 1
end
复制代码
  • 分布式限流将令牌的发放放到共享介质中.
  • 获取(消费)令牌操做必须是原子的.
  • 共享介质要高可用(Redis集群)

结语

网关做为内部系统外的一层屏障, 对内起到必定的保护做用, 限流即是其中之一. 网关层的限流能够简单地针对不一样业务的接口进行限流, 也可考虑将限流功能作成网关的一个功能模块(如限流规则的配置、统计、针对用户维度进行统计和限流等)

若是这篇文章对您有帮助,请点个赞吧 ( ̄▽ ̄)"

系列文章:

欢迎关注公众号:

[版权声明]
本文发布于 朴瑞卿的博客, 容许非商业用途转载, 但转载必须保留原做者 朴瑞卿 及连接: blog.piaoruiqing.com. 若有受权方面的协商或合做, 请联系邮箱: piaoruiqing@gmail.com.
相关文章
相关标签/搜索