校园抢课助手【7】-抢课接口限流

news2024/9/24 13:18:28

在上一节中,该接口已经接受过风控的处理,过滤掉了机器人脚本请求,剩下都是人为的下单请求。为了防止用户短时间内高频率点击抢课链接,海量请求造成服务器过载,这里使用接口限流算法。

先介绍下几种常用的接口限流策略:
1.计数器算法(固定窗口)
计数器算法是使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。
此算法存在一个问题就是,在此周期快结束时,大量请求泳入请求,一直持续到下一周期开始一段时间后,这段时间的接口访问量大大超过服务器的负载,却小于每个周期的计数器最大值。
在这里插入图片描述

2.滑动窗口
滑动窗口算法是将时间周期分为N个小周期,分别记录每个小周期内访问次数,并且根据时间滑动删除过期的小周期。尽可能地平滑过渡每一个小周期。
在这里插入图片描述
3、漏桶算法
漏桶算法是访问请求到达时直接放入漏桶,如当前容量已达到上限(限流值),则进行丢弃(触发限流策略)。漏桶以固定的速率进行释放访问请求(即请求通过),直到漏桶为空。
4.令牌桶算法
令牌桶算法是程序以r(r=时间周期/限流值)的速度向令牌桶中增加令牌,直到令牌桶满,请求到达时向令牌桶请求令牌,如获取到令牌则通过请求,否则触发限流策略

本文常用简单有效的固定窗口策略进行接口限流,具体流程如下:
1.自定义接口限流注解

package com.example.seckilldemo.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {

    int second();

    int maxCount();

    boolean needLogin() default true;
}

2.将接口限流做成拦截器,写入WebConfig中在回掉方法中扫描到有限流注解的接口进行接口限流

package com.example.seckilldemo.config;

import com.example.seckilldemo.pojo.User;
import com.example.seckilldemo.service.UserService;
import com.example.seckilldemo.utils.CookieUtil;
import com.example.seckilldemo.vo.RespBean;
import com.example.seckilldemo.vo.RespBeanEnum;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

@Component
public class AccessLimitInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService itUserService;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            User tUser = getUser(request, response);
            UserContext.setUser(tUser);
            HandlerMethod hm = (HandlerMethod) handler;
            //判断有没有接口限流的注解
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if (accessLimit == null) {
                return true;
            }
            int second = accessLimit.second();
            int maxCount = accessLimit.maxCount();
            boolean needLogin = accessLimit.needLogin();

            String key = request.getRequestURI();
            if (needLogin) {
                if (tUser == null) {
                    render(response, RespBeanEnum.SESSION_ERROR);
                }
                key += ":" + tUser.getId();
            }

            //接口限流使用计数器算法
            ValueOperations valueOperations = redisTemplate.opsForValue();
            Integer count = (Integer) valueOperations.get(key);
            if (count == null) {
                valueOperations.set(key, 1, second, TimeUnit.SECONDS);
            } else if (count < maxCount) {
                valueOperations.increment(key);
            } else {
                render(response, RespBeanEnum.ACCESS_LIMIT_REACHED);
                return false;
            }
        }
        return true;
    }

    private void render(HttpServletResponse response, RespBeanEnum respBeanEnum) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter printWriter = response.getWriter();
        RespBean bean = RespBean.error(respBeanEnum);
        printWriter.write(new ObjectMapper().writeValueAsString(bean));
        printWriter.flush();
        printWriter.close();
    }

    private User getUser(HttpServletRequest request, HttpServletResponse response) {
        String userTicket = CookieUtil.getCookieValue(request, "userTicket");
        if (StringUtils.isEmpty(userTicket)) {
            return null;
        }
        return itUserService.getUserByCookie(userTicket, request, response);
    }
}

这里还有个问题是虽然自增是原子操作,但是获取计数器并不是,改进使用lua脚本配合计数器实现接口限流原子性操作

@Component
public class AccessLimitInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService itUserService;
    @Autowired
    private RedisTemplate redisTemplate;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            User tUser = getUser(request, response);
            UserContext.setUser(tUser);
            HandlerMethod hm = (HandlerMethod) handler;
            //判断有没有接口限流的注解
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);

            if (accessLimit != null) {
                int second = accessLimit.second();
                int maxCount = accessLimit.maxCount();
                boolean needLogin = accessLimit.needLogin();

                String key = request.getRequestURI();
                if (needLogin) {
                    if (tUser == null) {
                        render(response, RespBeanEnum.SESSION_ERROR);
                        return false;
                    }
                    key += ":" + tUser.getId();
                }

                // 使用Lua脚本确保操作的原子性
                String luaScript = "local currentCount = redis.call('get', KEYS[1]) " +
                        "if currentCount and tonumber(currentCount) < tonumber(ARGV[1]) then " +
                        "  redis.call('incr', KEYS[1]) " +
                        "  if tonumber(currentCount) == 0 then " +
                        "    redis.call('expire', KEYS[1], ARGV[2]) " +
                        "  end " +
                        "  return 0 " +
                        "end " +
                        "return 1";

                DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(luaScript, Boolean.class);
                Boolean isLimited = (Boolean) redisTemplate.execute(redisScript, Collections.singletonList(key), maxCount, second);

                if (isLimited) {
                    render(response, RespBeanEnum.ACCESS_LIMIT_REACHED);
                    return false;
                }
            }
        }
            return true;
    }

    private void render(HttpServletResponse response, RespBeanEnum respBeanEnum) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter printWriter = response.getWriter();
        RespBean bean = RespBean.error(respBeanEnum);
        printWriter.write(new ObjectMapper().writeValueAsString(bean));
        printWriter.flush();
        printWriter.close();
    }

    private User getUser(HttpServletRequest request, HttpServletResponse response){
        String userTicket = CookieUtil.getCookieValue(request, "userTicket");
        if (StringUtils.isEmpty(userTicket)) {
            return null;
        }
        return itUserService.getUserByCookie(userTicket, request, response);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

脚拉脚模型笔记

脚拉脚模型 ⌈♪⌋例题&#xff1a; 辅助线&#xff08;中点&#xff09;做法&#xff1a; 倍长中线Rt △ △ △ 斜边中线等腰 △ △ △ 三线合一中位线 需要&#xff1a;两个等腰三角形&#xff0c;顶角互补 共__底点__ 底角需要连接 解&#xff1a; ∵ D Q 1 / 2 A B O…

中国人工智能最好50所大学排名-2024年最强学校名单

人工智能最强的学校包含&#xff1a;清华大学、上海交通大学、南京大学、西安电子科技大学、电子科技大学、中国科学技术大学、哈尔滨工业大学、华中科技大学、东南大学、浙江大学等学校。这些都是人工智能专业排名全国前十的名牌大学。 圆梦小灯塔将在下文继续为2024年高考生…

鸿蒙应用开发 DevEcoStudio 汉化

步骤 DevEcoStudio 是默认支持中文的&#xff0c;只是默认是关闭的&#xff0c;需要在已安装的插件中搜索 Chinese 关键字&#xff0c;然后启用并重启即可&#xff08;注意&#xff1a;是在已安装的插件中搜索&#xff09;。 1. 2. 3. 重启就行

滚珠花键:新能源汽车传动系统的核心动力传递者

在日常生活中&#xff0c;汽车已经成为了必不可少的交通工具&#xff0c;尤其是新能源汽车。而滚珠花键作为传动系统中的重要组成部分&#xff0c;在传动系统方面的作用不容忽视。 随着科技的不断发展&#xff0c;汽车行业也在不断进步&#xff0c;滚珠花键作为高精度的机械传动…

PE安装win11原版系统“无法创建新的分区,也找不到现有的分区”和“windows无法对计算机进行启动到下一个安装阶段”的解决办法

问题1 针对“无法创建新的分区&#xff0c;也找不到现有的分区”&#xff1a; 解决办法&#xff1a; 用Diskgenius等分区工具删除整个分区&#xff0c;不要在分区工具里新建分区&#xff0c;而是在安装系统选择安装磁盘的时候&#xff0c;直接选择这个磁盘&#xff0c;从而完成…

五. TensorRT API的基本使用-build-model-from-scratch

目录 前言0. 简述1. 案例运行2. 代码分析2.1 main.cpp2.2 model.cpp 3. 案例3.1 sample_conv3.2 sample_permute3.3 sample_reshape3.4 sample_batchNorm3.5 sample_cbr 4. 补充说明总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。…

《学会 SpringMVC 系列 · 写入拦截器 ResponseBodyAdvice》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

3.4数组和特殊矩阵

3.4.1数组的定义 数组是由n个相同类型的数据元素构成的有序序列 数组是线性表的推广,一个数组可以视为一个线性表 数组一旦被定义,其长度不会再改变,所以数组只会有存取元素和修改元素的操作 3.4.2数组的存储结构 多维数组 有两种映射方法:按行优先和按列优先 按行优先 …

2024 年最值得阅读的 10 个外国技术网站

从网络上数以千计的博客中挑选出最好的技术网站&#xff0c;并根据相关性、权威性、社交媒体关注者和新鲜度进行排名。 1. TechCrunch TechCrunch 是一家领先的科技媒体&#xff0c;致力于深入分析初创公司、评论新的互联网产品和发布科技新闻。该网站是科技专业人士和爱好者…

【传知代码】实体关系抽取(论文复现)

当谈论信息提取领域的最前沿时&#xff0c;实体关系抽取无疑是其中一颗耀眼的明星。从大数据时代的信息海洋中提炼出有意义的关系&#xff0c;不仅是科技进步的体现&#xff0c;更是人类对知识管理和智能决策迫切需求的响应。本文将探索实体关系抽取的核心技术、应用场景及其在…

域控搭建(windows 2012 R2和win10)

域控搭建 环境准备 两台windows虚拟机 主域控为&#xff1a;windows server2012 子域为&#xff1a;win10 虚拟机设置网段 Win10网络设置 Windows server2012网络设置 Windows server2012网络适配器 设置 识别成功 更改计算机名字 等待重启 Win10网络适配器 设置 识别成功 …

opencv-图像透视变换

透射变换是视角变化的结果&#xff0c;是指利用透视中心&#xff0c;像点&#xff0c;目标点共线的条件&#xff0c;按透视旋转定律使承影面(透视面)绕迹线(透视轴旋转某一角度&#xff0c;破坏原有的投影光束&#xff0c;仍能保持承影面上投影几何图形不变的变化) 它的本质将图…

QT实现步进电机控制和IMU数据读取显示

实现功能&#xff1a; 1.两步进电机分别使能和循环运动&#xff0c;可以设置循环次数、循环里分别运行的角度、旋转的速度和加减速度等等&#xff0c;在最下方的表格里显示发送和接收的CAN报文 2.读取水平电机当前位置和速度并画图显示&#xff0c;示波器暂停、缩放、滑动等功…

CVPR24《Neural Markov Random Field for Stereo Matching》

论文地址&#xff1a; https://arxiv.org/abs/2403.11193 源码地址&#xff1a; https://github.com/aeolusguan/NMRF 概述 手工设计的MRF模型在传统的立体匹配中占据主导地位&#xff0c;但与端到端的深度学习模型相比&#xff0c;其建模准确性不足。尽管深度学习大大改进了MR…

力扣SQL50 修复表中的名字 字符串函数

Problem: 1667. 修复表中的名字 &#x1f468;‍&#x1f3eb; 参考题解 select user_id, CONCAT(UPPER(left(name, 1)), LOWER(RIGHT(name, length(name) - 1))) as name from Users order by user_id

SQL注入实例(sqli-labs/less-2)

0、初始网页 1、闭合方式判断 当没有闭合符号进行注释时&#xff0c;网页并没有报错&#xff0c;所以可以确定无闭合符号&#xff0c;为数值型注入 2、确定查询表的列数 可以确定列数小于4 ?id1 order by 4 -- 确定查询表的列数为3列 ?id1 order by 3 -- 3、确定回显位置…

MySQL系列之--关系型数据库以及SQL语句分类之DDL数据库和表的操作

文章目录 前言关系型数据库&#xff08;RDBMS&#xff09;关系型数据库的特点 MySQL数据模型SQL介绍基本语法规则SQL语句的分类DDL的介绍DDL的数据库操作DDL的表操作 前言 上一节MySQL系列之–详细安装教程和启动方法中介绍了MySQL如何安装&#xff0c;以及如何启动和客户端连接…

c++| c++11左右值引用,完美转发,可变参数模板,functional包装器,bind函数

c| c11的新特性 左&#xff0c;右值引用什么是左值&#xff0c;右值左值引用和右值引用右值引用解决什么问题呢&#xff1f;移动构造万能引用形式 完美转发格式 lambada表达式格式 可变参数模板可变参数模板实现打印不同类型emplace_push以list的emplace_back的实现举例包装器b…

新160个crackme - 020-cosh.3

运行分析 老规矩&#xff0c;需要破解Name和Serial PE分析 c程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 ida查找关键字符串 分析关键函数&#xff0c;得出以下结论&#xff1a;Name、Serial每一位进行亦或计算&#xff0c;若计算结果相等则弹窗成功 算法分析…

吴恩达机器学习作业-ex7(主成分分析)

data1 导入库&#xff0c;读取数据&#xff0c;并进行可视化数据 import numpy as np import scipy.io as sio import matplotlib.pyplot as plt#读取数据 path "./ex7data1.mat" data sio.loadmat(path) # print(data.keys()) X data.get("X") # pri…