文档章节

Aop实现注解限流和Redis缓存

JLLang
 JLLang
发布于 2019/12/05 19:58
字数 1075
阅读 15
收藏 0

gratisography-plam-trees-summer

限流注解实现

业务系统中某些接口需要进行限流的时候在spring家族中可以采用RateLimiter进行接口限流,减轻服务器的压力。实现思路如下:

RateLimit 注解

/**
 * @description: 限流注解
 * @author: lilang
 * @version:
 * @modified By:1170370113@qq.com
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RateLimit {

    double limitNum() default 20;  //默认每秒放入桶中的token

    //获取令牌的等待时间
    int timeOut() default 0;

    //等待时间单位
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

注解RateLimit AOP实现类

@Component
@Aspect
public class RateLimitAspect {

    private Logger log = LoggerFactory.getLogger(this.getClass());
    //用来存放不同接口的RateLimiter(key为接口名称,value为RateLimiter)
    private ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>();

    private static ObjectMapper objectMapper = new ObjectMapper();

    private RateLimiter rateLimiter;

    @Pointcut("@annotation(com.itstyle.mail.common.aop.RateLimit)")
    public void serviceLimit() {
    }

    @ResponseBody
    @Around("serviceLimit()")
    public Object around(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        Object obj = null;
        //获取拦截的方法名
        Signature sig = joinPoint.getSignature();
        //获取拦截的方法名
        MethodSignature msig = (MethodSignature) sig;
        //返回被织入增加处理目标对象
        Object target = joinPoint.getTarget();
        //为了获取注解信息
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        //获取注解信息
        RateLimit annotation = currentMethod.getAnnotation(RateLimit.class);
        double limitNum = annotation.limitNum(); //获取注解每秒加入桶中的token
        TimeUnit timeUnit = annotation.timeUnit();//获取时间单位
        String functionName = msig.getName(); // 注解所在方法名区分不同的限流策略
        int timeOut = annotation.timeOut();
        
        //获取rateLimiter
        if(map.containsKey(functionName)){
            rateLimiter = map.get(functionName);
        }else {
            map.put(functionName, RateLimiter.create(limitNum));
            rateLimiter = map.get(functionName);
        }

        try {
            if (rateLimiter.tryAcquire(timeOut,timeUnit)) {
                //执行方法
                obj = joinPoint.proceed();
            } else {
                return Result.error("服务器繁忙,请稍后再试....");
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return obj;
    }

}

使用方式 比如在controller层进行控制

@RateLimit(limitNum = 10,timeOut = 1,timeUnit = TimeUnit.SECONDS)
@GetMapping("limit/go")
public void queryFromMysql(){
	//数据库操作逻辑
}

Redis缓存实现

缓存注解定义:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RedisCache {

    String prefix() default "";
    int expire() default 1;
    TimeUnit TIME_UNIT() default TimeUnit.DAYS;
    //缓存反序列化获取的对象
    Class clazz() default Object.class;
    //序列化后的对象是否是jsonarry 比如 List<Object>
    boolean isArray() default false;
}

缓存切面类实现:

@Component
@Aspect
public class RedisCacheAspect {

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

    @Autowired
    private RedisTemplate redisTemplate ;

    /**
     * 分隔符 生成key 格式为 类全类名|方法名|参数所属类全类名
     **/
    private static final String DELIMITER = "-";

    /**
     * Service层切点 使用到了我们定义的 RedisCacheAspect 作为切点表达式。
     * 而且我们可以看出此表达式基于 annotation。
     * 并且用于内建属性为查询的方法之上
     */
    @Pointcut("@annotation(com.itstyle.mail.common.aop.RedisCache)")
    public void redisCacheAspect() {
    }

    /**
     * Around 手动控制调用核心业务逻辑,以及调用前和调用后的处理,
     * <p>
     * 注意:当核心业务抛异常后,立即退出,转向AfterAdvice 执行完AfterAdvice,再转到ThrowingAdvice
     *
     */
    @Around(value = "redisCacheAspect()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 得到类名、方法名和参数
        String clazzName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        // 根据类名、方法名和参数生成Key
        logger.info("key参数: " + clazzName + "." + methodName);
        String key = getKey(clazzName, methodName, args);
        if (logger.isInfoEnabled()) {
            logger.info("生成key: " + key);
        }

        // 得到被代理的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        //redis 前缀
        String prefix = method.getAnnotation(RedisCache.class).prefix();

        int expire = method.getAnnotation(RedisCache.class).expire();

        TimeUnit timeUnit = method.getAnnotation(RedisCache.class).TIME_UNIT();

        Class objectType = method.getAnnotation(RedisCache.class).clazz();

        boolean isArray=method.getAnnotation(RedisCache.class).isArray();

        // 检查Redis中是否有缓存
        String value = (String) redisTemplate.opsForValue().get(prefix);

        // 得到被代理方法的返回值类型
        // Class returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();

        // result是方法的最终返回结果
        Object result = null;
        try {
            if (null == value) {
                logger.info("缓存未命中");
                // 调用数据库查询方法
                result = joinPoint.proceed(args);
                // 结果放入缓存
                redisTemplate.opsForValue().set(prefix, JSON.toJSONString(result),expire,timeUnit);
            } else {

                /**
                 * 可以直接针对mapper进行缓存,如果mapper查询返回的List<DemoObjec> 需要isArray 为true 否则转换异常
                 */

                if (isArray){
                    return JSON.parseArray(value,objectType);

                }else {
                    return JSON.parseObject(value,objectType);
                }
            }
        } catch (Throwable e) {
            logger.error("程序异常",e.getMessage());
            throw e;
        }
        return result;
    }

    /**
     *      * 根据类名、方法名和参数生成Key
     *      * @param clazzName
     *      * @param methodName
     *      * @param args
     *      * @return key格式:全类名|方法名|参数类型
     *
     */
    private String getKey(String clazzName, String methodName, Object[] args) {
        StringBuilder key = new StringBuilder(clazzName);
        key.append(DELIMITER);
        key.append(methodName);
        key.append(DELIMITER);
        key.append(Arrays.stream(args).map(x->x.toString()).collect(Collectors.joining(DELIMITER)));
        return key.toString();
    }
}

