springboot自定义类注解,springboot注入接口类

  springboot自定义类注解,springboot注入接口类

   键     * @param count 限流次数     * @param times 限流时间     * @return     */    public boolean limit(String key, int count, int times) {        try {            String script = "local lockKey = KEYS[1]n" +                    "local lockCount = KEYS[2]n" +                    "local lockExpire = KEYS[3]n" +                    "local currentCount = tonumber(redis.call(get, lockKey) or "0")n" +                    "if currentCount < tonumber(lockCount)n" +                    "thenn" +                    "    redis.call("INCRBY", lockKey, "1")n" +                    "    redis.call("expire", lockKey, lockExpire)n" +                    "    return truen" +                    "elsen" +                    "    return falsen" +                    "end";            RedisScript<Boolean> redisScript = new DefaultRedisScript<>(script, Boolean.class);            List<String> keys = Arrays.asList(key, String.valueOf(count), String.valueOf(times));            return redisTemplate.execute(redisScript, keys);        } catch (Exception e) {            log.error("限流脚本执行失败:{}", e.getMessage());        }        return false;    }}通过 Lua 脚本,根据 Redis 中缓存的键值判断限流时间(也是 key 的过期时间)内,访问次数是否超出了限流次数,没超出则访问次数 +1,返回 true,超出了则返回 false。

  

 

  

4、限流切面处理类

