<p><span style="color: #ff0000"><strong>一、什么是限流?为什么要限流?</strong></span></p>
<p>不知道大家有没有做过帝都的地铁,就是进地铁站都要排队的那种,为什么要这样摆长龙转圈圈?答案就是为了<code>限流</code>!因为一趟地铁的运力是有限的,一下挤进去太多人会造成站台的拥挤、列车的超载,存在一定的安全隐患。同理,我们的程序也是一样,它处理请求的能力也是有限的,一旦请求多到超出它的处理极限就会崩溃。为了不出现最坏的崩溃情况,只能耽误一下大家进站的时间。<br>
</p>
<p>限流是保证系统高可用的重要手段!!!</p>
<p>由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,为了保证系统不被巨大的流量压垮,会在系统流量到达一定阈值时,拒绝掉一部分流量。</p>
<p>限流会导致用户在短时间内(这个时间段是毫秒级的)系统不可用,一般我们衡量系统处理能力的指标是每秒的<code>QPS</code>或者<code>TPS</code>,假设系统每秒的流量阈值是1000,理论上一秒内有第1001个请求进来时,那么这个请求就会被限流。</p>
<p><span style="color: #ff0000"><strong>二、限流方案</strong></span></p>
<p><strong>1、计数器</strong></p>
<p>Java内部也可以通过原子类计数器<code>AtomicInteger</code>、<code>Semaphore</code>信号量来做简单的限流。</p>
<div class="blockcode">
<pre class="brush:java;">
// 限流的个数
private int maxCount = 10;
// 指定的时间内
private long interval = 60;
// 原子类计数器
private AtomicInteger atomicInteger = new AtomicInteger(0);
// 起始时间
private long startTime = System.currentTimeMillis();
public boolean limit(int maxCount, int interval) {
atomicInteger.addAndGet(1);
if (atomicInteger.get() == 1) {
startTime = System.currentTimeMillis();
atomicInteger.addAndGet(1);
return true;
}
// 超过了间隔时间,直接重新开始计数
if (System.currentTimeMillis() - startTime > interval * 1000) {
startTime = System.currentTimeMillis();
atomicInteger.set(1);
return true;
}
// 还在间隔时间内,check有没有超过限流的个数
if (atomicInteger.get() > maxCount) {
return false;
}
return true;
}</pre>
</div>
<p><strong>2、漏桶算法</strong></p>
<p>漏桶算法思路很简单,我们把水比作是<code>请求</code>,漏桶比作是<code>系统处理能力极限</code>,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流。<br>
</p>
<p style="text-align: center"><img alt="" referrerpolicy="no-referrer" src="https://beijingoptbbs.oss-cn-hangzhou.aliyuncs.com/jb/2426819-049466247e057315a119a9c6a5d9ec52.jpg" title="在这里插入图片描述"></p>
<p><strong>3、令牌桶算法</strong></p>
<p>令牌桶算法的原理也比较简单,我们可以理解成医院的挂号看病,只有拿到号以后才可以进行诊病。</p>
<p>系统会维护一个令牌(<code>token</code>)桶,以一个恒定的速度往桶里放入令牌(<code>token</code>),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(<code>token</code>),当桶里没有令牌(<code>token</code>)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。<br>
</p>
<p style="text-align: center"><img alt="" referrerpolicy="no-referrer" src="https://beijingoptbbs.oss-cn-hangzhou.aliyuncs.com/jb/2426819-6572ac6337f2c0d843e4cd39b8fe27cc.jpg" title="在这里插入图片描述"></p>
<p><strong>4、Redis + Lua</strong></p>
<p>很多同学不知道<code>Lua</code>是啥?个人理解,<code>Lua</code>脚本和 <code>MySQL</code>数据库的存储过程比较相似,他们执行一组命令,所有命令的执行要么全部成功或者失败,以此达到原子性。也可以把<code>Lua</code>脚本理解为,一段具有业务逻辑的代码块。</p>
<p>而<code>Lua</code>本身就是一种编程语言,虽然<code>redis</code> 官方没有直接提供限流相应的<code>API</code>,但却支持了 <code>Lua</code> 脚本的功能,可以使用它实现复杂的令牌桶或漏桶算法,也是分布式系统中实现限流的主要方式之一。</p>
<p>相比<code>Redis</code>事务,<code>Lua脚本</code>的优点:</p>
<ul>
<li>减少网络开销: 使用Lua脚本,无需向Redis 发送多次请求,执行一次即可,减少网络传输</li>
<li>原子操作:Redis 将整个Lua脚本作为一个命令执行,原子,无需担心并发</li>
<li>复用:Lua脚本一旦执行,会永久保存 Redis 中,,其他客户端可复用</li>
</ul>
<p><code>Lua</code>脚本大致逻辑如下:</p>
<div class="blockcode">
<pre class="brush:plain;">
-- 获取调用脚本时传入的第一个key值(用作限流的 key)
local key = KEYS[1]
-- 获取调用脚本时传入的第一个 |
|