首页 网站首页 商业信息 限流 查看内容

实战 Spring Cloud Gateway 之限流篇

微信营销 2023-1-1 16:34 8042人围观 限流

话说在 Spring Cloud Gateway 问世之前,Spring Cloud 的微办事天下里,网关一定非 Netflix Zuul 莫属。可是由于 Zuul 1.x 存在的一些题目,比如阻塞式的 API,不支持 WebSocket 等,一向被人所诟病,而且 Zuul 升级新版本依靠于 Netflix 公司,经过几次跳票以后,Spring 开源社区决议推出自己的网关组件,替换 Netflix Zuul。

从 18 年 6 月 Spring Cloud 公布的 Finchley 版本起头,Spring Cloud Gateway 逐步崭露头角,它基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技术开辟,不但支持响应式和无阻塞式的 API,而且支持 WebSocket,和 Spring 框架慎麋集成。虽然 Zuul 后来也推出了 2.x 版本,在底层利用了异步无阻塞式的 API,大大改良了其性能,可是今朝看来 Spring 并没有筹算继续集成它的计划。按照官网的描写,Spring Cloud Gateway 的首要特征以下:

  • Built on Spring Framework 5, Project Reactor and Spring Boot 2.0
  • Able to match routes on any request attribute
  • Predicates and filters are specific to routes
  • Hystrix Circuit Breaker integration
  • Spring Cloud DiscoveryClient integration
  • Easy to write Predicates and Filters
  • Request Rate Limiting
  • Path Rewriting

可以看出 Spring Cloud Gateway 可以很方便的和 Spring Cloud 生态中的其他组件停止集成(比如:断路器和办事发现),而且供给了一套简单易写的 断言Predicates,有的地方也翻译成 谓词)和 过滤器Filters)机制,可以对每个 路由Routes)停止特别请求处置。比来在项目中利用了 Spring Cloud Gateway,并在它的根本上实现了一些高级特征,如限流和留痕,在网关的利用进程中碰到了很多的应战,因而趁着项目竣事,抽点时候系统地进修并总结下。这篇文章首要进修限流技术,首先我会先容一些常见的限流场景和限流算法,然后先容一些关于限流的开源项目,进修他人是若何实现限流的,最初先容我是若何在网关中实现限流的,并分享一些实现进程中的经历和碰到的坑。

一、常见的限流场景

缓存升级限流 被称为高并发、散布式系统的三驾马车,网关作为全部散布式系统中的第一道关卡,限流功用自然必不成少。经过限流,可以控制办事请求的速度,从而进步系统应对突发大流量的才能,让系统更具弹性。限流有着很多现实的利用处景,比如双十一的秒杀活动, 12306 的抢票等。

1.1 限流的工具

经过上面的先容,我们对限流的概念能够感受还是比力模糊,到底限流限的是什么?望文生义,限流就是限制流量,但这里的流量是一个比力笼统的概念。假如斟酌各类分歧的场景,限流是很是复杂的,而且和具体的营业法则亲近相关,可以斟酌以下几种常见的场景:

  • 限制某个接口一分钟内最多请求 100 次
  • 限制某个用户的下载速度最多 100KB/S
  • 限制某个用户同时只能对某个接口倡议 5 路请求
  • 限制某个 IP 来历制止拜候任何请求

从上面的例子可以看出,按照分歧的请求者和请求资本,可以组合出分歧的限流法则。可以按照请求者的 IP 来停止限流,大概按照请求对应的用户来限流,又大概按照某个特定的请求参数来限流。而限流的工具可所以请求的频次,传输的速度,大概并发量等,其中最多见的两个限流工具是请求频次和并发量,他们对应的限流被称为 请求频次限流(Request rate limiting)和 并发量限流(Concurrent requests limiting)。传输速度限流 鄙人载场景下比力常用,比如一些资本下载站会限制普通用户的下载速度,只要采办会员才能提速,这类限流的做法现实上和请求频次限流类似,只不外一个限制的是请求量的几多,一个限制的是请求数据报文的巨细。这篇文章首要先容请求频次限流和并发量限流。

1.2 限流的处置方式

在系统中设想限流计划时,有一个题目值得设想者去仔细斟酌,当请求者被限流法则阻挡以后,我们该若何返回成果。一般我们有下面三种限流的处置方式:

  • 拒绝办事
  • 排队期待
  • 办事升级

最简单的做法是拒绝办事,间接抛出异常,返回毛病信息(比如返回 HTTP 状态码 429 Too Many Requests),大概给前端返回 302 重定向到一个毛病页面,提醒用户资本没有了或稍后再试。可是对于一些比力重要的接口不能间接拒绝,比如秒杀、下单等接口,我们既不希望用户请求太快,也不希望请求失利,这类情况一般会将请求放到一个消息行列中排队期待,消息行列可以起到削峰和限流的感化。第三种处置方式是办事升级,当触发限流条件时,间接返回兜底数据,比如查询商品库存的接口,可以默许返回有货。

1.3 限流的架构

针对分歧的系统架构,需要利用分歧的限流计划。以下图所示,办事摆设的方式一般可以分为单机形式和集群形式:



单机形式的限流很是简单,可以间接基于内存便可以实现,而集群形式的限流必须依靠于某个“中心化”的组件,比如网关或 Redis,从而引出两种分歧的限流架构:网关层限流中心件限流



网关作为全部散布式系统的进口,承当了一切的用户请求,所以在网关中停止限流是最合适不外的。网关层限流偶然也被称为 接入层限流。除了我们利用的 Spring Cloud Gateway,最常用的网关层组件还有 Nginx,可以经过它的ngx_http_limit_req_module模块,利用 limit_conn_zone、limit_req_zone、limit_rate 等指令很轻易的实现并发量限流、请求频次限流和传输速度限流。这里差池 Nginx 作过量的说明,关于这几个指令的具体信息可以 参考 Nginx 的官方文档。另一种限流架构是中心件限流,可以将限流的逻辑下沉到办事层。可是集群中的每个办事必须将自己的流量信息同一汇总到某个地方供其他办事读取,一般来说用 Redis 的比力多,Redis 供给的过期特征和 lua 剧本履行很是合适做限流。除了 Redis 这类中心件,还有很多类似的散布式缓存系统都可以利用,如 Hazelcast、Apache Ignite、Infinispan 等。我们可以更进一步扩大上面的架构,将网关改成集群形式,虽然这还是网关层限流架构,可是由于网关酿成了集群形式,所以网关必须依靠于中心件停止限流,这和上面会商的中心件限流没有区分。



二、常见的限流算法

经过上面的进修,我们晓得限流可以分为请求频次限流和并发量限流,按照系统架构的分歧,又可以分为网关层限流和散布式限流。在分歧的利用处景下,我们需要采用分歧的限流算法。这一节将先容一些支流的限流算法。有一点要留意的是,操纵池化技术也可以到达限流的目标,比如线程池或毗连池,但这不是本文的重点。

2.1 牢固窗口算法(Fixed Window)

