限流器的实践

news/2024/12/24 7:53:56/文章来源:https://www.cnblogs.com/leecoder5/p/18420799

背景

我们有一个业务场景是给学生发布考试,发布的过程不复杂,就是一个老师传递一些考试相关的参数过来,服务器自动给所有学生生成一份任务,但是在学生上交的时候会有个问题,就是成百上千的学生一起上交,会有并发流量的问题。

这里由于我们的考试可能会设计多个班级的联考,乃至一个学校或多个学校的联考,因为上交成绩单是一个比较集中的时间段,因此这里需要考虑的是服务器能承受的最大QPS以及学校的流量峰值。

这里比较特殊的是需要考虑学校的流量峰值,我们不希望因为考试把学校网络给整瘫痪了。

功能实现

鉴于上面两种情况,我首先想到的是限流,因此就有了第一种方案

方案一

采用阿里的Sentinel或者Guava的RateLimiter限流器,限流器的好处在于能对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。

这里不去研究限流采用的是滑动窗口法,还是漏桶算法或是令牌桶算法,因为这些都能达到我们的目的,而且框架已经封装的很好。

限流的大小如何去确定呢,我们可以根据实际情况计算得出,int maxQps=学校最大允许上传流量*某个百分比/每份作业大小(我们每份作业按50KB算),这里的百分比是控制流量的大小,可以设定为80%或90%。

客户端请求时,超过maxQps的请求就return false,然后客户端做延时重试上传。

下面是流程图:

现在看上去已经很完美了,把限流器一加,就已经达到了控制QPS的目的,但其实这里有个很大的问题,服务器的QPS是控制住了,但是学校的流量并没有控制住,学校每次都是把所有作业数据请求过来,然后才知道这次请求是true还是false的,这样还是会导致学校流量瘫痪。

因此就有了下面的第二种方案

方案二

在HTTP跨域请求中,在正式跨域之前,浏览器会根据需要发起一次预检(也就是option请求,这次请求是不带任何数据的),用来让服务端返回允许的方法(如get、post),被跨域访问的Origin(来源或者域),还有是否需要Credentials(认证信息)等。当然跨域的PreFlight也有个前提,就是用了自定义的请求头,不过这些都不是重点,这里我引用这个option请求知识点的目的在于,我们是否也可以运用这种思想。

第一步,客户端先发送一次类似option的PreFlight请求去获取token,这个token就是是否能进行第二次请求的令牌。

第二步,服务器接受到请求,根据限流器返回结果(true或false)。

第三步,客户端根据返回的结果,来决定是否进行真实的请求,如果是false则需要重试。

第四步,假设客户端已经获取到令牌,然后发送真实的请求。

流程图:

通过上面的操作,我们可以一直限制请求个数在maxQps范围内,因为永远最多只有maxQps的令牌可以通过申请,也就只有maxQps的客户端可以发送出真实请求,达到了控制学校流量的目的。

这样看上去依旧很完美,然而很不幸,这个方案还是有很大的问题:

假定我们设置的令牌桶(或者滑动窗口)大小为5,则必然会出现真实请求和PreFlight请求同时出现在桶里的情况,结果可能是一个桶里面的请求大部分都是这样PreFlight请求,就会导致真实请求数量达不到预期的结果,使QPS大大下降。

而且如果PreFlight请求和真实请求是同一个接口也不利于接口参数的判断。

造成这个结果的原因是因为真实请求和PreFlight请求同时出现在一个桶里,那我们把他们分开,于是就有了方案三。

方案三

在这里,我把PreFlight请求单独设立一个getToken接口,和上交考试的接口分开,然后PreFlight请求设立一个限流器,称作A限流器。

第一步,客户端调用getToken接口,获取令牌。

第二步,服务器根据限流器返回结果(true或false)。

第三步,客户端根据返回的结果,来决定是否进行真实的请求,如果是false则需要重试。

第四步,假设客户端已经获取到令牌,然后发送真实的请求。

这里的操作步骤和上面是一样的,无非就是把Token请求接口给单独分离,这样不会影响真实请求的发送,并且实现了限制客户端每秒能获取到的令牌数量,也就实现了限制真实请求的QPS。

(2)获取Token接口由于限流是针对一个学校的流量的,所以这里还得考虑多个学校联考时,各个学校Token的获取不能受干扰,这样就得给每个学校设置一个限流器。

我查阅了阿里的Sentinel根据接口限流的功能,Sentinel根据可以实现根据不同接口限流,但是需要给固定的接口设置限流规则,也就是这接口一开始就定义好的,如果是接口里有路径参数(如/v1/homework/token/{schoolId}),就会视为不同接口,也就需要配置不同的规则。所以这种方案是不符合我们的场景的,所以我自己摸索了一套方法用于给每个学校设定限流器。

大体思路就是每次调用Token请求时,我都会创建一个这个学校的规则,并且和已经存在的规则去做对比,如果已存在规则里有这个学校的规则,就不添加,不然就添加到规则里,并重载到FlowRuleManager。代码如下:

/**
* 限流器规则 
*/
private Set<FlowRule> rules = new ConcurrentSkipListSet<>((rule1, rule2) -> Objects.equals(rule1, rule2) ? 0 : 1);
/**
* 获取是否能上交考试作业的令牌 
* 
* @return 
*/
public Boolean getToken(Integer schoolId) {
String resourceName = ConstantUtil.Sentinel.EXAM_RULE_PREFIX + schoolId;
FlowRule rule = new FlowRule(resourceName);
rule.setCount(100);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//每次进来都创建规则,如果这个学校没在限流列表,那就加入规则列表,并重载规则if (rules.add(rule)) {FlowRuleManager.loadRules(Lists.newArrayList(rules));}    return SphO.entry(resourceName);
}

