利用aop实现redis的缓存处理

1.利用aop实现redis的缓存处理

1.1 业务说明

由于将代码直接写道service业务层中,会导致代码的耦合性高不便于扩展,为了实现代码松耦合可以利用奥胖机制实现缓存处理

2. AOP的知识回顾

2.1 什么是AOP

AOP = 通知方法 + 切入点表达式
说明:当程序的执行曼珠了切尔u点表达式,代码执行的过程会发生变化,会执行通知方法
AOP用途:将代码中不得不做的事情,放在切面中完成
AOP作用:对原有的方法进行扩展
业务中常用的aop的场景
1.日志的记录
2.监控执行时间
3.实现事务控制
4.异常监控
5.缓存的处理
特点:公用的业务

2.2 通知方法

前置通知:在目标方法执行之前执行
后置通知:在目标方法执行之后执行
异常通知:在目标方法执行之后报错时通知
最终通知:无论用户发生了什么,都要执行的操作,最后执行
特点:前四大通知不能影响目标方法是否执行,只能做记录(监控程序的状态),不能改变程序的运行轨迹
环绕通知:目标方法执行前后都要执行的通知方法;可以影响程序的执行轨迹,功能最为强大

2.3 切入点的表达式

1.粗粒度的:按位置匹配
1.1:bean(beanId) 按照某个bean对象进行匹配 一个
1.2:within(包名.类名) 可以执行某些类进行匹配 多个
2.细粒度的
2.1:execution(返回值类型 包名.类名.方法名(参数列表))
ep:

	execution( int com.tedu.service.goodsImpl.add(int))
	execution( * com.tedu.service..*.*(..))
	//返回值类型任意:

@annotation(包名.注解名) 被注解的类,方法,属性都可以被切入点表达式匹配

2.4 AOP的复习案例:

2.4.1 获取类名方法名和属性

package com.jt.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component //1.我是一个javaBean
@Aspect    //2.我是一个切面
public class CacheTestAOP {
	 //定义切入点表达式:
    @Pointcut("bean(itemServiceImpl)")
    public void pointCut(){}
    /**
     * 定义通知方法
     * 1. 想获取目标方法的名称
     * 2. 获取目标方法的对象
     * 3. 获取目标方法的参数
     */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getDeclaringTypeName(); //获取类名
        String methodName = joinPoint.getSignture().getName(); //获取方法名
        Object target = joinPoint.getTarget(); //获取对象
        Object[] args = joinPoint.getArgs();//获取参数
        System.out.println("我是前置通知方法");
        System.out.println("这是我的名字" + name);
        String.out.println("这是我的方法名" + methodName)
        System.out.println("这是我的对象" + target);
        System.out.println("这是我的参数" + args);
    }
}

2.4.2 自定义注解

package com.jt.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD) //注解在方法中使用
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface CacheFind {
    //设定key
    String key();             // 设定key,用户自己设定
    int seconds() default 0;  //可以指定超时时间,也可以不指定
}

2.4.3 使用自定义注解

@Override
	@CacheFind(key = "ITEM_CAT_LIST")  //使用注解  自定义前缀
	public List<EasyUITree> findItemCatListByParentId(Long parentId){
		QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
		queryWrapper.eq("parent_id",parentId);
		List<ItemCat> itemCatList = itemCatMapper.selectList(queryWrapper);
		List<EasyUITree> treeList = new ArrayList<>();  //先准备一个空集合.
		//需要将数据一个一个的格式转化.
		for(ItemCat itemcat : itemCatList){
			Long id = itemcat.getId();	//获取ID
			String text = itemcat.getName();	//获取文本
			//如果是父级,则默认应该处于关闭状态 closed, 如果不是父级 则应该处于打开状态. open
			String state = itemcat.getIsParent()?"closed":"open";
			//利用构造方法 为VO对象赋值  至此已经实现了数据的转化
			EasyUITree tree = new EasyUITree(id,text,state);
			treeList.add(tree);
		}

		//用户需要返回List<EasyUITree>
		return treeList;
	}

2.4.4 使用AOP实现缓存

package com.jt.aop;

import com.jt.annotation.CacheFind;
import com.jt.utils.ObjectMapperUtil;
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.stereotype.Component;
import redis.clients.jedis.Jedis;
import java.util.Arrays;

@Component
@Aspect
public class CacheAOP {
    //引入redis配置
    @Autowired(required = false)
    private Jedis jedis;