牢固窗口算法是一种最简单的限流算法,它按照限流的条件,将请求时候映照到一个时候窗口,再利用计数器累加拜候次数。比方限流条件为每分钟 5 次,那末就依照分钟为单元映照时候窗口,假定一个请求时候为 11:00:45,时候窗口就是 11:00:00 ~ 11:00:59,在这个时候窗口内设定一个计数器,每来一个请求计数器加一,当这个时候窗口的计数器跨越 5 时,就触发限流条件。当请求时候落鄙人一个时候窗口内时(11:01:00 ~ 11:01:59),上一个窗口的计数器生效,当前的计数器清零,重新起头计数。计数器算法很是轻易实现,在单机场景下可以利用AtomicLong、LongAdder或Semaphore来实现计数,而在散布式场景下可以经过 Redis 的INCR和EXPIRE等号令并连系EVAL或 lua 脚原本实现,Redis 官网供给了几种简单的实现方式。不管是请求频次限流还是并发量限流都可以利用这个算法。不外这个算法的缺点也比力明显,那就是存在严重的临界题目。由于每过一个时候窗口,计数器就会清零,这使得限流结果不够平滑,恶意用户可以操纵这个特点绕过我们的限流法则。以下图所示,我们的限流条件原本是每分钟 5 次,可是恶意用户在 11:00:00 ~ 11:00:59 这个时候窗口的后半分钟倡议 5 次请求,接下来又在 11:01:00 ~ 11:01:59 这个时候窗口的前半分钟倡议 5 次请求,这样我们的系统就在 1 分钟内承受了 10 次请求。



2.2 滑动窗口算法(Rolling Window 或 Sliding Window)

为领会决牢固窗口算法的临界题目,可以将时候窗口分别红更小的时候窗口,然后随着时候的滑动删除响应的小窗口,而不是间接滑过一个大窗口,这就是滑动窗口算法。我们为每个小时候窗口都设备一个计数器,大时候窗口的总请求次数就是每个小时候窗口的计数器的和。以下图所示,我们的时候窗口是 5 秒,可以按秒停止分别,将其分别红 5 个小窗口,时候每过一秒,时候窗口就滑过一秒:



每次处置请求时,都需要计较一切小时候窗口的计数器的和,斟酌到性能题目,分别的小时候窗口不宜过量,比方限流条件是每小时 N 个,可以按分钟分别为 60 个窗口,而不是按秒分别红 3600 个。固然假如不斟酌性能题目,分别粒度越细,限流结果就越平滑。相反,假如分别粒度越粗,限流结果就越不切确,出现临界题目标能够性也就越大,当分别粒度为 1 时,滑动窗口算法就退化成了牢固窗口算法。由于这两种算法都利用了计数器,所以也被称为 计数器算法(Counters)。进一步思考我们发现,假如分别粒度最粗,也就是只要一个时候窗口时,滑动窗口算法退化成了牢固窗口算法;那假如我们把分别粒度调到最细,又会若何呢?那末怎样才能让分别的时候窗口最细呢?时候窗口细到一定境界时,意味着每个时候窗口中只能包容一个请求,这样我们可以省略计数器,只记录每个请求的时候,然后统计一段时候内的请求数有几多个即可。具体的实现可以参考Redis sorted set 技能 和Sliding window log 算法。

2.3 漏桶算法(Leaky Bucket)

除了计数器算法,另一个很自然的限流思绪是将一切的请求缓存到一个行列中,然后按某个牢固的速度渐渐处置,这实在就是漏桶算法(Leaky Bucket)。漏桶算法假定将请求装到一个桶中,桶的容量为 M,当桶满时,请求被抛弃。在桶的底部有一个洞,桶中的请求像水一样按牢固的速度(每秒 r 个)漏出来。我们用下面这个形象的图来暗示漏桶算法:



桶的上面是个水龙头,我们的请求从水龙头流到桶中,水龙头流出的水速不定,偶然快偶然慢,这类忽快忽慢的流量叫做Bursty flow。假如桶中的水满了,过剩的水就会溢进来,相当于请求被抛弃。从桶底部漏出的水速是牢固稳定的,可以看出漏桶算法可以平滑请求的速度。漏桶算法可以经过一个行列来实现,以下图所示:



当请求到达时,不间接处置请求,而是将其放入一个行列,然后另一个线程以牢固的速度从行列中读取请求并处置,从而到达限流的目标。留意的是这个行列可以有分歧的实现方式,比如设备请求的存活时候,或将行列革新成 PriorityQueue,按照请求的优先级排序而不是先辈先出。固然行列也有满的时辰,假如行列已经满了,那末请求只能被抛弃了。漏桶算法有一个缺点,在处置突发流量时效力很低,因而人们又想出了下面的令牌桶算法。

2.4 令牌桶算法(Token Bucket)

令牌桶算法(Token Bucket)是今朝利用最普遍的一种限流算法,它的根基思惟由两部分组成:天生令牌消耗令牌

  • 天生令牌:假定有一个装令牌的桶,最多能装 M 个,然后按某个牢固的速度(每秒 r 个)往桶中放入令牌,桶满时不再放入;
  • 消耗令牌:我们的每次请求都需要从桶中拿一个令牌才能放行,当桶中没有令牌时即触发限流,这时可以将请求放入一个缓冲行列中排队期待,大概间接拒绝;

令牌桶算法的图示以下:



在上面的图中,我们将请求放在一个缓冲行列中,可以看出这一部分的逻辑和漏桶算法几近如出一辙,只不外在处置请求上,一个是以牢固速度处置,一个是从桶中获得令牌后才处置。仔细思考就会发现,令牌桶算法有一个很关键的题目,就是桶巨细的设备,正是这个参数可以让令牌桶算法具有处置突发流量的才能。比方将桶巨细设备为 100,天生令牌的速度设备为每秒 10 个,那末在系统余暇一段时候的以后(桶中令牌一向没有消耗,渐渐的会被装满),忽然来了 50 个请求,这时系统可以间接按每秒 50 个的速度处置,随着桶中的令牌很快用完,处置速度又会渐渐降下来,和天生令牌速度趋于分歧。这是令牌桶算法和漏桶算法最大的区分,漏桶算法不管来了几多请求,只会一向以每秒 10 个的速度停止处置。固然,处置突发流量虽然进步了系统性能,但也给系统带来了一定的压力,假如桶巨细设备不公道,突发的大流量能够会间接压垮系统。经过上面临令牌桶的道理分析,一般会有两种分歧的实现方式。第一种方式是启动一个内部线程,不竭的往桶中增加令牌,处置请求时从桶中获得令牌,和上面图中的处置逻辑一样。第二种方式不依靠于内部线程,而是在每次处置请求之前先实时计较出要添补的令牌数并添补,然后再从桶中获得令牌。下面是第二种方式的一种典范实现,其中capacity暗示令牌桶巨细,refillTokensPerOneMillis暗示添补速度,每毫秒添补几多个,availableTokens暗示令牌桶中还剩几多个令牌,lastRefillTimestamp暗示上一次添补时候。