import com.asurplus.common.annotation.Limit;import com.asurplus.common.enums.LimitType;import com.asurplus.common.exception.CustomException;import com.asurplus.common.ip.IpUtil;import com.asurplus.common.redis.RedisLimitUtil;import com.asurplus.common.utils.HttpRequestUtil;import lombok.extern.slf4j.Slf4j;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.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.lang.reflect.Method;/** * 限流处理 * * @author Lizhou */@Slf4j@Aspect@Componentpublic class LimitAspect {    @Autowired    private RedisLimitUtil redisLimitUtil;    /**     * 前置通知,判断是否超出限流次数     *     * @param point     */    @Before("@annotation(limit)")    public void doBefore(JoinPoint point, Limit limit) {        try {            // 拼接key            String key = getCombineKey(limit, point);            // 判断是否超出限流次数            if (!redisLimitUtil.limit(key, limit.count(), limit.time())) {                throw new CustomException("访问过于频繁,请稍候再试");            }        } catch (CustomException e) {            throw e;        } catch (Exception e) {            throw new RuntimeException("接口限流异常,请稍候再试");        }    }    /**     * 根据限流类型拼接key     */    public String getCombineKey(Limit limit, JoinPoint point) {        StringBuilder sb = new StringBuilder(limit.prefix());        // 按照IP限流        if (limit.type() == LimitType.IP) {            sb.append(IpUtil.getIpAddr(HttpRequestUtil.getRequest())).append("-");        }        // 拼接类名和方法名        MethodSignature signature = (MethodSignature) point.getSignature();        Method method = signature.getMethod();        Class<?> targetClass = method.getDeclaringClass();        sb.append(targetClass.getName()).append("-").append(method.getName());        return sb.toString();    }}

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

 

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

  

 

  

5、使用与测试

1、测试方法

 

  

@Limit(type = LimitType.DEFAULT, time = 10, count = 2)@GetMapping("test")public String test() {    return "请求成功:" + System.currentTimeMillis();}

使用自定义注解 @Limit,限制为 10 秒内,允许访问 2 次

 

  2、测试结果

  第一次

  

 

  第二次

  

 

  第三次

  

 

  可以看出,前面两次都成功返回了请求结果,第三次超出了接口限流次数,返回了自定义异常信息。

  

 

  

SpringBoot工程中限流方式

限流,是防止用户恶意刷新接口。常见的限流方式有阿里开源的sentinel、redis等。

 

  

 

  

1、google的guava,令牌桶算法实现限流

Guava的 RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。

 

  

// RateLimiter提供了两个工厂方法,最终会调用下面两个函数,生成RateLimiter的两个子类。static RateLimiter create(SleepingStopwatch stopwatch, double permitsPerSecond) {     RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);    rateLimiter.setRate(permitsPerSecond);     return rateLimiter;}static RateLimiter create(     SleepingStopwatch stopwatch, double permitsPerSecond, long warmupPeriod, TimeUnit unit, double coldFactor) {     RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);    rateLimiter.setRate(permitsPerSecond);     return rateLimiter;}

平滑突发限流:使用 RateLimiter的静态方法创建一个限流器,设置每秒放置的令牌数为10个。返回的RateLimiter对象可以保证1秒内不会给超过10个令牌,并且以固定速率进行放置,达到平滑输出的效果。平滑预热限流:RateLimiter的 SmoothWarmingUp是带有预热期的平滑限流,它启动后会有一段预热期,逐步将分发频率提升到配置的速率。

@RestControllerpublic class HomeController {    // 这里的10表示每秒允许处理的量为10个    private RateLimiter limiter = RateLimiter.create(10);    private RateLimiter limiter2 = RateLimiter.create(2, 1000, TimeUnit.SECONDS);    //permitsPerSecond: 表示 每秒新增 的令牌数;warmupPeriod: 表示在从 冷启动速率 过渡到 平均速率 的时间间隔    @GetMapping("/test/{name}")    public String test(@PathVariable("name") String name) {        // 请求RateLimiter, 超过permits会被阻塞        final double acquire = limiter.acquire();        System.out.println("acquire=" + acquire);        //判断double是否为空或者为0        if (acquire == 0) {            return name;        } else {            return "操作太频繁";        }    }    @AccessLimit(limit = 2, sec = 10)    @GetMapping("/test2/{name}")    public String test2(@PathVariable("name") String name) {        return name;    }}

 

  

2、interceptor+redis根据注解限流

public class AccessLimitInterceptor implements HandlerInterceptor {    @Resource    private RedisTemplate<String, Integer> redisTemplate;    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        if (handler instanceof HandlerMethod) {            HandlerMethod handlerMethod = (HandlerMethod) handler;            Method method = handlerMethod.getMethod();            if (!method.isAnnotationPresent(AccessLimit.class)) {                return true;            }            AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);            if (accessLimit == null) {                return true;            }            int limit = accessLimit.limit();            int sec = accessLimit.sec();            String key = IPUtil.getIpAddress(request) + request.getRequestURI();            //资源唯一标识            Integer maxLimit = redisTemplate.opsForValue().get(key);            if (maxLimit == null) {                //set时一定要加过期时间                redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);            } else if (maxLimit < limit) {                redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);            } else {                output(response, "请求太频繁!");                return false;            }        }        return true;    }    public void output(HttpServletResponse response, String msg) throws IOException {        response.setContentType("application/json;charset=UTF-8");        ServletOutputStream outputStream = null;        try {            outputStream = response.getOutputStream();            outputStream.write(msg.getBytes("UTF-8"));        } catch (IOException e) {            e.printStackTrace();        } finally {            outputStream.flush();            outputStream.close();        }    }    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {    }}
@Configurationpublic class InterceptorConfig extends WebMvcConfigurationSupport {    @Bean    public AccessLimitInterceptor accessLimitInterceptor() {        return new AccessLimitInterceptor();    }    @Override    public void addInterceptors(InterceptorRegistry registry) {        //addPathPatterns 添加拦截规则        registry.addInterceptor(accessLimitInterceptor()).addPathPatterns("/**");    }    @Override    public void addViewControllers(ViewControllerRegistry registry) {        registry.addViewController("/");    }}
@Configurationpublic class RedisConfig {    @Bean    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();        template.setConnectionFactory(redisConnectionFactory);        template.setKeySerializer(new StringRedisSerializer());        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());        template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());        template.afterPropertiesSet();        return template;    }}

限流方式还有很多,后续继续尝试。

 

  以上为个人经验,希望能给大家一个参考,也希望大家多多支持盛行IT。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: