关于java:餐厅小故事-服务限流的实施

7次阅读

共计 4549 个字符,预计需要花费 12 分钟才能阅读完成。

大家好,我是小菜。
一个心愿可能成为 吹着牛 X 谈架构 的男人!如果你也想成为我想成为的人,不然点个关注做个伴,让小菜不再孤独!

本文次要介绍 服务限流

如有须要,能够参考

如有帮忙,不忘 点赞

微信公众号已开启,小菜良记,没关注的同学们记得关注哦!

天气微凉,适宜火锅?走起!小菜来到了海上捞火锅店,意料之中的人满为患,想走?嘴巴却不批准。那只能拿号排队了!看着每家店都是人满为患的样子,心里不禁意想了起来,如果我是某家店的老板那还用的敲代码吗?

瞎想总会成为可能,我不禁洋洋为我未来的餐厅思考起来了,一家饭店,受场地规模和工作人员的因素可能承载的客流总是被限度的,因而很多受欢迎的餐厅在高峰期的时候都须要排队,餐厅为满载之后的客人排号,只有当顾客用餐结束之后,能力让有号码牌且对应号码程序的顾客进入餐厅就餐。那为什么要这么设计呢?其实这就是一种限流措施,严格控制客流量使其稳固在餐厅的经营能力之内,不会因客流量骤增而导致餐厅无奈失常营业。这种限流形式保障在餐厅内就餐的顾客总数(并发量)是统一的。只有走了一个客人,能力容许一位客人进入,井条有序,正当运行!

服务限流 该当是每个并发程序都应该思考的~!限流的目标不仅是为了管制拜访的总并发量,而且还要尽量让拜访的流量来的更平衡,这样才不会让零碎的负载大起大落,因而又称为"流量整形"

当然在微服务流行的时代,咱们思考到的 服务限流 不再单单应答 单体服务 ,而是更要分明 分布式场景 下如何进行 服限流

一丶单体限流

以上三种是咱们在 单体服务 中常见的限流算法,咱们接下来别离认识一下!

1、计数器限流

计数器限流 是属于一种比较简单粗犷的形式!

设计思路如下:

咱们会限度一秒钟内可能通过的申请数(比方 50),从第一个申请进来开始计数,在接下去的1s 内,每进来一个申请,咱们就会把计数值加 1,如果累加的数字达到了 50,那么后续的申请就会被全副回绝,等到 1s 过来之后,把计数复原成 0,并从新开始计数

应用计数器能够用来限度肯定工夫内的总并发数,但说到底这是一种简略粗犷的限流形式,而不是均匀速率限流,在某些场景下能够应用。然而遇到某些非凡的状况下,如果零碎的负载量只有 50,在第 59 秒霎时申请 50 次,并且在第 1:00 也申请了 50 次,那么这个程序在 1 秒内被申请了 100 次,霎时超过总负载,很有可能间接击垮咱们的应用程序!

当然,事件都没有相对的,咱们能够应用 滑动窗口 的形式解决问题。说到 滑动窗口 有些小伙伴并不生疏,因为 TCP 协定 就有采纳 滑动窗口 来管制流量,不分明的小伙伴往下看!

滑动窗口算法指的是以以后工夫为截止工夫,往前取肯定的工夫,比方取 60 秒工夫,在这 60 秒之内运行的最大拜访数为 50,此时算法的执行逻辑为:先革除 60 秒之前的所有申请记录,再计算以后汇合内申请数量是否大于设定的最大申请数 50?如果大于则执行限流回绝策略,否则插入本次申请记录并执行失常流程。

咱们在上图能够看出,一个被红色线段圈起来的就能够认为是一个工夫窗口( 1 分钟),而后咱们将工夫窗口进行划分为 5 小格子,也就相当于 1 个小格是 12 s,每超过 12 s,工夫窗口就会往前步移一格,每一格都有本人独立的计数器,假如第 35 秒的时候来了一个申请,那么 0:25~0:36 这个范畴格子的计数值就会加 1。

咱们通过上图回顾下 计数器 限流会遇到的问题,当 0:59 来了 50 个申请时会落在上图 紫色区域 中,如果 1:00 又来了 50 个申请,会落在上图的 粉色区域 中,因为工夫窗口的挪动,总共 100 个申请落在了同一个工夫窗口中,就会被检测出从而触发限流。而这就是 滑动窗口 的思维,接下来咱们能够借助 Redis 来简略演示下:

执行后果:

Thread-0    失常执行
Thread-2    失常执行
Thread-3    失常执行
Thread-6    失常执行
Thread-7    失常执行
Thread-10    失常执行
Thread-11    失常执行
Thread-14    失常执行
Thread-1    失常执行
Thread-4    失常执行
Thread-5    失常执行
Thread-8    超出最大的零碎负载量, 执行限流
Thread-8    失常执行
Thread-9    超出最大的零碎负载量, 执行限流
Thread-9    失常执行
Thread-12    超出最大的零碎负载量, 执行限流
Thread-12    失常执行
Thread-13    超出最大的零碎负载量, 执行限流
Thread-13    失常执行

这段繁难的代码当然有许多破绽,然而仅仅给你提供一个实现的思路!

2、漏桶算法

咱们结尾说到的 餐厅排号 其实就是一品种漏桶的实现形式,餐厅的容量就相当于是一个 桶容量 ,桶的容量是固定的,桶底的水会一直的流出( 用餐完结的顾客 ),桶顶的水( 待用餐的顾客)一直流入。如果流入的水量(申请量)超出了流出的桶流量(最大并发量),桶满后新流入的水会间接溢出,这就是限流利用中罕用的漏桶算法。

其实 Java 就曾经自带了一个很好实现漏桶算法的工具,那就是 Semaphore,它能够无效的管制服务的最大并发总数,避免服务过载。上面是 Semaphore 的典型用法:

通过上述例子咱们不难发现,漏桶算法次要关注的是以后的并发总量(信号总量),只有某个资源被开释的信号收回(release操作),期待进入的申请能力取得“通行证 ”, 有出才有进,咱们通过这种形式同样能够保证系统的负载可控。

3、令牌桶算法

限流的另一种罕用算法是令牌桶算法,它的实现原理为零碎以恒定的速度往桶中放入令牌,申请须要从桶中获取令牌能力被解决,一旦桶中无令牌可取,则拒绝服务。

咱们能够借助第三方工具实现该算法,如 Google Guava 的 RateLimiter组件则是采纳令牌桶算法,以下是简略的应用示例:

OUTPUT:
Thread-1    2021-08-01 00:09:14
Thread-10    2021-08-01 00:09:14
Thread-9    2021-08-01 00:09:15
Thread-8    2021-08-01 00:09:15
Thread-6    2021-08-01 00:09:16
Thread-7    2021-08-01 00:09:16
Thread-5    2021-08-01 00:09:17
Thread-3    2021-08-01 00:09:17
Thread-4    2021-08-01 00:09:18
Thread-2    2021-08-01 00:09:18

从下面的后果上来看,令牌的确是 1 秒产生 2 个,而 acquire() 办法为阻塞期待令牌,它能够传递一个 int 类型的参数,用来指定获取令牌的个数,当然它还有一种代替办法 tryAcquire(),此办法在没有可用令牌的时候就会间接返回 false,这样就不会阻塞期待了。当然 tryAcquire()能够设置超时事件,未超过最大期待事件会阻塞期待获取令牌,如果超过了最大等待时间还没有可用的令牌就会返回 false

OUTPUT:
limit
limit
limit
limit
limit
limit
limit
limit
Thread-10    2021-08-01 00:08:05
Thread-4    2021-08-01 00:08:05

通过以上例子咱们能够总结:应用 RateLimiter实现的令牌桶算法不仅能够应答失常流量的限速,而且能够解决突发暴增的申请,实现平滑限流。

二丶分布式限流

单机限流 场景下,各个服务节点负责各自机器的限流,不关注其余节点,更不关注集群的总调用量~!然而后盾资源是无限的,在分布式的场景下,咱们的关注点不能再集中于某个节点上,有时候尽管各个单节点的流量都没有超,然而各个节点的流量和却超过了后盾资源的总接受量,所以必须管制服务在所有节点上的总流量,这就是 分布式限流

说到分布式,咱们会想到 网关 的概念

相干浏览请空降:《吃透微服务》– 服务网关之 Gateway

当咱们在理解完网关的概念与作用后,天然分明总流量管制能够在网关层面进行限流,然而有种 P2P 直连模式的服务集群就没有网关的概念。这个时候要怎么办呢?

咱们下面说到有时候单个节点没有超过总流量,然而节点流量和却超过了总流量。那咱们无妨先汇总每个服务节点的流量,并将汇总后的流量与预设的总流量进行比拟,如果超过了总流量就须要进行限流。

简略来说就是咱们如果集群的承载量为 1000,然而汇总进去的总流量是 1200,这个时候超了 200,就须要进行限流!那咱们这个时候就须要把流量升高到 (1 – (200/1200) ) = 0.83,而这个 0.83 就是个单机阈值,也就是咱们的限流比例,每个节点都要将以后的流量升高这个比例。

通过限流比例算出各自的限流阈值,而后再依据各自的限流阈值去调用下面说到的单机限流几种算法去做单机限流。因而集群环境下的限流也是以单点的限流为根底,然而在流量断定上有所不同。下面说的是一种限流思路的方向,接下来说下两种具体的限流操作。

1、Redis + Lua

这个限流策略重点在于 Lua 脚本的编写,什么是 Lua 脚本?有些同学又不淡定了~ 理解分布式锁的同学应该分明能够利用 Redis + Lua 实现分布式锁。

Lua是一种轻量玲珑的脚本语言,用规范 C 语言编写并以源代码模式凋谢,其设计目标是为了嵌入应用程序种,从而为程序提供灵便的扩大和定制性能。

既然 Lua 是外围,那咱们先理清一下 Lua的逻辑实现:

看着流程图自然而然代码就进去了~

1、首先定义两个全局变量别离用来接管 Redis 利用中传递的键和限流大小

2、在利用端传递 KEYS 是一个数据列表,在 Lua 脚本中通过索引下标形式获取数组内的值

3、在利用端传递 ARGV 参数比拟灵便,能够是一个或多个独立的参数,但对应到 Lua 脚本中对立应用 ARGV 这个数组接管,获取形式也是通过数组下标获取

编辑好 Lua 脚本后,咱们就能够在 Java 中欢快的应用了:

在具体的业务场景中,咱们能够自定义一个 限流注解,配合 AOP 切面达到限流的成果!

2、Nginx + Lua

应用 Nginx + Lua 的形式对系统侵入性较低!咱们间接看代码

Lua 局部

能够参考 OpenResty 官网给出的限流示例,稍加批改即可

而后咱们须要批改 nginx.conf 配置文件:

http 块中增加

而后在须要限流的 server 块中增加:

当然除了以上说到的两种实现思路外,咱们还能够利用现成的中间件 HystrixSentinel

Sentinel 相干浏览请空降:《吃透微服务》– 服务容错之 Sentinel

说到最初

说完了两种场景的限流,当然只是在很浅显的层面泛泛而谈!无妨再让我以一段骚话结尾:咱们在习惯单应用服务的时候,会发现其实单纯的单点限流并不难,因为咱们的关注点是 1,由 1 进行延长,事态往往会变得不可控,如果说单应用服务的时候咱们还有很多现成的组件能够抉择,然而如果要思考到整个分布式集群的限流形式,咱们往往手足无措。咱们须要思考服务节点的调用监控,日志采集,日志聚合,计算剖析,限流决策判断等等许多环节,然而不用恐怖,咱们换个方向想想,当你思考的越多,不正阐明你晋升也会越多,最怕的不是艰难与挑战,而是你原地踏步不前的无知!

我是小菜,与你结伴而行~

不要空谈,不要贪懒,和小菜一起做个 吹着牛 X 做架构 的程序猿吧~ 点个关注做个伴,让小菜不再孤独。咱们下文见!

明天的你多致力一点,今天的你就能少说一句求人的话!
我是小菜,一个和你一起变强的男人。 💋
微信公众号已开启,小菜良记,没关注的同学们记得关注哦!

正文完
 0