1public class TokenBucket {
2
3 private final long capacity;
4 private final double refillTokensPerOneMillis;
5 private double availableTokens;
6 private long lastRefillTimestamp;
7
8 public TokenBucket(long capacity, long refillTokens, long refillPeriodMillis) {
9 this.capacity = capacity;
10 this.refillTokensPerOneMillis = (double) refillTokens / (double) refillPeriodMillis;
11 this.availableTokens = capacity;
12 this.lastRefillTimestamp = System.currentTimeMillis();
13 }
14
15 synchronized public boolean tryConsume(int numberTokens) {
16 refill();
17 if (availableTokens < numberTokens) {
18 return false;
19 } else {
20 availableTokens -= numberTokens;
21 return true;
22 }
23 }
24
25 private void refill() {
26 long currentTimeMillis = System.currentTimeMillis();
27 if (currentTimeMillis > lastRefillTimestamp) {
28 long millisSinceLastRefill = currentTimeMillis - lastRefillTimestamp;
29 double refill = millisSinceLastRefill * refillTokensPerOneMillis;
30 this.availableTokens = Math.min(capacity, availableTokens + refill);
31 this.lastRefillTimestamp = currentTimeMillis;
32 }
33 }
34}

可以像下面这样建立一个令牌桶(桶巨细为 100,且每秒天生 100 个令牌):

1TokenBucket limiter = new TokenBucket(100, 100, 1000);

从上面的代码片断可以看出,令牌桶算法的实现很是简单也很是高效,仅仅经过几个变量的运算就实现了完整的限流功用。焦点逻辑在于refill() 这个方式,在每次消耗令牌时,计较当前时候和上一次添补的时候差,并按照添补速度计较出应当添补几多令牌。在重新添补令牌后,再判定请求的令牌数能否充足,假如不够,返回 false,假如充足,则减去令牌数,并返回 true。在现实的利用中,常常不会间接利用这类原始的令牌桶算法,一般会在它的根本上作一些改良,比如,添补速度支持静态调剂,令牌总数支持透支,基于 Redis 支持散布式限流等,不外整体来说还是合适令牌桶算法的整体框架,我们在前面进修一些开源项目时对此会有更深的体味。

三、一些开源项目

有很多开源项目中都实现了限流的功用,这一节经过一些开源项目标进修,领会限流是若何实现的。

3.1 Guava 的 RateLimiter

Google Guava 是一个强大的焦点库,包括了很多有用的工具类,例如:调集、缓存、并发库、字符串处置、I/O 等等。其中在并发库中,Guava 供给了两个和限流相关的类:RateLimiter 和 SmoothRateLimiter。Guava 的 RateLimiter 基于令牌桶算法实现,不外在传统的令牌桶算法根本上做了点改良,支持两种分歧的限流方式:平滑突发限流(SmoothBursty) 和 平滑预热限流(SmoothWarmingUp)。下面的方式可以建立一个平滑突发限流器(SmoothBursty):

1RateLimiter limiter = RateLimiter.create(5);

RateLimiter.create(5) 暗示这个限流器容量为 5,而且每秒天生 5 个令牌,也就是每隔 200 毫秒天生一个。我们可以利用limiter.acquire() 消耗令牌,假如桶中令牌充足,返回 0,假如令牌不敷,则阻塞期待,并返回期待的时候。我们持续请求几次:

1System.out.println(limiter.acquire());
2System.out.println(limiter.acquire());
3System.out.println(limiter.acquire());
4System.out.println(limiter.acquire());

输出成果以下:

10.0
20.198239
30.196083
40.200609

可以看出限流器建立以后,初始会有一个令牌,然后每隔 200 毫秒天生一个令牌,所以第一次请求间接返回 0,前面的请求城市阻塞大约 200 毫秒。别的,SmoothBursty还具有应对突发的才能,而且 还答应消耗未来的令牌,比以下面的例子:

1RateLimiter limiter = RateLimiter.create(5);
2System.out.println(limiter.acquire(10));
3System.out.println(limiter.acquire(1));
4System.out.println(limiter.acquire(1));

会获得类似下面的输出:

10.0
21.997428
30.192273
40.200616

限流器建立以后,初始令牌只要一个,可是我们请求 10 个令牌居然也经过了,只不外看前面请求发现,第二次请求花了 2 秒左右的时候把前面的透支的令牌给补上了。

Guava 支持的另一种限流方式是平滑预热限流器(SmoothWarmingUp),可以经过下面的方式建立:

1RateLimiter limiter = RateLimiter.create(2, 3, TimeUnit.SECONDS);
2System.out.println(limiter.acquire(1));
3System.out.println(limiter.acquire(1));
4System.out.println(limiter.acquire(1));
5System.out.println(limiter.acquire(1));
6System.out.println(limiter.acquire(1));

第一个参数还是每秒建立的令牌数目,这里是每秒 2 个,也就是每 500 毫秒天生一个,前面的参数暗示从冷启动速度过渡到均匀速度的时候间隔,也就是所谓的热身时候间隔(warm up period)。我们看下输出成果:

10.0
21.329289
30.994375
40.662888
50.501287

第一个请求还是立即获得令牌,可是前面的请求和上面平滑突发限流就完全纷歧样了,按理来说 500 毫秒就会天生一个令牌,可是我们发现第二个请求却等了 1.3s,而不是 0.5s,前面第三个和第四个请求也等了一段时候。不外可以看出,期待时候在渐渐的接近 0.5s,直到第五个请求期待时候才起头变得一般。从第一个请求到第五个请求,这中心的时候间隔就是热身阶段,可以算出热身的时候就是我们设备的 3 秒。

3.2 Bucket4j

Bucket4j是一个基于令牌桶算法实现的强大的限流库,它不但支持单机限流,还支持经过诸如 Hazelcast、Ignite、Coherence、Infinispan 或其他兼容 JCache API (JSR 107) 标准的散布式缓存实现散布式限流。在利用 Bucket4j 之前,我们有需要先领会 Bucket4j 中的几个焦点概念:

  • Bucket
  • Bandwidth
  • Refill

Bucket接口代表了令牌桶的具体实现,也是我们操纵的进口。它供给了诸如tryConsume和tryConsumeAndReturnRemaining这样的方式供我们消耗令牌。可以经过下面的机关方式来建立Bucket:

1Bucket bucket = Bucket4j.builder().addLimit(limit).build();
2if(bucket.tryConsume(1)) {
3 System.out.println("ok");
4} else {
5 System.out.println("error");
6}

Bandwidth的意义是带宽, 可以了解为限流的法则。Bucket4j 供给了两种方式来建立 Bandwidth:simple和classic。下面是 simple 方式建立的 Bandwidth,暗示桶巨细为 10,添补速度为每分钟 10 个令牌:

1Bandwidth limit = Bandwidth.simple(10, Duration.ofMinutes(1));

simple方式桶巨细和添补速度是一样的,classic 方式更灵活一点,可以自界说添补速度,下面的例子暗示桶巨细为 10,添补速度为每分钟 5 个令牌:

1Refill filler = Refill.greedy(5, Duration.ofMinutes(1));
2Bandwidth limit = Bandwidth.classic(10, filler);

其中,Refill用于添补令牌桶,可以经过它界说添补速度,Bucket4j 有两种添补令牌的战略:间隔战略(intervally) 和 贪心战略(greedy)。在上面的例子中我们利用的是贪心战略,假如利用间隔战略可以像下面这样建立Refill:

1Refill filler = Refill.intervally(5, Duration.ofMinutes(1));

所谓间隔战略指的是每隔一段时候,一次性的添补一切令牌,比如上面的例子,会每隔一分钟,添补 5 个令牌,以下所示:



而贪心战略会尽能够贪心的添补令牌,一样是上面的例子,会将一分钟分别红 5 个更小的时候单元,每隔 12 秒,添补 1 个令牌,以下所示:



在领会了 Bucket4j 中的几个焦点概念以后,我们再来看看官网先容的一些特征:

  • 基于令牌桶算法
  • 高性能,无锁实现
  • 不存在精度题目,一切计较都是基于整型的
  • 支持经过合适 JCache API 标准的散布式缓存系统实现散布式限流
  • 支持为每个 Bucket 设备多个 Bandwidth
  • 支持同步和异步 API
  • 支持可插拔的监听 API,用于集成监控和日志
  • 不但可以用于限流,还可以用于简单的调剂

Bucket4j 供给了丰富的文档,保举在利用 Bucket4j 之前,先把官方文档中的 根基用法 和 高级特征 仔细阅读一遍。别的,关于 Bucket4j 的利用,保举这篇文章 Rate limiting Spring MVC endpoints with bucket4j,这篇文章具体的讲授了若何在 Spring MVC 中利用阻挡器和 Bucket4j 打造营业无侵入的限流计划,别的还讲授了若何利用 Hazelcast 实现散布式限流;别的,Rate Limiting a Spring API Using Bucket4j 这篇文章也是一份很好的入门教程,先容了 Bucket4j 的根本常识,在文章的最初还供给了 Spring Boot Starter 的集成方式,连系 Spring Boot Actuator 很轻易将限流目标集成到监控系统中。和 Guava 的限流器相比,Bucket4j 的功用明显要更胜一筹,究竟 Guava 的目标只是用作通用工具类,而不是用于限流的。利用 Bucket4j 根基上可以满足我们的大大都要求,不但支持单机限流和散布式限流,而且可以很好的集成监控,搭配 Prometheus 和 Grafana 简直完善。值得一提的是,有很多开源项目比方 JHipster API Gateway 就是利用 Bucket4j 来实现限流的。Bucket4j 唯一不敷的地方是它只支持请求频次限流,不支持并发量限流,别的还有一点,虽然 Bucket4j 支持散布式限流,但它是基于 Hazelcast 这样的散布式缓存系统实现的,不能利用 Redis,这在很多利用 Redis 作缓存的项目中就很不爽,所以我们还需要在开源的天下里继续摸索。

3.3 Resilience4j

Resilience4j 是一款轻量级、易利用的高可用框架。用过 Spring Cloud 早期版本的同学肯建都听过 Netflix Hystrix,Resilience4j 的设想灵感就来自于它。自从 Hystrix 停止保护以后,官方也保举大师利用 Resilience4j 来取代 Hystrix。



Resilience4j 的底层采用 Vavr,这是一个很是轻量级的 Java 函数式库,使得 Resilience4j 很是合适函数式编程。Resilience4j 以装潢器形式供给对函数式接口或 lambda 表达式的封装,供给了一波高可用机制:重试(Retry)熔断(Circuit Breaker)限流(Rate Limiter)限时(Timer Limiter)隔离(Bulkhead)缓存(Caceh)升级(Fallback)。我们重点关注这里的两个功用:限流(Rate Limiter) 和 隔离(Bulkhead),Rate Limiter 是请求频次限流,Bulkhead 是并发量限流。Resilience4j 供给了两种限流的实现:SemaphoreBasedRateLimiterAtomicRateLimiterSemaphoreBasedRateLimiter基于信号量实现,用户的每次请求城市申请一个信号量,并记录申请的时候,申请经过则答应请求,申请失利则限流,别的有一个内部线程会定期扫描过期的信号量并开释,很明显这是令牌桶的算法。AtomicRateLimiter和上面的典范实现类似,不需要额外的线程,在处置每次请求时,按照间隔上次请求的时候和天生令牌的速度自动添补。关于这两者的区分可以参考文章 Rate Limiter Internals in Resilience4j。Resilience4j 也供给了两种隔离的实现:SemaphoreBulkheadThreadPoolBulkhead,经过信号量或线程池控制请求的并发数,具体的用法参考官方文档,这里不再赘述。下面是一个同时利用限流和隔离的例子:

1// 建立一个 Bulkhead,最大并发量为 150
2BulkheadConfig bulkheadConfig = BulkheadConfig.custom()
3 .maxConcurrentCalls(150)
4 .maxWaitTime(100)
5 .build();
6Bulkhead bulkhead = Bulkhead.of("backendName", bulkheadConfig);
7
8// 建立一个 RateLimiter,每秒答应一次请求
9RateLimiterConfig rateLimiterConfig = RateLimiterConfig.custom()
10 .timeoutDuration(Duration.ofMillis(100))
11 .limitRefreshPeriod(Duration.ofSeconds(1))
12 .limitForPeriod(1)
13 .build();
14RateLimiter rateLimiter = RateLimiter.of("backendName", rateLimiterConfig);
15
16// 利用 Bulkhead 和 RateLimiter 装潢营业逻辑
17Supplier<String> supplier = () -> backendService.doSomething();
18Supplier<String> decoratedSupplier = Decorators.ofSupplier(supplier)
19 .withBulkhead(bulkhead)
20 .withRateLimiter(rateLimiter)
21 .decorate();
22
23// 挪用营业逻辑
24Try<String> try = Try.ofSupplier(decoratedSupplier);
25assertThat(try.isSuccess()).isTrue();

Resilience4j 在功用特征上比 Bucket4j 强大很多,而且还支持并发量限流。不外最大的遗憾是,Resilience4j 不支持散布式限流。

3.4 其他

网上还有很多限流相关的开源项目,不成能逐一先容,这里列出来的只是冰山之一角:

  • https://github.com/mokies/ratelimitj
  • https://github.com/wangzheng0822/ratelimiter4j
  • https://github.com/wukq/rate-limiter
  • https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
  • https://github.com/onblog/SnowJena
  • https://gitee.com/zhanghaiyang/spring-boot-starter-current-limiting
  • https://github.com/Netflix/concurrency-limits

可以看出,限流技术在现实项目中利用很是普遍,大师对实现自己的限流算法乐此不疲,新算法和新实现层见叠出。可是找来找去,今朝还没有找到一款开源项目完全满足我的需求。我的需务实在很简单,需要同时满足两种分歧的限流场景:请求频次限流和并发量限流,而且能同时满足两种分歧的限流架构:单机限流和散布式限流。下面我们就起头在 Spring Cloud Gateway 中实现这几种限流,经过前面先容的那些项目,我们扬长避短,根基上都能用比力成熟的技术实现,只不外对于最初一种情况,散布式并发量限流,网上没有搜到现成的处理计划,在和同事会商了几个早晨以后,想出一种新型的基于双窗口滑动的限流算法,我在这里举一反三,接待大师批评斧正,假如大师有更好的方式,也接待会商。

