SpringBoot实现限流注解

news2024/11/27 4:38:38

SpringBoot实现限流注解

在高并发系统中,保护系统的三种方式分别为:缓存,降级和限流。

限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待

1、限流类型枚举类

/**
 * 限流类型
 * @author ss_419
 */

public enum LimitType {
    /**
     * 默认的限流策略,针对某一个接口进行限流
     */
    DEFAULT,
    /**
     * 针对某一个IP进行限流
     */
    IP

}

2、自定义限流注解

/**
 * @author ss_419
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimiter {
    /**
     * 限流的 key,主要是指前缀
     * @return
     */
    String key() default "rate_limit:";

    /**
     * 在时间窗内的限流次数
     * @return
     */
    int count() default 100;

    /**
     * 限流类型
     * @return
     */
    LimitType limitType() default LimitType.DEFAULT;
    /**
     * 限流时间窗
     * @return
     */
    int time() default 60;
}

3、限流lua脚本

1、由于我们使用 Redis 进行限流,我们需要引入 Redis 的 maven 依赖,同时需要引入 aop 的依赖

<!-- aop依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- redis依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置redis以及lua脚本

@Configuration
public class RedisConfig {

    @Bean
    RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        template.setKeySerializer(jackson2JsonRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        return template;
    }

    /**
     * 读取lua脚本
     * @return
     */
    @Bean
    DefaultRedisScript<Long> limitScript() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));
        return script;
    }
}

通过 Lua 脚本,根据 Redis 中缓存的键值判断限流时间(也是 key 的过期时间)内,访问次数是否超出了限流次数,没超出则访问次数 +1,返回 true,超出了则返回 false。
limit.lua:

local key = KEYS[1]
local time = tonumber(ARGV[1])
local count = tonumber(ARGV[2])
local current = redis.call('get', key)
if current and tonumber(current) > count then
    return tonumber(current)
end
current = redis.call('incr', key)
if tonumber(current) == 1 then
    redis.call('expire', key, time)
end
return tonumber(current)

4、限流切面处理类

1、使用我们刚刚的 Lua 脚本判断是否超出了限流次数,超出了限流次数后返回一个自定义异常,然后在全局异常中去捕捉异常,返回 JSON 数据。

2、根据注解参数,判断限流类型,拼接缓存 key 值

package org.pp.ratelimiter.aspectj;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.pp.ratelimiter.annotation.RateLimiter;
import org.pp.ratelimiter.enums.LimitType;
import org.pp.ratelimiter.exception.RateLimitException;
import org.pp.ratelimiter.utils.IpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Collections;


@Aspect
@Component
public class RateLimiterAspect {

    private static final Logger logger = LoggerFactory.getLogger(RateLimiterAspect.class);

    @Autowired
    RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    RedisScript<Long> redisScript;


    @Before("@annotation(rateLimiter)")
    public void before(JoinPoint jp, RateLimiter rateLimiter) throws RateLimitException {
        int time = rateLimiter.time();
        int count = rateLimiter.count();
        String combineKey = getCombineKey(rateLimiter, jp);
        try {
            Long number = redisTemplate.execute(redisScript, Collections.singletonList(combineKey), time, count);
            if (number == null || number.intValue() > count) {
                //超过限流阈值
                logger.info("当前接口以达到最大限流次数");
                throw new RateLimitException("访问过于频繁,请稍后访问");
            }
            logger.info("一个时间窗内请求次数:{},当前请求次数:{},缓存的 key 为 {}", count, number, combineKey);
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * 这个 key 其实就是接口调用次数缓存在 redis 的 key
     * rate_limit:11.11.11.11-org.javaboy.ratelimit.controller.HelloController-hello
     * rate_limit:org.javaboy.ratelimit.controller.HelloController-hello
     * @param rateLimiter
     * @param jp
     * @return
     */
    private String getCombineKey(RateLimiter rateLimiter, JoinPoint jp) {
        StringBuffer key = new StringBuffer(rateLimiter.key());
        if (rateLimiter.limitType() == LimitType.IP) {
            key.append(IpUtils.getIpAddr(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()))
                    .append("-");
        }
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();
        key.append(method.getDeclaringClass().getName())
                .append("-")
                .append(method.getName());
        return key.toString();
    }
}

5、使用与测试

@RestController
public class HelloController {

    /**
     * 限流 10 秒之内,这个接口可以访问3次
     * @return
     */
    @GetMapping("/hello")
    @RateLimiter(time = 10,count = 3)
    public Map<String, Object> hello() {
        Map<String, Object> map = new HashMap<>();
        map.put("status", 200);
        map.put("message", "Hello RateLimiter");
        return map;
    }

}

十秒之内访问次数超过3次就会报异常
image

redis中的数据,每一次访问都加1
image
当访问次数超过3,则进行限流操作
image

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

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

相关文章

MySQL---存储引擎

1. 存储引擎 数据库存储引擎是数据库底层软件组织&#xff0c;数据库管理系统使用数据引擎进行创建、查询、更新和删 除数据。 不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能。不同的数据库管理系统都支持 多种不同的数据引擎。MySQL的核心就是存储引擎。 …

IDEA创建项目的操作步骤以及在虚拟机里面创建Scala的项目简单介绍

文章目录 前言 一、Idea创建项目的操作 1、选择本地的Scala插件包 2、创建项目的操作 3、输入新建的项目名称 4、在文件file里面选择new一个新的module文件 5、输入项目的名称&#xff0c;点击下一步 6、创建一个Scala文件夹 7、选择Mark directory as 8、单击文件里…

【5.21】六、自动化测试—概述

目录 6.1 自动化测试概述 6.1.1 什么是自动化测试 6.1.2 自动化测试的基本流程 6.1.3 自动化测试实施策略 6.1.4 自动化测试的优势和劣势 测试中的许多操作都是重复性的、非智力性的和非创造性的&#xff0c;但要求工作准确细致&#xff0c;此时计算机最适合代替人工去完成…

SpringBoot2.6.3集成ElasticSearch7.13.4详解,上下两篇,上篇集群配置,下篇集成配置(上)

一、集群配置准备 &#xff08;1&#xff09;方式一&#xff0c;从网盘下载ElasticSearch7.13.4 链接: https://pan.baidu.com/s/1vwUu1kbpCc5exkfOPgb29g 提取码: thn5 &#xff08;2&#xff09;方式二&#xff0c;从官网下载 https://www.elastic.co/cn/downloads/past-…

【C++学习】C++11——lambda表达式 | 可变参数模板 | 包装器

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; lambda表达式 | 可变参数模板 | 包装器 &#x1f3c0;lambda表达式&#x1f94e;lambda表达式语法&am…

汇编十一、汇编实现外部中断

1、实现目的 (1)实现8颗LED灯呈流水灯依次被点亮&#xff1b;静态数码管通过按键按下&#xff0c;显示数值发生改变&#xff0c;通过按键依次显示0-9。 (2)按键检测采用外部中断检测。 2、原理图及硬件连接 2.1、LED灯 (1)51单片机P1端口接八个共阴极LED灯&#xff0c;即I…

【python之django1.11框架二】django ORM 操作MySQL的基本操作

1. django 连接数据库(MySQL) # setting.py 文件 # 默认用的是sqkite3 DATABASES {default: {ENGINE: django.db.backends.sqlite3,NAME: os.path.join(BASE_DIR, db.sqlite3),} }# django链接MySQL 1.第一步配置文件中配置 DATABASES {default: {ENGINE: django.db.backend…

RocketMQ 消息发送、消息类别

一、消息发送 1.1 单生产者单消费者消息发送&#xff08;OneToOne&#xff09; 1、新建maven项目recketmqtest 2、导入RocketMQ客户端坐标 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-client</artifactId><…

chatgpt赋能Python-pythonbug

Python Bug: 了解并避免Python编程中的错误 在编程中遇到错误是件非常常见的事情&#xff0c;Python编程也不例外。在Python中&#xff0c;被称为“bug”的错误主要分为两种&#xff0c;编译错误和运行时错误。本文将向您介绍如何识别、调试和避免Python编程中的错误&#xff…

Windi CSS 原子css 下一代工类 CSS 框架

最近由于项目原因接触到了windi Css 发现这个东西真是绝绝子啊,不用在代码里写一行style,完全以类的形式去写样式,它里面包含了几乎所有的css样式&#xff0c;可以让我们不需要再去繁琐的写css样式&#xff0c;原来几行的css现在只需要短短的几个字符。他的许多新特性给我们带来…

《算法竞赛进阶指南》(持续更新ing)

算法竞赛进阶指南 位运算 AcWing 89. a^b #include<iostream> using namespace std;int main(void) {long long a,b,p;cin>>a>>b>>p;long long ans1%p;while(b){if(b&1)//判断b当前二进制位是否为1{ansans*a%p;}aa*a%p;//每跨越一个二进制位&…

用WaveNet预测(Adapted Google WaveNet-Time Series Forecasting)

目录 剧情简介: 数据来源 加载数据 分割数据和可视化 时间序列的多元波网模型:实现(多步预测) 创建模型 创建数据集 数据准备 1- Training dataset preparation 2- Validation dataset preparation Train the Model with TPU: 使用经过训练的适应Google WaveNet预测…

【多线程】| 基本知识汇总

目录 &#x1f981; 掌握基本概念1. 什么是线程&#xff1f;2. 什么是主线程以及子线程&#xff1f;3. 什么是串行&#xff1f;什么是并行&#xff1f; 什么是并发? &#x1f981; 线程的创建1. 通过继承Thread类实现多线程2. 通过Runnable接口实现多线程 &#x1f981; 线程执…

AI故事:智慧学校的人脸识别奇幻之旅

人脸识别 在一个名为智慧学校的小镇上&#xff0c;生物老师Rita和她的丈夫朝哥&#xff0c;一个富有创造力的艺术家&#xff0c;过着幸福美满的生活。他们的家庭与学校紧密相连&#xff0c;成为了一座小小的教育乐园。 智慧学校里有一群充满朝气的学生&#xff0c;其中小枣是…

自定义属性,v-bind computed的使用

0.0 自定义组件的使用 【掌握】 先自定义自己的组件 引入组件 import 组件名 from 路径/文件名 注册组件 <script> export default {components:{ // 组件注册组件名:组件名&#xff0c;组件名1},data(){ // 数据return {}},methods:{ // 方法} ​ } ​ </script&…

buu [AFCTF2018]MyOwnCBC 1

题目描述&#xff1a; 三份文件 #!/usr/bin/python2.7 # -*- coding: utf-8 -*-from Crypto.Cipher import AES from Crypto.Random import random from Crypto.Util.number import long_to_bytesdef MyOwnCBC(key, plain):if len(key)!32:return "error!"cipher_t…

lwIP更新记03:IPv6

从 lwIP-2.0.0 开始&#xff0c;lwIP 终于有可用的 IPv6 协议栈了&#xff01;IPv6 支持 双栈&#xff08;IPv4 和 IPv6 同时使用&#xff09; 或 IPv4/IPv6 二选一 模式。 lwIP-1.4.1 版本也有 IPv6&#xff0c;但那是实验性质的&#xff08;见…\lwip-1.4.1\src\core\ipv6目…

linux专题:嵌入式linux系统启动流程基础分析

目录 第一&#xff1a;linux内核源码基本简介 第二&#xff1a;uboot启动分析 第三&#xff1a;内核源码分析 第一&#xff1a;linux内核源码基本简介 下载 Linux 内核网址&#xff1a; https://www.kernel.org/ 最新 Linux 内核是 5.15 版本。现在常用 Linux 内核源码为4…

八大排序-直接插入排序、希尔排序、直接选择排序、冒泡排序、堆排序、快速排序、归并排序、基数排序

目录 前言 直接插入排序&#xff08;Insertion Sort&#xff09; 一、概念及其介绍 二、过程图示 三、代码 四、复杂度 希尔排序&#xff08;Shell Sort&#xff09; 一、概念 二、实现思路 三、图示过程 四、代码 4.1代码 4.2运行结果 4.3解释 五、复杂度 堆排…

路径规划算法:基于蝙蝠算法的路径规划算法- 附代码

路径规划算法&#xff1a;基于蝙蝠的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于蝙蝠的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法蝙蝠算法来进…