文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

怎么使用AOP+redis+lua做限流

2023-06-30 11:19

关注

这篇“怎么使用AOP+redis+lua做限流”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么使用AOP+redis+lua做限流”文章吧。

需求

公司里使用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@Documentedpublic @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;@Configurationpublic 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@Configurationpublic 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做限流”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