四、在网关中实现限流

在文章一路头先容 Spring Cloud Gateway 的特征时,我们留意到其中有一条Request Rate Limiting,说明网关自带了限流的功用,可是 Spring Cloud Gateway 自带的限流有很多限制,比方不支持单机限流,不支持并发量限流,而且它的请求频次限流也是不尽人意,这些都需要我们自己脱手来处理。

写代码的渣渣鹏

4.1 实现单机请求频次限流

Spring Cloud Gateway 中界说了关于限流的一个接口 RateLimiter,以下:

1public interface RateLimiter<C> extends StatefulConfigurable<C> {
2 Mono<RateLimiter.Response> isAllowed(String routeId, String id);
3}

这个接口就一个方式isAllowed,第一个参数routeId暗示请求路由的 ID,按照 routeId 可以获得限流相关的设置,第二个参数id暗示要限流的工具的唯一标识,可所以用户名,也可以是 IP,大概其他的可以从ServerWebExchange中获得的信息。我们看下RequestRateLimiterGatewayFilterFactory中对isAllowed的挪用逻辑:

1@Override
2public GatewayFilter APPly(Config config) {
3 // 从设置中获得 KeyResolver
4 KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver);
5 // 从设置中获得 RateLimiter
6 RateLimiter<Object> limiter = getOrDefault(config.rateLimiter,
7 defaultRateLimiter);
8 boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey);
9 HttpStatusHolder emptyKeyStatus = HttpStatusHolder
10 .parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));
11
12 return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY)
13 .flatMap(key -> {
14 // 经过 KeyResolver 获得 key,作为唯一标识 id 传入 isAllowed() 方式
15 if (EMPTY_KEY.equals(key)) {
16 if (denyEmpty) {
17 setResponseStatus(exchange, emptyKeyStatus);
18 return exchange.getResponse().setComplete();
19 }
20 return chain.filter(exchange);
21 }
22 // 获得当前路由 ID,作为 routeId 参数传入 isAllowed() 方式
23 String routeId = config.getRouteId();
24 if (routeId == null) {
25 Route route = exchange
26 .getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
27 routeId = route.getId();
28 }
29 return limiter.isAllowed(routeId, key).flatMap(response -> {
30
31 for (Map.Entry<String, String> header : response.getHeaders()
32 .entrySet()) {
33 exchange.getResponse().getHeaders().add(header.getKey(),
34 header.getValue());
35 }
36 // 请求答应,间接走到下一个 filter
37 if (response.isAllowed()) {
38 return chain.filter(exchange);
39 }
40 // 请求被限流,返回设备的 HTTP 状态码(默许是 429)
41 setResponseStatus(exchange, config.getStatusCode());
42 return exchange.getResponse().setComplete();
43 });
44 });
45}

从上面的的逻辑可以看出,经过实现KeyResolver接口的resolve方式便可以自界说要限流的工具了。

1public interface KeyResolver {
2 Mono<String> resolve(ServerWebExchange exchange);
3}

比以下面的 HostAddrKeyResolver 可以按照 IP 来限流:

1public interface KeyResolver {
2 Mono<String> resolve(ServerWebExchange exchange);
3}
4比以下面的 HostAddrKeyResolver 可以按照 IP 来限流:
5public class HostAddrKeyResolver implements KeyResolver {
6 @Override
7 public Mono<String> resolve(ServerWebExchange exchange) {
8 return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
9 }
10}

我们继续看 Spring Cloud Gateway 的代码发现,RateLimiter 接口只供给了一个实现类 RedisRateLimiter:




很明显是基于 Redis 实现的限流,虽说经过 Redis 也可以实现单机限流,可是总感受有些牛鼎烹鸡,而且对于那些没有 Redis 的情况很不友爱。所以,我们要实现实在的当地限流。

我们从 Spring Cloud Gateway 的 pull request 中发现了一个新特征 Feature/local-rate-limiter,而且看提交记录,这个新特征很有能够会合并到 3.0.0 版本中。我们无妨来看下这个 local-rate-limiter 的实现:LocalRateLimiter.java,可以看出它是基于 Resilience4成心机的是,这个类 还有一个早期版本,是基于Bucket4j实现的:

1public Mono<Response> isAllowed(String routeId, String id) {
2 Config routeConfig = loadConfiguration(routeId);
3
4 // How many requests per second do you want a user to be allowed to do?
5 int replenishRate = routeConfig.getReplenishRate();
6
7 // How many seconds for a token refresh?
8 int refreshPeriod = routeConfig.getRefreshPeriod();
9
10 // How many tokens are requested per request?
11 int requestedTokens = routeConfig.getRequestedTokens();
12
13 final io.github.resilience4j.ratelimiter.RateLimiter rateLimiter = RateLimiterRegistry
14 .ofDefaults()
15 .rateLimiter(id, createRateLimiterConfig(refreshPeriod, replenishRate));
16
17 final boolean allowed = rateLimiter.acquirePermission(requestedTokens);
18 final Long tokensLeft = (long) rateLimiter.getMetrics().getAvailablePermissions();
19
20 Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));
21 return Mono.just(response);
22}

成心机的是,这个类 还有一个早期版本,是基于 Bucket4j 实现的:

1public Mono<Response> isAllowed(String routeId, String id) {
2
3 Config routeConfig = loadConfiguration(routeId);
4
5 // How many requests per second do you want a user to be allowed to do?
6 int replenishRate = routeConfig.getReplenishRate();
7
8 // How much bursting do you want to allow?
9 int burstCapacity = routeConfig.getBurstCapacity();
10
11 // How many tokens are requested per request?
12 int requestedTokens = routeConfig.getRequestedTokens();
13
14 final Bucket bucket = bucketMap.computeIfAbsent(id,
15 (key) -> createBucket(replenishRate, burstCapacity));
16
17 final boolean allowed = bucket.tryConsume(requestedTokens);
18
19 Response response = new Response(allowed,
20 getHeaders(routeConfig, bucket.getAvailableTokens()));
21 return Mono.just(response);
22}

实现方式都是类似的,在上面临 Bucket4j 和 Resilience4j 已经作了比力具体的先容,这里不再赘述。不外从这里也可以看出 Spring 生态圈对 Resilience4j 是比力看好的,我们也可以将其引入到我们的项目中。

4.2 实现散布式请求频次限流

上面先容了若何实现单机请求频次限流,接下来再看下散布式请求频次限流。这个就比力简单了,由于上面说了,Spring Cloud Gateway 自带了一个限流实现,就是RedisRateLimiter,可以用于散布式限流。它的实现道理仍然是基于令牌桶算法的,不外实现逻辑是放在一段 lua 剧本中的,我们可以在src/main/resources/META-INF/scripts目录下找到该剧本文件request_rate_limiter.lua:

