iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >使用AOP+redis+lua做方法限流的实现
  • 612
分享到

使用AOP+redis+lua做方法限流的实现

2024-04-02 19:04:59 612人浏览 安东尼
摘要

目录需求实现方式源码Limit 注解LimiTKEyLimitTypeRedisLimiterHelperLimitInterceptorTestService需求 公司里使用One

需求

公司里使用OneByOne的方式删除数据,为了防止一段时间内删除数据过多,让我这边做一个接口限流,超过一定阈值后报异常,终止删除操作。

实现方式

创建自定义注解 @limit 让使用者在需要的地方配置 count(一定时间内最多访问次数)period(给定的时间范围),也就是访问频率。然后通过LimitInterceptor拦截方法的请求, 通过 redis+lua 脚本的方式,控制访问频率。

源码

Limit 注解

用于配置方法的访问频率count、period

import javax.validation.constraints.Min;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Limit {
    
    String key() default "";
    
    String prefix() default "";
    
    @Min(1)
    int count();
    
    @Min(1)
    int period();
    
    LimitType limitType() default LimitType.CUSTOMER;
}

LimitKey

用于标记参数,作为redis key值的一部分

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitKey {
}

LimitType

枚举,redis key值的类型,支持自定义key和ip、methodName中获取key

public enum LimitType {
    
    CUSTOMER,
    
    IP,
    
    METHOD_NAME;
}

RedisLimiterHelper

初始化一个限流用到的redisTemplate Bean

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JSONRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.io.Serializable;
@Configuration
public class RedisLimiterHelper {
    @Bean
    public RedisTemplate<String, Serializable> limitRedisTemplate(@Qualifier("defaultStringRedisTemplate") StringRedisTemplate redisTemplate) {
        RedisTemplate<String, Serializable> template = new RedisTemplate<String, Serializable>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2jsonRedisSerializer());
        template.setConnectionFactory(redisTemplate.getConnectionFactory());
        return template;
    }
}

LimitInterceptor

使用 aop 的方式来拦截请求,控制访问频率

