AOP缓存的实现(day13)

1. AOP缓存的实现

1.1 自定义注解

在hcds-common下创建一个新的包,名字叫com.hc.annotation,作为自定义注解的包,注解名称命名为CacheFind

package com.hc.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-value  value是方法的返回值
    String key();//要求用户必须指定key
    int seconds() default -1;//设定超时时间 -1 无需超时
}

1.2 使用缓存注解

在ItemCatServiceImpl里面找到findItemCatList方法,在方法上面使用缓存注解@CacheFind(key=“是哪里的业务逻辑就写成哪里的key”)
例如:@CacheFind(key=“ITEM_CAT_PARENTID”)

AOP缓存的实现(day13)

1.3 编辑RedisAOP

在hcds-common下找到对应的com.hc.aop包下的RedisAOP

1.3.1 通知选择:是否控制目标方法是否执行 环绕通知

原因:如果有缓存数据,直接查找缓存数据,但是如果没有缓存数据,需要走相应的目标方法

 //通知选择:是否控制目标方法是否执行  环绕通知
    //切入点的表达式:控制注解 @annotation(语法...)
    @Around("@annotation(com.hc.annotation.CacheFind)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("执行环绕通知");
        return joinPoint.proceed();
    }

1.3.2 反射机制

获取目标对象~~~获取方法对象~获取注解 原始API