1local tokens_key = KEYS[1]
2local timestamp_key = KEYS[2]
3
4local rate = tonumber(ARGV[1])
5local capacity = tonumber(ARGV[2])
6local now = tonumber(ARGV[3])
7local requested = tonumber(ARGV[4])
8
9local fill_time = capacity/rate
10local ttl = math.floor(fill_time*2)
11
12local last_tokens = tonumber(redis.call("get", tokens_key))
13if last_tokens == nil then
14 last_tokens = capacity
15end
16
17local last_refreshed = tonumber(redis.call("get", timestamp_key))
18if last_refreshed == nil then
19 last_refreshed = 0
20end
21
22local delta = math.max(0, now-last_refreshed)
23local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
24local allowed = filled_tokens >= requested
25local new_tokens = filled_tokens
26local allowed_num = 0
27if allowed then
28 new_tokens = filled_tokens - requested
29 allowed_num = 1
30end
31
32if ttl > 0 then
33 redis.call("setex", tokens_key, ttl, new_tokens)
34 redis.call("setex", timestamp_key, ttl, now)
35end
36
37return { allowed_num, new_tokens }

这段代码和上面先容令牌桶算法时用 Java 实现的那段典范代码几近是一样的。这里利用 lua 剧本,主如果操纵了 Redis 的单线程特征,以及履行 lua 剧本的原子性,避免了并发拜候时能够出现请求量超越上限的现象。设想今朝令牌桶中还剩 1 个令牌,此时有两个请求同时到来,判定令牌能否充足也是同时的,两个请求都以为还剩 1 个令牌,因而两个请求都被答应了。有两种方式来设置 Spring Cloud Gateway 自带的限流。第一种方式是经过设置文件,比以下面所示的代码,可以对某个 route 停止限流:

1spring:
2 cloud:
3 gateway:
4 routes:
5 - id: test
6 uri: http://httpbin.org:80/get
7 filters:
8 - name: RequestRateLimiter
9 args:
10 key-resolver: '#{@hostAddrKeyResolver}'
11 redis-rate-limiter.replenishRate: 1
12 redis-rate-limiter.burstCapacity: 3

其中,key-resolver利用 SpEL 表达式 #{@beanName} 从 Spring 容器中获得hostAddrKeyResolver工具,burstCapacity暗示令牌桶的巨细,replenishRate暗示每秒往桶中添补几多个令牌,也就是添补速度。第二种方式是经过下面的代码来设置:

1@Bean
2public RouteLocator myRoutes(RouteLocatorBuilder builder) {
3 return builder.routes()
4 .route(p -> p
5 .path("/get")
6 .filters(filter -> filter.requestRateLimiter()
7 .rateLimiter(RedisRateLimiter.class, rl -> rl.setBurstCapacity(3).setReplenishRate(1)).and())
8 .uri("http://httpbin.org:80"))
9 .build();
10}

这样便可以对某个 route 停止限流了。可是这里有一点要留意,Spring Cloud Gateway 自带的限流器有一个很大的坑,replenishRate 不支持设备小数,也就是说往桶中添补的 token 的速度最少为每秒 1 个,所以,假如我的限流法则是每分钟 10 个请求(按理说应当每 6 秒添补一次,或每秒添补 1/6 个 token),这类情况 Spring Cloud Gateway 就没法正确的限流。网上也有人提了 issue,support greater than a second resolution for the rate limiter,但还没有获得处理。

4.3 实现单机并发量限流

上面进修 Resilience4j 的时辰,我们提到了 Resilience4j 的一个功用特征,叫 隔离(Bulkhead)。Bulkhead 这个单词的意义是船的舱壁,操纵舱壁可以将分歧的船舱隔离起来,这样假如一个船舱破坏进水,那末只损失这一个船舱,别的船舱可以不受影响。鉴戒造船行业的经历,这类形式也被引入到软件行业,我们把它叫做 舱壁形式(Bulkhead pattern)。舱壁形式一般用于办事隔离,对于一些比力重要的系统资本,如 CPU、内存、毗连数等,可以为每个办事设备各自的资本限制,避免某个异常的办事把系统的一切资本都消耗掉。这类办事隔离的思唯一样可以用来做并发量限流。正如前文所述,Resilience4j 供给了两种 Bulkhead 的实现:SemaphoreBulkhead和ThreadPoolBulkhead,这也正是舱壁形式常见的两种实现计划:一种是带计数的信号量,一种是牢固巨细的线程池。斟酌到多线程场景下的线程切换本钱,默许保举利用信号量。在操纵系统根本课程中,我们进修过两个名词:互斥量(Mutex)信号量(Semaphores)。互斥量用于线程的互斥,它和临界区有点类似,只要具有互斥工具的线程才有拜候资本的权限,由于互斥工具只要一个,是以任何情况下只会有一个线程在拜候此同享资本,从而保证了多线程可以平安的拜候和操纵同享资本。而信号量是用于线程的同步,这是由荷兰科学家 E.W.Dijkstra 提出的概念,它和互斥量分歧,信号答应多个线程同时利用同享资本,可是它同时设定了拜候同享资本的线程最大数目,从而可以停止并发量控制。下面是利用信号量限制并发拜候的一个简单例子:

1public class SemaphoreTest {
2
3 private static ExecutorService threadPool = Executors.newFixedThreadPool(100);
4 private static Semaphore semaphore = new Semaphore(10);
5
6 public static void main(String[] args) {
7 for (int i = 0; i < 100; i++) {
8 threadPool.execute(new Runnable() {
9 @Override
10 public void run() {
11 try {
12 semaphore.acquire();
13 System.out.println("Request processing ...");
14 semaphore.release();
15 } catch (InterruptedException e) {
16 e.printStack();
17 }
18 }
19 });
20 }
21 threadPool.shutdown();
22 }
23}

这里我们建立了 100 个线程同时履行,可是由于信号量计数为 10,所以同时只能有 10 个线程在处置请求。说到计数,现实上,在 Java 里除了Semaphore还有很多类也可以用作计数,比如AtomicLong或LongAdder,这在并发量限流中非经常见,只是没法供给像信号量那样的阻塞才能:

1public class AtomicLongTest {
2
3 private static ExecutorService threadPool = Executors.newFixedThreadPool(100);
4 private static AtomicLong atomic = new AtomicLong();
5
6 public static void main(String[] args) {
7 for (int i = 0; i < 100; i++) {
8 threadPool.execute(new Runnable() {
9 @Override
10 public void run() {
11 try {
12 if(atomic.incrementAndGet() > 10) {
13 System.out.println("Request rejected ...");
14 return;
15 }
16 System.out.println("Request processing ...");
17 atomic.decrementAndGet();
18 } catch (InterruptedException e) {
19 e.printStack();
20 }
21 }
22 });
23 }
24 threadPool.shutdown();
25 }
26}