使用方式 :

以下是放在mapper层加入的注解,实际项目中可以根据自己的需求注解加在任意位置。

@Mapper
public interface DemoMapper {

    @RedisCache(expire = 1,clazz = DemoObject.class,isArray = true)
    public List<DemoObject> queryFromMysql();

    @RedisCache(expire = 1,clazz = DemoObject.class)
    public DemoObject queryFromMysql();
}

使用如上方式,我们便可以在实际项目中无侵入的实现业务限流和业务缓存。

© 著作权归作者所有

JLLang
粉丝 32
博文 108
码字总数 193766
作品 0
济南
私信 提问
加载中

评论(0)

redis 辅助工具包 - redis-aux

redis-aux 是基于 redisTemplate 的 redis 辅助工具包。目前有两个主要模块,基于注解的三种限流方式以及简单易用布隆过滤器。 用法: 这里用maven作为工具管理包演示,添加jitpack源、添加下...

1016644172
01/06
6.3K
10
Jboot v1.2.3 新增 J2Cache 适配及限流功能

Jboot是一个基于JFinal 和 undertow开发的微服务框架。提供了AOP、RPC、分布式缓存、限流、降级、熔断、统一配置中心、Opentracing数据追踪、metrics数据监控、分布式session、代码生成器、s...

理工男海哥
2018/01/04
835
7
微服务架构四大金刚利器 - 知乎

概述 互联网应用发展到今天,从单体应用架构到SOA以及今天的微服务,随着微服务化的不断升级进化,服务和服务之间的稳定性变得越来越重要,分布式系统之所以复杂,主要原因是分布式系统需要考...

我是程序员
2019/11/15
0
0
SpringBoot集成Redis实现缓存处理(Spring AOP实现)

第一章 需求分析 计划在Team的开源项目里加入Redis实现缓存处理,因为业务功能已经实现了一部分,通过写Redis工具类,然后引用,改动量较大,而且不可以实现解耦合,所以想到了Spring框架的A...

Javahih
2017/12/14
0
2
Spring AOP整合redis 实现缓存统一管理

项目使用redis作为缓存数据,但面临着问题,比如,项目A,项目B都用到redis,而且用的redis都是一套集群,这样会带来一些问题。 问题:比如项目A的开发人员,要缓存一些热门数据,想到了red...

豆芽菜橙
2018/08/01
0
0

没有更多内容

加载失败,请刷新页面

加载更多

00-Java 面试准备

面试之前 面试前准备简历需要注意的几个方面: 写简历、改简历,这个一定要干的。简历有两个作用,一个是吸引别人,能让别人邀请你去面试,这是前提;另一个是引导面试的人,让面试的人问你所...

源程序
今天
54
0
OSChina 周二乱弹 —— 大王(@罗马的王)颜值制霸Osc社区

Osc乱弹歌单(2020)请戳(这里) 【今日歌曲】 @巴拉迪维 :Lunik的单曲《Seeing You Soar》 I hope you’re smiling,When seeing me soar. #今日歌曲推荐# 《Seeing You Soar》- Lunik 手...

小小编辑
今天
75
0
wordcount代码

1.写出map类 public class WCMapper extends Mapper<LongWritable,Text,Text,LongWritable>{ @Override protected void map(LongWritable key,Text value,Context context)throws IOExcepti......

七宝1
今天
59
0
Spring Batch 小任务(Tasklet)步骤

Chunk-Oriented Processing不是处理 step 的唯一方法。 考虑下面的一个场景,如果你仅仅需要调用一个存储过程,你可以在 ItemReader 中实现这个调用,然后在存储过程完成调用后返回 null。这...

honeymoose
今天
67
0
Linux日志分析

1. Linux日志文件的类型 2. 系统服务日志 2.1 syslogd的简介 2.2 syslogd的配置和使用 2.3 日志的安全性设置 2.4 远程日志记录服务 3. 日志的轮替 3.1 logrotate简介 3.2 logrotate的配置 3....

JiaMing
昨天
67
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部