import com.Google.common.collect.ImmutableList;
import com.yxt.qida.api.bean.service.xxv2.openapi.anno.Limit;
import com.yxt.qida.api.bean.service.xxv2.openapi.anno.LimitKey;
import com.yxt.qida.api.bean.service.xxv2.openapi.anno.LimitType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.WEB.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.Http.httpservletRequest;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@Slf4j
@Aspect
@Configuration
public class LimitInterceptor {
    private static final String UNKNOWN = "unknown";
    private final RedisTemplate<String, Serializable> limitRedisTemplate;
    @Autowired
    public LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) {
        this.limitRedisTemplate = limitRedisTemplate;
    }
    @Around("execution(public * *(..)) && @annotation(com.yxt.qida.api.bean.service.xxv2.openapi.anno.Limit)")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Limit limitAnnotation = method.getAnnotation(Limit.class);
        LimitType limitType = limitAnnotation.limitType();
        int limitPeriod = limitAnnotation.period();
        int limitCount = limitAnnotation.count();
        
        String key;
        switch (limitType) {
            case IP:
                key = getIpAddress();
                break;
            case CUSTOMER:
                key = limitAnnotation.key();
                break;
            case METHOD_NAME:
                String methodName = method.getName();
                key = StringUtils.upperCase(methodName);
                break;
            default:
                throw new RuntimeException("limitInterceptor - 无效的枚举值");
        }
        
        Object[] args = pjp.getArgs();
        Annotation[][] paramAnnoAry = method.getParameterAnnotations();
        for (Annotation[] item : paramAnnoAry) {
            int paramIndex = ArrayUtils.indexOf(paramAnnoAry, item);
            for (Annotation anno : item) {
                if (anno instanceof LimitKey) {
                    Object arg = args[paramIndex];
                    if (arg instanceof String && StringUtils.isNotBlank((String) arg)) {
                        key = (String) arg;
                        break;
                    }
                }
            }
        }
        if (StringUtils.isBlank(key)) {
            throw new RuntimeException("limitInterceptor - key值不能为空");
        }
        String prefix = limitAnnotation.prefix();
        String[] keyAry = StringUtils.isBlank(prefix) ? new String[]{"limit", key} : new String[]{"limit", prefix, key};
        ImmutableList<String> keys = ImmutableList.of(StringUtils.join(keyAry, "-"));
        try {
            String luaScript = buildLuaScript();
            RedisScript<Number> redisScript = new DefaultRedisScript<Number>(luaScript, Number.class);
            Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
            if (count != null && count.intValue() <= limitCount) {
                return pjp.proceed();
            } else {
                String classPath = method.getDeclarinGClass().getName() + "." + method.getName();
                throw new RuntimeException("limitInterceptor - 限流被触发:"
                        + "class:" + classPath
                        + ", keys:" + keys
                        + ", limitcount:" + limitCount
                        + ", limitPeriod:" + limitPeriod + "s");
            }
        } catch (Throwable e) {
            if (e instanceof RuntimeException) {
                throw new RuntimeException(e.getLocalizedMessage());
            }
            throw new RuntimeException("limitInterceptor - 限流服务异常");
        }
    }
    
    public String buildLuaScript() {
        StringBuilder lua = new StringBuilder();
        lua.append("local c");
        lua.append("\nc = redis.call('get',KEYS[1])");
        // 调用不超过最大值,则直接返回
        lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
        lua.append("\nreturn c;");
        lua.append("\nend");
        // 执行计算器自加
        lua.append("\nc = redis.call('incr',KEYS[1])");
        lua.append("\nif tonumber(c) == 1 then");
        // 从第一次调用开始限流,设置对应键值的过期
        lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
        lua.append("\nend");
        lua.append("\nreturn c;");
        return lua.toString();
    }
    public String getIpAddress() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

TestService

使用方式示例

    @Limit(period = 10, count = 10)
    public String delUserByUrlTest(@LimitKey String token, String thirdId, String url) throws IOException {
        return "success";
    }

到此这篇关于使用AOP+redis+lua做方法限流的实现的文章就介绍到这了,更多相关AOP+redis+lua限流内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 使用AOP+redis+lua做方法限流的实现