4.4 实现散布式并发量限流经过在单机实现并发量限流,我们把握了几种常用的手段:信号量、线程池、计数器,这些都是单机上的概念。那末稍微拓展下,假如能实现散布式信号量、散布式线程池、散布式计数器,那末实现散布式并发量限流不就易如反掌了吗?关于散布式线程池,是我自己诬捏的词,在网上并没有找到类似的概念,比力接近的概念是资本调剂和分发,可是又感受不像,这里间接疏忽吧。关于散布式信号量,还真有这样的工具,比如 Apache Ignite 就供给了IgniteSemaphore用于建立散布式信号量,它的利用方式和Semaphore很是类似。利用 Redis 的 ZSet 也可以实现散布式信号量,比如 这篇博客先容的方式,还有《Redis in Action》这本电子书中也提到了这样的例子,教你若何实现 Counting semaphores。别的,Redisson 也实现了基于 Redis 的散布式信号量 RSemaphore,用法也和Semaphore类似。利用散布式信号量可以很轻易实现散布式并发量限流,实现方式和上面的单机并发量限流几近是一样的。最初,关于散布式计数器,实现计划也是多种多样。比如利用 Redis 的INCR就很轻易实现,更有甚者,利用 MySQL 数据库也可以实现。只不外利用计数器要留意操纵的原子性,每次请求时都要经过这三步操纵:取计数器当前的值、判定能否跨越阈值,跨越则拒绝、将计数器的值自增。这实在和信号量的 P 操纵是一样的,而开释就对应 V 操纵。所以,操纵散布式信号量和计数器便可以实现并发量限流了吗?题目固然没有这么简单。现实上,上面经过信号量和计数器实现单机并发量限流的代码片断有一个严重 BUG:

1semaphore.acquire();
2System.out.println("Request processing ...");
3semaphore.release();

设想一下假如在处置请求时出现异常了会怎样样?很明显,信号量被该线程获得了,可是却永久不会开释,假如请求异常多了,这将致使信号量被占满,最初一个请求也进不来。在单机场景下,这个题目可以很轻易处理,加一个 finally 就行了:

1try {
2 semaphore.acquire();
3 System.out.println("Request processing ...");
4} catch (InterruptedException e) {
5 e.printStack();
6} finally {
7 semaphore.release();
8}

由于不管出现何种异常,finally 中的代码一定会履行,这样就保证了信号量一定会被开释。可是在散布式系统中,就不是加一个 finally 这么简单了。这是由于在散布式系统中能够存在的异常纷歧定是可被捕捉的代码异常,还有能够是办事解体大概不成预知的系统宕机,就算是一般的办事重启也能够致使散布式信号量没法开释。对于这个题目,我和几个同事持续会商了几个早晨,想出了两种处理方式:第一种方式是利用带 TTL 的计数器,第二种方式是基于双窗口滑动的一种比力 tricky 的算法。第一种方式比力轻易了解,我们为每个请求赋予一个唯一 ID,并在 Redis 里写入一个键值对,key 为requests_xxx(xxx 为请求 ID),value 为 1,并给这个 key 设备一个 TTL(假如你的利用中存在耗时很是长的请求,比方对于一些 WebSockket 请求能够会延续几个小时,还需要开一个线程定期去革新这个 key 的 TTL)。然后在判定并发量时,利用KEYS号令查询requests_* 开首的 key 的个数,便可以晓得当前一共有几多个请求,假如跨越并发量上限则拒绝请求。这类方式可以很好的应对办事解体或重启的题目,由于每个 key 都设备了 TTL,所以经过一段时候后,这些 key 就会自动消失,就不会出现信号量占满不开释的情况了。可是这里利用KEYS号令查询请求个数是一个很是低效的做法,在请求量比力多的情况下,网关的性能会遭到严重影响。我们可以把KEYS号令换成SCAN,性能会获得些许提升,但整体来说结果还是很不理想的。针对第一种方式,我们可以进一步优化,不用为每个请求写一个键值对,而是为每个散布式系统中的每个实例赋予一个唯一 ID,并在 Redis 里写一个键值对,key 为instances_xxx(xxx 为实例 ID),value 为这个实例当前的并发量。一样的,我们为这个 key 设备一个 TTL,而且开启一个线程定期去革新这个 TTL。每接管一个请求后,计数器加一,请求竣事,计数器减一,这和单机场景下的处置方式一样,只不外在判定并发量时,还是需要利用KEYS或SCAN获得一切的实例,并计较出并发量的总和。不外由于实例个数是有限的,性能比之前的做法有了明显的提升。第二种方式我称之为 双窗口滑动算法,连系了 TTL 计数器和滑动窗口算法。我们按分钟来设备一个时候窗口,在 Redis 里对应202009051130 这样的一个 key,value 为计数器,暗示请求的数目。当接管一个请求后,在当前的时候窗口中加一,当请求竣事,在当前的时候窗口中减一,留意,接管请求和请求竣事的时候窗口能够不是同一个。别的,我们还需要一个当地列表来记录当前实例正在处置的一切请求和请求对应的时候窗口,并经过一个小于时候窗口的按时线程(如 30 秒)来迁移过期的请求,所谓过期,指的是请求的时候窗口和当前时候窗口纷歧致。那末具体若何迁移呢?我们首先需要统计列表中一共有几多请求过期了,然后将列表中的过期请求时候更新为当前时候窗口,并从 Redis 中上一个时候窗口移动响应数目到当前时候窗口,也就是上一个时候窗口减 X,当前时候窗口加 X。由于迁移线程定期履行,所以过期的请求总是会被移动到当前窗口,终极 Redis 中只要当前时候窗口和上个时候窗口这两个时候窗口中稀有据,再早一点的窗口时候中的数据会被往后迁移,所以可以给这个 key 设备一个 3 分钟或 5 分钟的 TTL。判定并发量时,由于只要两个 key,只需要利用MGET获得两个值相加即可。下面的流程图具体描写了算法的运转进程:



其中有几个需要留意的细节:

  1. 请求竣事时,间接在 Redis 中当前时候窗口减一即可,就算是负数也没关系。请求列表中的该请求不用急着删除,可以打上竣事标志,在迁移线程中同一删除(固然,假如请求的起头时候和竣事时候在同一个窗口,可以间接删除);
  2. 迁移的时候间隔要小于时候窗口,一般设备为 30s;
  3. Redis 中的 key 一定要设备 TTL,时候最少为 2 个时候窗口,一般设备为 3 分钟;
  4. 迁移进程触及到“从上一个时候窗口减”和“在当前时候窗口加”两个操纵,要留意操纵的原子性;
  5. 获得当前并发量可以经过 MGET 一次性读取两个时候窗口的值,不用 GET 两次;
  6. 获得并发量和判定并发量能否超限,这个进程也要留意操纵的原子性。

总结

网关作为微办事架构中的重要一环,充任着一夫当关万夫莫开的脚色,所以对网关办事的稳定性要求和性能要求都很是高。为保证网关办事的稳定性,一代又一代的法式员们前仆后继,想出了十八般技艺:限流、熔断、隔离、缓存、升级、等等等等。这篇文章从限流动手,具体先容了限流的场景和算法,以及源码实现和能够踩到的坑。虽然限流只是网关的一个很是小的功用,但却影响到网关的各个方面,在系统架构的设想中相当重要。虽然我试着从分歧的角度希望把限流先容的更完全,但毕竟是井蛙之见,只见一斑,还有很多的内容没有先容到,比如阿里开源的 Sentinel 组件也可以用于限流,由于篇幅有限未能展开。别的前文提到的 Netflix 不再保护 Hystrix 项目,这是由于他们把精神放到另一个限流项目 concurrency-limits 上了,这个项目标方针是打造一款自顺应的,极具弹性的限流组件,它鉴戒了 TCP 堵塞控制的算法(TCP congestion control algorithm),实现系统的自动限流,感爱好的同学可以去它的项目主页领会更多内容。本文篇幅较长,难免疏漏,若有题目,还望不惜赐教。

参考

  1. 微办事网关实战——Spring Cloud Gateway
  2. 《亿级流量网站架构焦点技术》张开涛
  3. 聊聊高并发系统之限流特技
  4. 架构师长大之路之限流
  5. 微办事接口限流的设想与思考
  6. 常用4种限流算法先容及比力
  7. 来谈谈限流-从概念到实现
  8. 高并发下的限流分析
  9. 计数器算法
  10. 基于Redis的限流系统的设想
  11. API 挪用次数限制实现
  12. Techniques to Improve QoS
  13. An alternative approach to rate limiting
  14. Scaling your API with rate limiters
  15. Brief overview of token-bucket algorithm
  16. Rate limiting Spring MVC endpoints with bucket4j
  17. Rate Limiter Internals in Resilience4j
  18. 高可用框架Resilience4j利用指南
  19. 阿里巴巴开源限流系统 Sentinel 全剖析
  20. spring cloud gateway 之限流篇
  21. 办事容错形式
  22. 你的API会自顺应「弹性」限流吗?
来历:https://www.aneasystone.com/archives/2020/08/spring-cloud-gateway-current-limiting.html

高端人脉微信群

高端人脉微信群

人脉=钱脉,我们相信天下没有聚不拢的人脉,扫码进群找到你所需的人脉,对接你所需的资源。

商业合作微信

商业合作微信

本站创始人微信,13年互联网营销经验,擅长引流裂变、商业模式、私域流量,高端人脉资源丰富。

精彩点评

相关推荐

平台大洗牌,被限流该怎么办?

平台大洗牌,被限流该怎么办?

最近平台大洗牌,评论区问最多的就是,如果被限流了该怎么办,今天就详细的解答这个问

降级-熔断-限流-傻傻分不清楚

降级-熔断-限流-傻傻分不清楚

1. 熔断1.1 熔断来源我们家用电闸上都有保险丝模块,当电压出现短路问题时,自动跳闸

血泪教训:小红书被限流怎么办?怎么恢复正常?

血泪教训:小红书被限流怎么办?怎么恢复正常?

很多新手小白刚开始运营小红书的时候,多半会经历很长一段数据低迷的情况。一方面,是

小红书无故被限流了??这里有你不知道的小红书限流小 ...

小红书无故被限流了??这里有你不知道的小红书限流小 ...

自从有消息爆出小红书内测直播功能之后,小红书成为了当下最受关注的平台之一。而正在

《抖音限流/降权/违规词手册》

《抖音限流/降权/违规词手册》

为了让更多小伙伴能够自己找到并解决【抖音限流】问题,今天为大家梳理一份抖音「限流

闲鱼被限流了,教你恢复元气

闲鱼被限流了,教你恢复元气

作者:咸鱼暴增系统很多朋友在使用小闲的时候都有可能碰到没有流量了,或者流量降低了

司马南为何被限流?多篇文章和视频下架!

司马南为何被限流?多篇文章和视频下架!

连续两天,灵灵看世界针对司马南污蔑、挑衅张文宏和陶斯亮提出质疑和批评,结果是:司

一文搞清楚限流、熔断和降级

一文搞清楚限流、熔断和降级

限流是对系统的被请求频率以及内部的部分功能的执行频率加以限制,防止因突发的流量激

作品播放量低,账号是不是被限流了,该怎么办呢?

作品播放量低,账号是不是被限流了,该怎么办呢?

前天我新发布的视频的播放量创历史了,有史以来最低的,你是不是也遇到过这样的情况?

一招判断自己的微头条是否被限流,被限流后,到底该怎么解决呢?

一招判断自己的微头条是否被限流,被限流后,到底该怎么解决呢?

对于一个自媒体创作者来说,辛辛苦苦创作的内容,发表后阅读量寥寥无几,那种心痛只有

阿里巴巴开源限流系统 Sentinel 全解析

阿里巴巴开源限流系统 Sentinel 全解析

今年下半年阿里开源了自研的限流系统 Sentinel,官方对 Sentinel 的介绍中用到了一系

展现量少怀疑被限流,打开它让你一看究竟,稍做修改也许爆了

展现量少怀疑被限流,打开它让你一看究竟,稍做修改也许爆了

不少友友顽强坚持,笔耕不辍,作品不少,但展现量不高,毕竟展现量才是根本,没有展现

文章被限流了怎么办?如何采取行动进行补救?我以昨天的文章为例

文章被限流了怎么办?如何采取行动进行补救?我以昨天的文章为例

文章被限流,是大家一直以来热议的一个话题,往往也只能表示无奈,没有任何办法,毕竟

第一批蹦迪选手已经“阳康”?武汉夜店开始限流了。

第一批蹦迪选手已经“阳康”?武汉夜店开始限流了。

月初我们找了22位知名的夜店老板做了一期专访,话题是关于“放开后夜店生意变好了吗?

如何判断账号是否被限流?没流量?赶紧查看这个地方

如何判断账号是否被限流?没流量?赶紧查看这个地方

只要查看一个地方,就可以知道你的账号是不是被限流了,好多朋友播放量突然间大幅度降

头条被限流可能是因为这几个原因,浅谈一下头条推荐机制

头条被限流可能是因为这几个原因,浅谈一下头条推荐机制

前段时间写了一篇文章刚发第一天没什么流量,突然第二天流量暴涨了,随之问题也跟着来

一篇文章说清楚头条“限流”那些事儿

一篇文章说清楚头条“限流”那些事儿

相信很多创作者都遇到辛苦写的微头条没有展现,也没有阅读量的情况,有些创作者知道这

抖音限流原因归纳总结与破解之道

抖音限流原因归纳总结与破解之道

2022年随着5G的来临,短视频作为目前最高阶最有效的信息展现方式,风口期必将持续不断

5种限流算法,7种限流方式,挡住突发流量?

5种限流算法,7种限流方式,挡住突发流量?

最近几年,随着微服务的流行,服务和服务之间的依赖越来越强,调用关系越来越复杂,服

主流的四种限流策略,我都可以通过redis实现

主流的四种限流策略,我都可以通过redis实现

引言在web开发中功能是基石,除了功能以外运维和防护就是重头戏了。因为在网站运行期

商业洽谈 文章投递 寻求报道
电话咨询: 15924191378
关注微信