    /**
     * AOP缓存实现的业务策略
     * 1:切入点表达式应该拦截 @CacheFind注解
     * 2:通知方法: 环绕通知
     * 注意:
     * 如果使用环绕通知,这必须在第一个参数的位置添加ProceedingJoinPoint
     *     完成redis配置  key
     *
     * 动态获取注解参数的步骤
     *     1.@annotation(cacheFind)   切入点表达式要求拦截一个类型为cacheFind注解。
     *     2.并且利用连接点为参数中的cacheFind赋值
     */
    @Around("@annotation(cacheFind)")  //输入的是切入点的表达式
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
        Object result = null;
        try {
            //如何获取动态的获取注解中的数据?
            String prokey = cacheFind.key();
            //动态的获取方法中的参数
            String args = Arrays.toString(joinPoint.getArgs());
            String key = prokey + "::" + args;
            //3.校验redis中是否有数据,
            if (jedis.exists(key)){
                //有缓存  从redis缓存中获取json,之后还原对象返回
                String json = jedis.get(key);
                //target代表这目标方法的返回值类型
                //动态的获取目标方法的返回值类型
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                Class returnClass = methodSignature.getReturnType();
                //将json转化为对象
                result = ObjectMapperUtil.toObject(json, returnClass);
                System.out.println("调用redis的AOP转换类型");
            }else{
                //没缓存,第一次查询数据库
                result = joinPoint.proceed(); //执行目标方法
                System.out.println("开始执行AOP保存到redis数据库");
                //将数据保存到redis
                String json = ObjectMapperUtil.toJSON(result);
                if (cacheFind.seconds() > 0)//如果存在超时时间
                    jedis.setex(key,cacheFind.seconds(),json);
                else//不存在超时时间
                    jedis.set(key,json);
            }
            return result;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new RuntimeException(throwable);
        }
    }
}

3 常见的redis的面试题:

3.1 缓存穿透

特点:用户高并发的环境下,查询数据库中不存在的数据;
影响:由于用户高并发的访问数据库,容易造成宕机的风险
解决方法:
api网关的限制
限定ip的访问的次数

3.2 缓存击穿

由于用户高并发的访问,访问的数据刚开始有缓存,但是由于特殊原因导致(少量)数据的缓存失效,用户的请求访问直接访问数据库,导致数据库有风险。
特点:只影响一段时间(时间较短)
解决方法:让业务查询多个redis,并且保证数据不同一时间删除即可。

3.3 缓存雪崩

由于高并发的环境下,有大量的用户访问服务器,redis有大量的数据在同一时间超时(删除)
解决方法:让业务查询多个redis,并且包装数据不在统一时间删除即可

3.4 redis持久化问题

问题说明: redis中的数据都保存到内存中,如果我们服务关闭或者宕机则内存资源的直接丢失,导致缓存失效

3.4.1 持久化原理:

redis中有自己的持久化策略,redis启动时根据配置文件中指定的持久化方式进行持久化操作,redis中默认的持久化的方式为RDB的方式

3.4.2 RDB模式

RDB模式采用定期持久化方式,风险可能丢失数据
RDB模式记录的是当前redis的内存记录的快照(只记录挡墙的状态)持久化的效率是最高的
RDB模式是redis默认的持久化方式。

持久化的命令:

save  //同步的执行持久化的命令操作  要求马上持久化,可能对现有造成阻塞
bgsave   //异步的执行持久化命令操作  开启到哪都的线程实现持久化的任务

持久化的周期:
save 900 1 在900秒内如果执行一次更新操作,则持久化一次
save 300 10 在300秒内,如果执行10次更新操作,则持久化1次
save 60 10000 在60秒内,如果执行10000次更新操作,则持久化1次
用户操作越频繁,则持久化的周期越短

3.4.3 AOF模式

特点:
AOF默认是关闭状态如果需要,则需要手动开启
AOF能够记录程序的执行过程,可以实现数据的实时持久化,AOF文件占有的空间较大,回复数据的速度较慢
AOF模式开启后,RDB是不生效的
AOF的配置:
将appendoly改为yes
appendfsync always 实时持久化
appendfsync everysec 每秒持久化一IC,略低于rdb模式
appendfsync no 自己不主动持久化

3.4.4如何选择持久化方式:

思路:如果允许数据少量的丢失,则首选rdb,如果不允许数据丢失值使用ROF模式

3.4.5 情景思考:

小张在双11前夜误操作将redis服务器执行了flushAll命令,问项目经理如何解决
A:痛批一顿,让其提交离职申请。
B:批评教育,让其深刻反省,并请主管捏脚。
C:项目经理快速解决,并且通知全部门注意。

解决方法:
修改aof文件中的命令,删除flushall之后重启redis即可

3.5 redis内存的优化策略:

3.5.1 修改redis的内存

修改内存的大小的方式

vim redis.conf
:/set nu
//找到565行将#去掉并修改数据即可

3.5.2 LRU算法

概念:LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

3.5.3 LUF算法

LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
least frequently used (LFU) page-replacement algorithm
即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。

3.5.4 RANDOM算法

random即随机数发生器,使用之前需要使用Randomize语句进行随机数种子的初始化。RANDOM产生的是伪随机数或者说是用一种复杂的方法计算得到的序列值,因此每次运算时需要一个不同的种子值。种子值不同,得到的序列值也不同。因此也就是真正的随机数了。这也正是RANDOMIZE随机初始化的作用。 VB里用 NEW RANDOM()来表示初始化。

3.5.5 内存优化策略

  1. volatile-lru 在设定了超时时间的数据,采用LRU算法进行删除
  2. allkeys-lru 所有数据采用LRU算法0
  3. volatile-lfu 在设定了超时时间的数据,采用LFU算法进行删除
  4. allkeys-lfu 所有数据采用LFU算法
  5. volatile-random 设定超时时间数据采用随机算法
  6. allkeys-random 所有数据采用随机算法
  7. volatile-ttl 设定了超时时间的数据根据ttl规则进行删除
  8. noeviction 内存满了不做任何操作,直接报错返回
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值