/**
     * 需求:如何动态获取注解中的属性值...
     * 原理 反射机制
     *           获取目标对象~~~~~获取方法对象~~~获取注解  原始API
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(com.hc.annotation.CacheFind)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //1.获取目标对象类型
        Class targetClass=joinPoint.getTarget().getClass();
        //2.获取方法
        String name=joinPoint.getSignature().getName();
        Object[] objArgs=joinPoint.getArgs();
        Class[] classArgs=new Class[objArgs.length];
        for (int i=0;i<objArgs.length;i++){
            Object obj = objArgs[i];
            classArgs[i] = obj.getClass();
        }
        Method method=targetClass.getMethod(name,classArgs);
        System.out.println("执行环绕通知");
        return joinPoint.proceed();
    }

上面是运用反射机制写的一个比较复杂的写法,但是我们aop有一些方法可以直接取到我们想取的东西,例如第二种写法:

@Around("@annotation(com.hc.annotation.CacheFind)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature= (MethodSignature) joinPoint.getSignature();
        Method method=methodSignature.getMethod();
        CacheFind cacheFind=method.getAnnotation(CacheFind.class);
        String key=cacheFind.key();
        System.out.println("获取key"+key);
        return joinPoint.proceed();
    }

spring优化aop(动态获取属性的模板)

	AOP中的语法规范1.:
     *   如果通知方法有参数需要添加,则joinPoint 必须位于第一位.
     *   报错信息: error at ::0 formal unbound in pointcut
     * AOP中的语法规范2:
     *   如果需要动态接受注解对象,则在切入点表达式中直接写注解参数名称即可
     *   虽然看到的是名称,但是解析时变成了包名.类型
	@Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) throws Throwable {
        String key=cacheFind.key();
        System.out.println("获取key"+key);
        return joinPoint.proceed();
    }
 @Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) throws Throwable {
        Object result = null;
        //1.获取key="ITEM_CAT_PARENTID"
        String key = cacheFind.key();
        //2.动态拼接key 获取参数信息
        String args = Arrays.toString(joinPoint.getArgs());
        key += "::" + args;
        //3.redis缓存实现
        if(jedis.exists(key)){
            String json = jedis.get(key);
            //target标识返回值类型????
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Class returnType = methodSignature.getReturnType();
            result = ObjectMapperUtil.toObject(json, returnType);
            System.out.println("AOP缓存查询!!!");
        }else{
            //查询数据库 执行目标方法
            result = joinPoint.proceed();
            String json = ObjectMapperUtil.toJSON(result);
            if(cacheFind.seconds()>0)
                jedis.setex(key,cacheFind.seconds(),json);
            else
                jedis.set(key, json);
            System.out.println("AOP查询数据库");
        }
        return result;
    }

2 关于Redis 持久化机制

2.1 业务需求

Redis中的运行环境在内存中, 但是内存特点 断电即擦除.
想法:能否保存redis中的内存数据不丢失呢?
持久化: 将内存数据定期保存到磁盘中.

2.2 RDB模式

2.2.1 关于RDB模式说明

1.Redis 定期 将内存数据保存到RDB文件中.
2.RDB模式 redis默认的规则.
3.RDB模式记录的是内存数据的快照 ,持久化效率更高(只保留最新数据)
4.RDB模式由于定期持久化,可能导致数据丢失.(致命问题)

(解析1和2)删除Linux下的redis文件夹下的dump.rdb 文件,你会发现之前redis客户端里存储的数据都消失了
(解析3)将dump.rdb文件进行cat操作之后,我们会发现文件里面大部分都是乱码,其实这就是内存数据的快照。

2.2.2 RDB命令

1: 持久化操作 save 同步操作 可能其他线程陷入阻塞
2: 后端持久化 bgsave 异步操作 用户操作不会陷入阻塞 该操作什么时候完成不清楚.

在redis客户端下,敲写save命令 进行持久化操作 大家会发现之前删除的dump.rdb文件又重新出现了
进行持久化操作的时候,因为是同步操作所以我们可能导致线程进入阻塞状态,大家可以想一下我们不能一边进行set操作,一边进行持久化操作吧,就像不能一边吃一边那啥一样

2.2.3 RDB模式配置

save 900 1 900秒内用户更新1次 则持久化1次
save 300 10 300秒内用户更新10次 持久化 1次
save 60 10000 60秒内用户更新10000次 持久化1次
save 1 1 1秒操作1次,持久化1次 保证数据安全性 问题:效率极低 阻塞…
如果想让持久化性能更优,则需要通过监控的手段灵活运用.

用户操作越频繁,则持久化周期越短.

我们进入redis文件夹下,vim redis.conf 进入redis的配置文件中 然后搜索save(:/save 检索save)

2.3 AOF模式

2.3.1 开启AOF模式

默认条件下AOF模式 默认关闭的 如果想修改直接将后面的no改成yes
注意事项:(:/appendonly 检索下appendonly )
(:set nu 赋予行号)
AOP缓存的实现(day13)
开启AOF(appendfilename AOF持久化文件名称叫"appendonly.aof")
AOP缓存的实现(day13)
关闭redis的服务,重启redis的服务,然后大家会发现redis.conf文件下多了一个appendonly.aof的文件,至此代表AOF持久化操作成功
AOP缓存的实现(day13)

2.3.2 AOF模式特点

说明: 当开启AOF策略之后,redis持久化以AOF为主.(之前redis里面的数据没了)
AOP缓存的实现(day13)

特点:

	    1. AOF文件默认关闭的,需要手动开启
    	2. AOF文件记录的**是用户的操作过程**.则可以实现实时持久化操作.(几乎不丢数据)
        3. AOF文件做追加的操作,所有持久化文件较大.
        4. AOF持久化时,采用异步的方式进行.
        5. AOF文件需要定期清理.

第2条解析:
AOP缓存的实现(day13)

2.3.3 AOF持久化原则

appendfsync always 用户执行一次操作,持久化一次
appendfsync everysec 每秒持久化一次
appendfsync no 不主动持久化

2.3.4关于AOF与RDB如何选择

业务:
1.如果用户追求速度,允许少量的数据丢失 首选RDB模式. 快
2.如果用户追求数据的安全性. 首选AOF模式.

面试题:
如果你在redis中执行了flushAll命令,如何挽救??
答案: 修改AOF文件中,删除flushAll的命令.重启redis即可.
注意事项: 一般条件下 Redis会开启AOF与RDB 2种模式
常规用法: 一般会配置redis主从结构 主机开启RDB模式 从机开启AOF模式

3 关于Redis 内存优化策略

3.1 关于内存优化的说明

Redis运行环境,在内存中. 但是内存资源有限的.不能一味的扩容.所以需要对内存数据优化.
Redis内存大小的设定:
AOP缓存的实现(day13)
最大内存设定:
AOP缓存的实现(day13)

3.2 LRU算法

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

3.3 LFU算法

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

3.4随机算法

说明: 随机生成挑选数据删除.

3.5 TTL算法

说明: 根据设定了超时时间的数据,将马上要超时的数据提前删除.

3.6 算法优化

  1. volatile-lru 设定超时时间的数据中,采用lru算法删除数据.
    2.allkeys-lru 在所有的数据中 采用LRU算法删除数据.
    3.volatile-lfu 在设定了超时时间的数据中 采用LFU算法删除数据.
    4.allkeys-lfu 在所有数据中. 采用LFU算法删除数据.
    5.volatile-random 设定了超时时间数据,采用随机的方式删除数据
    6.allkeys-random 所有数据采用随机算法.
    7.volatile-ttl 设定超时时间的数据,采用TTl算法删除.
  2. noeviction 默认不删除数据. 如果内存满了,则报错返回. 默认策略
    AOP缓存的实现(day13)

4 关于Redis缓存常见面试题

5分钟:自己上网查找资料!!!

4.1 什么是缓存穿透

说明: 在高并发的条件下,用户频繁访问一个不存在的数据.导致大量的请求直接发往数据库.导致服务器宕机.

AOP缓存的实现(day13)
解决方案:
1. 只要能够保障用户访问的数据 数据库中一定存在. 布隆过滤器
2. IP限流 黑名单. 微服务中可以通过API网关进行控制

4.2 什么是缓存击穿

说明: 在高并发条件下 用户频繁访问一个热点数据. 但是该热点数据在缓存中失效.导致用户直接访问数据库.
俗语: 称它病,要它命

AOP缓存的实现(day13)

4.3 什么是缓存雪崩

说明: 在高并发的条件下, 有大量的缓存数据失效了. 导致redis缓存服务器命中率太低.从而导致用户直接访问数据库.
主要问题: 热点数据失效.
解决方案:
1.为热点数据设定超时时间时 采用随机数. 10秒+随机数 保障数据不会同时失效.
2. 设定多级缓存
AOP缓存的实现(day13)

5. 作业

1.预习什么是布隆过滤器       二进制数组长度      hash函数的个数
2.什么是redis分片机制/哨兵机制
3.什么是一致性hash算法. 
上一篇:Day13 类、对象、构造器、封装


下一篇:Java知识点复习:Day13