这里限流器规则rules需要用Set存储,防止重复(因为Sentinel规则是List的,不能去重,所以需要我们自己去重),Set我选择了ConcurrentSkipListSet来存储,效率较高,而且是线程安全的,不会有并发问题。

(3)接下来为了不影响QPS性能,我们在上交考试模块增加了一个RabbitMQ,能增加QPS的同时,也能减少数据库的压力,所以最后的逻辑图是这样的:

这里稍微提一下,RabbitMQ我会增加一个死信队列,用于保存推送失败时数据的保存,死信队列的数据我会保存到dead_exam_data表,当线上出现问题时,可以通过手动调用/v1/dead/exam接口,把死信数据提取出来,重新保存到HomeworkDetail作业表。

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

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

相关文章

易优cms 数据库连接失败,如何重新进行正确配置

当你遇到易优CMS(EyouCMS)数据库连接失败的问题时,可以按照以下步骤进行正确的配置和故障排除。 1. 检查数据库配置文件 易优CMS的数据库配置文件位于 application/database.php。你需要检查并修改该文件中的数据库配置信息。 2. 核对数据库账号和密码 确保数据库账号和密码…

在Windows10中使用rust的diesel库

介绍 最近在学习Actix Web时,需要用到数据库操作,简单尝试了一下diesel,也遇到了一些问题。在这里记录一下,供大家参考。 1.安装 根据Diesel官网介绍,使用cargo binstall安装diesel cli。 cargo binstall diesel_cli如果报错 error: no such command: `binstall` 需要先安…

易优eyoucms网站无法安装,数据库文件版本号(无)与CMS源码版本号(v1.3.1)不一致,点击查看!

解决方法比较简单, 可以找历史版本, 使用同版本数据库或者源码进行安装, 安装完成后操作升级。扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通PHP+MYSQL、HTML5、CSS3、Javascript等。承接:企业仿站、网站修改、网站改版、BUG修复、问题处…

易优eyoucms网站添加自定义新建字段的时候报错

根据提供的错误信息 SQLSTATE[42000]: Syntax error or access violation: 1118 Row size too large. The maximum row size for the used table type not counting BLOBs is 65535. You have to change some columns to TEXT or BLOBs,这个错误表明数据库表的行大小超过了 My…

易优eyoucms网站下载的系统导入就数据库出问题了,如何升级数据库?

数据库版本需要一样 数据库有新建模型的 需要打补丁包再导入扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通PHP+MYSQL、HTML5、CSS3、Javascript等。承接:企业仿站、网站修改、网站改版、BUG修复、问题处理、二次开发、PSD转HTML、网站被黑、…

易优eyoucms网站登录报错:Array and string offset access syntax with curly braces is deprecated

根据提供的错误信息 Array and string offset access syntax with curly braces is deprecated,这个错误提示表明当前使用的 PHP 版本不支持使用大括号 {} 来访问数组和字符串偏移量。这种语法在 PHP 7.4 之后被标记为已弃用。 以下是一些可能的解决步骤: 1. 切换 PHP 版本 尝…

易优eyoucms网站报错,\\core\\library\\think\\db\\Connection.php

报错 \\\\core\\\\library\\\\think\\\\db\\\\Connection.php 第 380 行左右 数据表或视图不存在,请联系技术处理。[错误代码] SQLSTATE[42S02]: Base table or view not found: 1146 Table eyoucms.ey_channeltype doesnt exist 根据提供的错误信息 SQLSTATE[42S02]: Base ta…

高等数学 3.3 泰勒公式

泰勒(Taylor)中值定理1 如果函数 \(f(x)\) 在 \(x_0\) 处具有 \(n\) 阶导数,那么存在 \(x_0\) 的一个邻域,对于该领域内的任一 \(x\) ,有 \[f(x) = f(x_0) + f^{}(x_0)(x - x_0) + \cfrac{f^{}(x_0)}{2!}(x - x_0)^2 + \cdots + \cfrac{f^{(n)}(x_0)}{n!}(x - x_0)^n + R_…

腾讯云TDSQL数据库认证值得考吗?来看看TDSQL证书有什么用

国内市场上的数据库产品有不少,很多大企业都有自己的数据库产品,比如金仓的KingBase、华为的OpenGauss、阿里云的PolarDB、达梦DM数据库等等,腾讯云也有自己的数据库产品,叫做TDSQL数据库,TDSQL数据库有两个分支:基于MySQL版 + 基于PostgreSQL版。腾讯云是国内知名的云平…

Maximum execution time of 30 seconds exceeded

遇到 Maximum execution time of 30 seconds exceeded 这个错误,通常是因为 PHP 脚本执行时间超过了设定的最大执行时间限制。这可能是由于脚本执行了耗时的操作,例如长时间的数据库查询或其他资源密集型任务。 以下是一些解决步骤: 1. 增加最大执行时间限制 可以在 PHP 配置…

易优eyoucms网站报错 \core\library\think\db\Connection.php 第 307 行左右,SQLSTATE[HY000] [1045]访问被拒,这样的情况要怎么处理啊

根据提供的错误信息 SQLSTATE[HY000] [1045] Access denied for user cs2021@localhost (using password: YES),这个错误表明数据库访问被拒绝了,通常是因为用户名或密码不正确导致的。 以下是几个可能的解决步骤:检查数据库连接配置:确认数据库连接配置文件中的用户名和密…

如何用Python将HTTP接口封装成可视化页面。

在软件行业中,经常会遇到有一些功能只能通过接口触发,没有页面。这样很不方便,。 我们这里,就是通过PyQt5实现,将接口的入参,封装成一个可视化的表单。将用户在表单中填写的数据,传给接口,接口再带参请求业务1.先看最终的效果,用户打开桌面应用后,只会出现下面的弹窗…