本文链接: https://www.lsjlt.com/news/147764.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 使用AOP+redis+lua做方法限流的实现
    目录需求实现方式源码Limit 注解LimitKeyLimitTypeRedisLimiterHelperLimitInterceptorTestService需求 公司里使用One...
    99+
    2024-04-02
  • 怎么使用AOP+redis+lua做限流
    这篇“怎么使用AOP+redis+lua做限流”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么使用AOP+redis+lu...
    99+
    2023-06-30
  • redis lua限流算法实现示例
    目录限流算法计数器算法场景分析算法实现漏铜算法令牌桶算法:算法实现限流算法 常见的限流算法 计数器算法漏桶算法令牌桶算法 计数器算法   顾名思义,计数器算法是指...
    99+
    2024-04-02
  • redis lua限流算法如何实现
    本篇内容介绍了“redis lua限流算法如何实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!限流算法常见的限流算法计数器算法漏...
    99+
    2023-07-02
  • SpringBoot+Redis+Lua分布式限流的实现
    Redis支持LUA脚本的主要优势 LUA脚本的融合将使Redis数据库产生更多的使用场景,迸发更多新的优势: 高效性:减少网络开销及时延,多次redis服务器网络请求的操作,使用L...
    99+
    2022-11-13
    SpringBoot Redis Lua分布式限流 SpringBoot 分布式限流
  • 使用Java实现Redis限流的方法
    1、概述   限流的含义是在单位时间内确保发往某个模块的请求数量小于某个数值,比如在实现秒杀功能时,需要确保在10秒内发往支付模块的请求数量小于500个。限流...
    99+
    2024-04-02
  • 怎么用Redis Lua脚本实现ip限流
    这篇文章主要讲解了“怎么用Redis Lua脚本实现ip限流”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么用Redis Lua脚本实现ip限流”吧!引言分布式限流最关...
    99+
    2023-07-02
  • Redis Lua脚本实现ip限流示例
    目录引言相比Redis事务来说,Lua脚本有以下优点Lua脚本java代码IP限流Lua脚本引言 分布式限流最关键的是要将限流服务做成原子化,而解决方案可以使使用redis+lua或...
    99+
    2024-04-02
  • Redis和Lua实现分布式限流器
    本篇文章为大家展示了Redis和Lua实现分布式限流器,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。原理计数器算法是指在一段窗口时间内允许通过的固定数量的请求, 比...
    99+
    2024-04-02
  • Springboot使用redis实现接口Api限流的方法
    这篇文章主要介绍“Springboot使用redis实现接口Api限流的方法”,在日常操作中,相信很多人在Springboot使用redis实现接口Api限流的方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答...
    99+
    2023-06-20
  • Redis限流的实现方法有哪些
    本篇内容主要讲解“Redis限流的实现方法有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Redis限流的实现方法有哪些”吧!一、简单的限流基本原理当系统处理能力有限,如何组织计划外的请求对...
    99+
    2023-06-22
  • Redis+AOP怎么自定义注解实现限流
    今天小编给大家分享一下Redis+AOP怎么自定义注解实现限流的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。下载1,下载页面...
    99+
    2023-07-02
  • 使用SpringBoot + Redis 实现接口限流的方式
    目录配置限流注解定制 RedisTemplateLua 脚本注解解析接口测试全局异常处理Redis 除了做缓存,还能干很多很多事情:分布式锁、限流、处理请求接口幂等性。。。太多太多了...
    99+
    2024-04-02
  • SpringBoot使用自定义注解+AOP+Redis实现接口限流的实例代码
    目录为什么要限流限流背景实现限流1、引入依赖2、自定义限流注解3、限流切面4、写一个简单的接口进行测试5、全局异常拦截6、接口测试为什么要限流 系统在设计的时候,我们会有一个系统的预...
    99+
    2024-04-02
  • Redis实现限流器的方法有哪些
    这篇文章主要介绍了Redis实现限流器的方法有哪些的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Redis实现限流器的方法有哪些文章都会有所收获,下面我们一起来看看吧。 ...
    99+
    2024-04-02
  • 如何使用Redis和Lua开发限流器功能
    如何使用Redis和Lua开发限流器功能引言:随着互联网的发展,许多应用都面临着高并发的挑战。在面对大量请求时,必须采取措施来保护系统的稳定性和可用性,其中一个重要的手段就是限流。限流是指对请求的流量进行控制,确保系统在负载高峰时仍然能够正...
    99+
    2023-10-22
    redis lua 限流器
  • redis执行lua脚本的实现方法
    目录1. 语法格式2.类型转换3.lua脚本3.1 script命令3.2 脚本原子性3.3 脚本缓存和EVALSHA3.4 全局变量保护3.5 日志记录从redis 2.6.0版本...
    99+
    2024-04-02
  • Redis实现限流器的三种方法(小结)
    目录方法一:基于Redis的setnx的操作方法二:基于Redis的数据结构zset方法三:基于Redis的令牌桶算法方法一:基于Redis的setnx的操作 我们在使用Redis的...
    99+
    2024-04-02
  • 基于Redis实现分布式应用限流的方法
    限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务。前几天在DD的公众号,看了一篇关于使用 瓜娃 实现单应用限流的方案 --》原文,参考《redis in action》 实...
    99+
    2023-05-30
    redis 限流 流的
  • 怎么使用Go+Redis实现常见限流算法
    本文小编为大家详细介绍“怎么使用Go+Redis实现常见限流算法”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么使用Go+Redis实现常见限流算法”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。固定窗口使用R...
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作