如何设计实现一个轻量的开放API网关之限流java
文章地址: blog.piaoruiqing.com/2019/08/26/…redis
开发高并发系统时有多重系统保护手段, 如缓存、限流、降级等. 在网关层, 限流的应用比较普遍. 不少状况下咱们能够认为网关上的限流与业务没有很强的关联(与系统的承载能力有关), 且各个子系统都有限流这种需求, 将部分限流功能放到网关会比较合适.算法
众所周知, 服务器、网站应用的处理能力是有上限的, 不论配置有多高总会有一个极限, 超过极限若是听任继续接收请求, 可能会发生不可控的后果.api
举个栗子🌰, 节假日网上购票, 经常会遇到排队中
、系统繁忙请稍后再试
等提示, 这即是服务端对单位时间处理请求的数量进行了限制, 超出限制就会排队、降级甚至拒绝服务, 不然若是把系统搞崩了, 你们都买不到票了╮( ̄▽ ̄)╭.缓存
咱们先给出限流
的定义: 限流
是高并发系统保护保护手段之一, 在网关层的应用很普遍. 其目的是对并发请求进行限速或限制一个时间窗口内请求的数量, 一旦达到阈值就排队等待或降级甚至拒绝服务.bash
其最终目的是: 在扛不住太高并发的状况下作到有损服务
而不是不服务.服务器
令牌桶算法, 是一个存放固定数量令牌的桶按照固定速率添加令牌. 如图:markdown
举个现实生活中比较常见的例子来理解, 电影院售票, 每场电影所售出的票数是必定的, 若是来晚了(后面的请求)就没票了, 要么等待下一场(等待新的令牌发放), 要么不看了(被拒绝).并发
漏桶是一个底部破洞的桶, 水能够匀速流出(这时候不考虑压强, 不要杠( ̄. ̄)), 因此与令牌桶不同的是, 漏桶算法是匀速消费, 能够用来进行流量整形
和流量控制
. 如图:分布式
一个单体的应用程序有其承受极限, 在高并发状况下, 有必要进行过载保护, 以防过多的请求将系统弄崩. 最简单粗暴的方式就是使用计数器进行控制, 处理请求时+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
复制代码
在令牌用尽后, 后面的请求都要等待有新的令牌后才能继续执行.
应用级限流实现简单, 但其局限性在于没法进行全局限流, 对于集群就无能为力了.
想要在集群中进行全局限流, 其关键在于将限流信息记录在共享介质中, 如Redis
、memcached
等. 为了将限流作的精确, 写必须是原子操做.
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 复制代码
网关做为内部系统外的一层屏障, 对内起到必定的保护做用, 限流即是其中之一. 网关层的限流能够简单地针对不一样业务的接口进行限流, 也可考虑将限流功能作成网关的一个功能模块(如限流规则的配置、统计、针对用户维度进行统计和限流等)
若是这篇文章对您有帮助,请点个赞吧 ( ̄▽ ̄)"
欢迎关注公众号: