高并发接口限流

文章目录

    • 简介
    • 实现限流常见的算法4种
      • 1、计数器限流算法
      • 2、滑动窗口限流算法
      • 3、漏桶限流算法
      • 4.令牌桶限流算法
    • 接口限流方案
    • 限流算法对比、网关限流实践总结
    • Redis实现限流的几种方式
      • 基于Redis的setNX的操作(固定时间算法)
      • 基于Redis的数据结构zset(滑动窗口)
      • 基于Redis的令牌桶算法

简介

所谓限流,就是指限制流量请求的频次。它主要是在高并发情况下,用于保护系统的一种策略,主要是避免在流量高峰导致系统崩溃,造成系统不可用的问题。
在设计接口限流方案时,可以考虑以下几种常见的限流策略:

  1. 固定窗口计数器:该策略将时间划分为固定大小的窗口,在每个窗口内限制请求的数量。例如,每秒钟最多允许处理10个请求。如果超过了限制数量,则拒绝后续请求。这种方法简单直观,但可能会因为窗口切换瞬间的流量峰值而导致不均匀的限流效果。
  2. 滑动窗口计数器:与固定窗口计数器类似,滑动窗口计数器也将时间划分为窗口,但窗口之间存在重叠。例如,每秒钟最多允许处理10个请求,但可以在前一秒的计数器中减去已过期的请求数量。这样可以更平滑地限制请求,减少流量峰值对服务的影响。
  3. 令牌桶算法:令牌桶算法通过维护一个固定容量的令牌桶,以固定速率向其中添加令牌。每次请求需要消耗一个令牌,只有当令牌桶中有足够的令牌时才能处理请求,否则请求被拒绝。这种算法能够平滑地限制请求,但可能会因为突发请求导致桶中令牌不足。
  4. 漏桶算法:漏桶算法将请求处理看作是一个水桶中的水流出的过程。固定的请求速率相当于固定的水流速度,而请求则相当于水滴。如果水桶已满,则多余的请求(水滴)会被丢弃,从而实现限流效果。这种算法能够稳定地限制请求速率,但可能会因为突发请求导致响应时间增加。
  5. 基于权重的限流:基于权重的限流方法根据不同的请求类型或用户进行区分,并为不同类型或用户设置不同的请求限制。例如,对于高优先级的请求可以设置较高的限制,而对于低优先级的请求可以设置较低的限制。这种方法可以根据具体场景进行细粒度的限流控制。
    选择合适的限流方案需要根据具体业务需求和性能要求来确定,通常需要综合考虑系统的稳定性、性能开销和用户体验等因素。

实现限流常见的算法4种

所谓限流,就是指限制流量请求的频次。它主要是在高并发情况下,用于保护系统的一种策略,主要是避免在流量高峰导致系统崩溃,造成系统不可用的问题。
实现限流常见的算法4种,分别是计数器限流算法、滑动窗口限流算法、漏桶限流算法、令牌桶限流算法。

1、计数器限流算法

一般用在单一维度的访问频率限制上,比如短信验证码每隔 60s只能发送一次,或者接口调用
次数等。它的实现方法很简单,就是每调用一次就加 1,处理结束以后减1。
统计一段时间内允许通过的请求数。比如 qps为100,即1s内允许通过的请求数100,每来一个请求计数器加1,超过100的请求拒绝、时间过1s后计数器清0,重新计数。这样限流比较暴力,如果前10ms 来了100个请求,那剩下的990ms只能眼睁睁看着请求被过滤掉,并不能平滑处理这些请求,容易出现常说的“突刺现象”。
在这里插入图片描述

2、滑动窗口限流算法

本质上也是一种计数器,只是通过以时间为维度的可滑动窗口设计,来减少了临界值带来的并
发超过阈值的问题。每次进行数据统计的时候,只需要统计这个窗口内每个时间刻度的访问量就可
以了。Spring Cloud 中的熔断框架 Hystrix,以及 Spring Cloud Alibaba 中的Sentinel 都采用滑动
窗口来做数据统计。
在这里插入图片描述

3、漏桶限流算法

它是一种恒定速率的限流算法,不管请求量是多少,服务端的处理效率是恒定的。基于 MQ 来实现
的生产者消费者模型,其实算是一种漏桶限流算法
在这里插入图片描述

4.令牌桶限流算法

相对漏桶算法来说,它可以处理突发流量的问题。它的核心思想是,令牌桶以恒定速率去生成令牌保存到令牌桶里面,桶的大小是固定的,令牌桶满了以后就不再生成令牌。每个客户端请求进来的时候,必须要从令牌桶获得一个令牌才能访问,否则排队等待。在流量低峰的时候,令牌桶会出现堆积,因此当出现瞬时高峰的时候,有足够多的令牌可以获取,因此令牌桶能够允许瞬时流量的处理。网关层面的限流、或者接口调用的限流,都可以使用令牌桶算法,像 Google 的Guava,和Redisson 的限流,都用到了令牌桶算法。我认为,限流的本质是实现系统保护,最终选择什么样的算法,一方面取决于统计的精准度,另一方面考虑限流维度和场景的需求。
假设有个桶,并且会以一定速率往桶中投放令牌,每次请求来时都要去桶中拿令牌,如果拿到则放行,拿不到则进行等待直至拿到令牌为止,比如以每秒100的速度往桶中投放令牌,令牌桶初始化一秒过后桶内有100个令牌,如果大量请求来时会立即消耗完100个令牌,其余请求进行等待,最终以匀速方式放行这些请求。此算法的好处在于既能应对短暂瞬时流量,又可以平滑处理请求。

在这里插入图片描述

接口限流方案

限制总并发数(比如数据库连接池、线程池)
限制瞬时并发数(如 nginx 的 limit_conn模块,⽤来限制 瞬时并发连接数)
限制时间窗口内的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req 模块,限制每秒的平均速率)
限制远程接口调用速率
限制MQ 的消费速率
可以根据网络络连接数、⽹络流量、CPU或内存负载等来限流

限流策略:
Nginx接入层限流
按照⼀定的规则如帐号、IP、系统调用逻辑等在Nginx层面做限流
业务应用系统限流
通过业务代码控制流量这个流量可以被称为信号量,可以理解成是⼀种锁,它可以限制⼀项资源最多能同时被多少进程访问。
2、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 0else --请求数+1,并设置2秒过期
redis.call("INCRBY", key,"1")
redis.call("expire", key,"2")
endreturn 1

减少⽹络开销: 不使⽤ Lua 的代码需要向 Redis 发送多次请求, ⽽脚本只需⼀次即可, 减少⽹络传输;
原⼦操作: Redis 将整个脚本作为⼀个原⼦执⾏, ⽆需担⼼并发, 也就⽆需事务;
复⽤: 脚本会永久保存 Redis 中, 其他客户端可继续使⽤.

2、ip限流lua脚本:

local key = "rate.limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]local is_exists = redis.call("EXISTS", key)if is_exists == 1 then
if redis.call("INCR", key) > limit then
return 0
else
return 1
endelse
redis.call("SET", key, 1)
redis.call("EXPIRE", key, expire_time)
return 1
endimport org.apache.commons.io.FileUtils;import redis.clients.jedis.Jedis;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class RedisLimitRateWithLUA {
public static void main(String[] args) {final CountDownLatch latch = new CountDownLatch(1);
for (int i = 0; i < 7; i++) {
new Thread(new Runnable() {
public void run() {
try {latch.await();System.out.println("请求是否被执⾏:"+accquire());
} catch (Exception e) {e.printStackTrace();
}
}
}).start();
}latch.countDown();
}
public static boolean accquire() throws IOException, URISyntaxException {Jedis jedis = new Jedis("127.0.0.1");File luaFile = new File(RedisLimitRateWithLUA.class.getResource("/").toURI().getPath() + "limit.lua"String luaScript = FileUtils.readFileToString(luaFile);String key = "ip:" + System.currentTimeMillis()/1000; // 当前秒String limit = "5"; // 最⼤限制List<String> keys = new ArrayList<String>();keys.add(key);List<String> args = new ArrayList<String>();args.add(limit);Long result = (Long)(jedis.eval(luaScript, keys, args)); // 执⾏lua脚本,传⼊参数
return result == 1;
}

限流算法对比、网关限流实践总结

Guava RateLimiter实现平滑限流
Guava RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流

2.1 spirng cloud gateway网关限流原理解析
** 核心限流类:org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter

2.2 spring-cloud-zuul网关限流zuul-ratelimit原理分析
zuul-ratelimt 支持memory、redis限流,通过计数器算法实现限流,即在窗口时间内消耗指定数量的令牌后限流,窗口时间刷新后重新指定消耗令牌数量为0。

2.3 Guava RateLimiter实现平滑限流
Guava RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现,代码实现时序图如下:

在这里插入图片描述

Redis实现限流的几种方式

互联网应用往往是高并发的场景,互联网的特性就是瞬时、激增,比如鹿晗官宣了,此时,如果没有流量管控,很容易导致系统雪崩。
而限流是用来保证系统稳定性的常用手段,当系统遭遇瞬时流量激增,很可能会因系统资源耗尽导致宕机,限流可以把一超出系统承受能力外的流量直接拒绝掉,保证大部分流量可以正常访问,从而保证系统只接收承受范围以内的请求。
我们常用的限流算法有:漏桶算法、令牌桶算法。

基于Redis的setNX的操作(固定时间算法)

我们在使用Redis的分布式锁的时候,大家都知道是依靠了setNX的指令,在CAS(Compare and swap)的操作的时候,同时给指定的key设置了过期实践(expire),我们在限流的主要目的就是为了在单位时间内,有且仅有N数量的请求能够访问我的代码程序。所以依靠setnx可以很轻松的做到这方面的功能。
比如我们需要在10秒内限定20个请求,那么我们在setnx的时候可以设置过期时间10,当请求的setnx数量达到20时候即达到了限流效果。代码比较简单就不做展示了。
当然这种做法的弊端是很多的,比如当统计1-10秒的时候,无法统计2-11秒之内,如果需要统计N秒内的M个请求,那么我们的Redis中需要保持N个key等等问题。

// 限流的个数
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;
}

基于Redis的数据结构zset(滑动窗口)

上面的计数器算法存在的弊端,使用滑动窗口可以很容易的实现。上面提到的1-10怎么变成2-11,其实可以理解为窗口大小不变,变化的是窗口的启始结束位置,而我们如果用Redis的list数据结构可以轻而易举的实现该功能。
我们可以将请求打造成一个zset数组,当每一次请求进来的时候,value保持唯一,可以用UUID生成,而score可以用当前时间戳表示,因为score我们可以用来计算当前时间戳之内有多少的请求数量。而zset数据结构也提供了range方法让我们可以很轻易的获取到2个时间戳内有多少请求
代码实现也比较简单,

public Response limitFlow(){Long currentTime = new Date().getTime();System.out.println(currentTime);if(redisTemplate.hasKey("limit")) {Integer count = redisTemplate.opsForZSet().rangeByScore("limit", currentTime -  intervalTime, currentTime).size();        // intervalTime是限流的时间 System.out.println(count);if (count != null && count > 5) {return Response.ok("每分钟最多只能访问5次");}}redisTemplate.opsForZSet().add("limit",UUID.randomUUID().toString(),currentTime);return Response.ok("访问成功");
}

基于Redis的令牌桶算法

提到限流就不得不提到令牌桶算法了。令牌桶算法提及到输入速率和输出速率,当输出速率大于输入速率,那么就是超出流量限制了。
也就是说我们每访问一次请求的时候,可以从Redis中获取一个令牌,如果拿到令牌了,那就说明没超出限制,而如果拿不到,则结果相反。可以理解成医院的挂号看病,只有拿到号以后才可以进行诊病。依靠上述的思想,我们可以结合Redis的List数据结构很轻易的做到这样的代码,只是简单实现。另外,关注互联网架构师,在后台回复:2T,可以获取我整理的 Redis 系列面试题和答案,非常齐全。 依靠List的leftPop来获取令牌

public Response limitFlow(Long id){Object result = redisTemplate.opsForList().leftPop("limit_list");if(result == null){return Response.ok("当前令牌桶中无令牌");}return Response.ok("访问成功"); }
再依靠Java的定时任务,定时往List中rightPush令牌,当然令牌也需要唯一性,所以我这里还是用UUID进行了生成.一旦需要提高速率,则按需提高放入桶中的令牌的速率即可。
@Scheduled(fixedDelay = 100,initialDelay = 0)
public void setIntervalTimeTask(){redisTemplate.opsForList().rightPush("limit_list",UUID.randomUUID().toString());
}

令牌桶算法提及到输入速率和输出速率,当输出速率大于输入速率,那么就是超出流量限制了。
也就是说我们每访问一次请求的时候,可以从Redis中获取一个令牌,如果拿到令牌了,那就说明没超出限制,而如果拿不到,则结果相反。
依靠上述的思想,我们可以结合Redis的List数据结构很轻易的做到这样的代码,只是简单实现依靠List的leftPop方法来获取令牌。

static void LimitRequest(){RedisClient client = new RedisClient("1633com@192.168.1.128:6379");string key = "limitRate";var result=  client.PopItemFromList(key);if(result==null){Console.WriteLine("系统繁忙,请稍后再试");}else{Console.WriteLine("访问成功");}}

再依靠定时任务,定时往令牌桶List中加入新的令牌(使用List的rightPush方法),当然令牌也需要唯一性,这里还是用UUID生成令牌: 10S的速率往令牌桶中添加UUID,保证唯一性
/// 比如我们速率限制是1分钟100个,那么就处理为1分钟内,桶中就只有100个令牌。

/// </summary>
static void AddTokenToBucket()
{string key = "limitRate";RedisClient client = new RedisClient("1633com@192.168.1.128:6379");var count = client.GetListCount(key);for(var i=0;i<100-count;i++) //需要判断原来是否还有剩余,有则相应扣减,确保桶中只有100个令牌{client.AddItemToList(key, Guid.NewGuid().ToString());}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/299436.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

家校互通小程序实战开发01需求分析

目录 1 角色的划分2 用例分析3 创建业务数据源4 创建登录用户数据源总结 最近几年&#xff0c;随着移动互联网的深入发展&#xff0c;我们的日常生活和工作和微信已经紧密绑定。其实&#xff0c;有时候生活和工作的界限已经不明显&#xff0c;在我们的微信好友里既有家人、朋友…

软件测试必问的33个面试题

1.你为什么选择软件测试行业 因为之前有了解软件测试这个行业&#xff0c;觉得他的发展前景很好。 2.根据你以前的工作经验描述一下软件开发、测试过程&#xff0c;由那些角色负责&#xff0c;你做什么 要有架构师、开发经理、测试经理、程序员、测试员。我在里面主要是负责所…

vue3 全局配置Axios实例

目录 前言 配置Axios实例 页面使用 总结 前言 Axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;用于浏览器和 Node.js 环境。它提供了一种简单、一致的 API 来处理HTTP请求&#xff0c;支持请求和响应的拦截、转换、取消请求等功能。关于它的作用&#xff1a; 发起 HTTP …

国内厉害的游戏开发公司有哪些?

中懿游游戏软件开发,中国有许多厉害的游戏开发公司&#xff0c;其中一些在国际上也享有盛誉。以下是一些在中国游戏开发领域中备受关注的公司&#xff1a; 腾讯游戏&#xff08;Tencent Games&#xff09;&#xff1a; 作为中国最大的互联网公司之一&#xff0c;腾讯的游戏分支…

dpdk原理概述及核心源码剖析

dpdk原理 1、操作系统、计算机网络诞生已经几十年了&#xff0c;部分功能不再能满足现在的业务需求。如果对操作系统做更改&#xff0c;成本非常高&#xff0c;所以部分问题是在应用层想办法解决的&#xff0c;比如前面介绍的协程、quic等&#xff0c;都是在应用层重新开发的框…

MAC电脑清理利器CleanMyMacX4.14.6新功能介绍及下载步骤教程

CleanMyMac X拥有可让您的 Mac 保持最佳性能的所有工具。只需点按一下&#xff0c;即可释放硬盘空间&#xff0c;为您的 Mac 提速并让其远离恶意软件。 CleanMyMac x 4.14.6Mac是一款最专业最强大的Macosx系统清理优化工具&#xff0c;CleanMyMac是集所有功能于一身的先进程序…

气动冷凝水回收泵机械浮球泵的特点工作原理介绍 不需要电源

​ 1&#xff1a;气动凝水回收泵机械式介绍 气动冷凝水回收泵是一种设计用于不使用电力来泵送冷凝液、油和其他高温液体等的设备。它无需维护&#xff0c;能将大量凝结水和其它液体从低位、低压或真空场所泵送到高处及高压区域。与传统电泵相比&#xff0c;气动冷凝水回收泵可…

Keil5 5.38官方下载、安装及注册教程(详细版)

一、下载地址 官方C51版本下载地址&#xff1a;https://www.keil.com/demo/eval/c51.htm 官方ARM版本下载地址&#xff1a;https://www.keil.com/demo/eval/arm.htm 注&#xff1a;两个版本的安装教程一样 Keil注册机2032年&#xff1a; 链接&#xff1a;https://pan.baidu.…

光伏制氢:技术前沿与未来展望

光伏制氢&#xff1a;技术前沿与未来展望 一、引言 随着全球对可再生能源需求的日益增长&#xff0c;光伏制氢技术作为一种将太阳能转化为氢能的有效方式&#xff0c;正逐渐受到人们的关注。通过光伏发电和电解水相结合&#xff0c;我们可以将丰富的太阳能资源转化为清洁的氢…

构建自己的拦截器:深入理解MyBatis的拦截机制

Mybatis拦截器系列文章&#xff1a;从零开始的 MyBatis 拦截器之旅&#xff1a;实战经验分享 构建自己的拦截器&#xff1a;深入理解MyBatis的拦截机制 文章目录 前言拦截器声明注册-解析-添加拦截器注册拦截器解析-添加拦截器 拦截器执行及原理--如何起作用的为什么只能对4种组…

Python字符串处理全攻略(六):常用内置方法轻松掌握

文章目录 引言相关链接常用内置方法str.format_map()功能介绍语法示例注意事项 str.isidentifier()功能介绍语法示例注意事项总结 str.ljust()功能介绍语法注意事项总结 str.rjust()功能介绍语法示例注意事项 结束语 引言 欢迎来到Python的世界&#xff01;字符串是Python中最…

自动驾驶中的“雷达”

自动驾驶中有好几种雷达&#xff0c;新手可能会蒙蔽&#xff0c;这里统一介绍一下它们。 首先&#xff0c;所有雷达的原理都是发射波&#xff0c;接收回波&#xff0c;并通过发射和接收的时间差以及波的速度计算距离。只不过发射的波不同&#xff0c;功能也不同。 激光雷达 …