1 AOP 实现 Redis 缓存
1.1 如何了解 AOP
名称: 面向切面编程
作用: 升高零碎中代码的耦合性, 并且在不扭转原有代码的条件下对原有的办法进行性能的扩大.
公式: AOP = 切入点表达式 + 告诉办法
1.2 告诉类型
1. 前置告诉 指标办法执行之前执行
2. 后置告诉 指标办法执行之后执行
3. 异样告诉 指标办法执行过程中抛出异样时执行
4. 最终告诉 无论什么时候都要执行的告诉
特点: 上述的四大告诉类型 不能干涉指标办法是否执行.个别用来做程序运行状态的记录. 监控
5. 盘绕告诉 在指标办法执行前后都要执行的告诉办法 该办法能够控制目标办法是否运行.joinPoint.proceed(); 性能作为弱小的.
1.3 切入点表达式
了解: 切入点表达式就是一个程序是否进入告诉的一个判断 (IF)
作用: 当程序运行过程中 ,满足了切入点表达式时才会去执行告诉办法,实现业务的扩大.
品种(写法):
- bean(bean 的名称 bean 的 ID) 只能拦挡具体的某个 bean 对象 只能匹配一个对象
lg: bean(“itemServiceImpl”)
- within(包名. 类名) within(“com.jt.service.*”) 能够匹配多个对象
粗粒度的匹配准则 按类匹配
`3. execution(返回值类型 包名. 类名. 办法名(参数列表)) 最为弱小的用法
lg : execution(* com.jt.service..*.*(..))
返回值类型任意 com.jt.service 包下的所有的类的所有的办法都会被拦挡.
4.@annotation(包名. 注解名称) 依照注解匹配.`
* 1
* 2
* 3
* 4
1.4 AOP 入门案例
`package com.jt.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.Arrays;
@Aspect // 我是一个 AOP 切面类
@Component // 将类交给 spring 容器治理
public class CacheAOP {
// 公式 = 切入点表达式 + 告诉办法
/**
* 对于切入点表达式的应用阐明
* 粗粒度:
* 1.bean(bean 的 Id) 一个类
* 2.within(包名. 类名) 多个类
* 细粒度
*/
//@Pointcut("bean(itemCatServiceImpl)")
//@Pointcut("within(com.jt.service..*)") // 匹配多级目录
@Pointcut("execution(* com.jt.service..*.*(..))") // 办法参数级别
public void pointCut(){// 定义切入点表达式 只为了占位}
// 区别: pointCut() 示意切入点表达式的援用 实用于多个告诉 共用切入点的状况
// @Before("bean(itemCatServiceImpl)") 实用于单个告诉. 不须要复用的
// 定义前置告诉, 与切入点表达式进行绑定. 留神绑定的是办法
/**
* 需要: 获取指标对象的相干信息.
* 1. 获取指标办法的门路 包名. 类名. 办法名
* 2. 获取指标办法的类型 class
* 3. 获取传递的参数
* 4. 记录以后的执行工夫
*/
@Before("pointCut()")
//@Before("bean(itemCatServiceImpl)")
public void before(JoinPoint joinPoint){String className = joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
Class targetClass = joinPoint.getTarget().getClass();
Object[] args = joinPoint.getArgs();
Long runTime = System.currentTimeMillis();
System.out.println("办法门路:" +className+"."+methodName);
System.out.println("指标对象类型:" + targetClass);
System.out.println("参数:" + Arrays.toString(args));
System.out.println("执行工夫:" + runTime+"毫秒");
}
/* @AfterReturning("pointCut()")
public void afterReturn(){System.out.println("我是后置告诉");
}
@After("pointCut()")
public void after(){System.out.println("我是最终告诉");
}*/
/**
* 盘绕告诉阐明
* 注意事项:
* 1. 盘绕告诉中必须增加参数 ProceedingJoinPoint
* 2.ProceedingJoinPoint 只能盘绕告诉应用
* 3.ProceedingJoinPoint 如果当做参数 则必须位于参数的第一位
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint){System.out.println("盘绕告诉开始!!!");
Object result = null;
try {result = joinPoint.proceed(); // 执行下一个告诉或者指标办法
} catch (Throwable throwable) {throwable.printStackTrace();
}
System.out.println("盘绕告诉完结");
return result;
}
}`
2 对于 AOP 实现 Redis 缓存
2.1 自定义缓存注解
问题: 如何管制 哪些办法须要应用缓存? cacheFind()
解决方案: 采纳自定义注解的模式 进行定义, 如果 办法执行须要应用缓存, 则标识注解即可.
对于注解的阐明:
1. 注解名称 : cacheFind
2. 属性参数 :
2.1 key: 应该由用户本人手动增加 个别增加业务名称 之后动静拼接造成惟一的 key
2.2 seconds: 用户能够指定数据的超时的工夫
`@Target(ElementType.METHOD) // 注解对办法无效
@Retention(RetentionPolicy.RUNTIME) // 运行期无效
public @interface CacheFind {public String preKey(); // 用户标识 key 的前缀.
public int seconds() default 0; // 如果用户不写示意不须要超时. 如果写了以用户为准.}`
2.2 编辑 CacheAOP
`package com.jt.aop;
import com.jt.anno.CacheFind;
import com.jt.config.JedisConfig;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect // 我是一个 AOP 切面类
@Component // 将类交给 spring 容器治理
public class CacheAOP {
@Autowired
private Jedis jedis;
/**
* 切面 = 切入点 + 告诉办法
* 注解相干 + 盘绕告诉 控制目标办法是否执行
*
* 难点:
* 1. 如何获取注解对象
* 2. 动静生成 key prekey + 用户参数数组
* 3. 如何获取办法的返回值类型
*/
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
Object result = null;
try {
//1. 拼接 redis 存储数据的 key
Object[] args = joinPoint.getArgs();
String key = cacheFind.preKey() +"::" + Arrays.toString(args);
//2. 查问 redis 之后判断是否有数据
if(jedis.exists(key)){
//redis 中有记录, 无需执行指标办法
String json = jedis.get(key);
// 动静获取办法的返回值类型 向上造型 向下造型
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Class returnType = methodSignature.getReturnType();
result = ObjectMapperUtil.toObj(json,returnType);
System.out.println("AOP 查问 redis 缓存");
}else{
// 示意数据不存在, 须要查询数据库
result = joinPoint.proceed(); // 执行指标办法及告诉
// 将查问的后果保留到 redis 中去
String json = ObjectMapperUtil.toJSON(result);
// 判断数据是否须要超时工夫
if(cacheFind.seconds()>0){jedis.setex(key,cacheFind.seconds(),json);
}else {jedis.set(key, json);
}
System.out.println("aop 执行指标办法查询数据库");
}
} catch (Throwable throwable) {throwable.printStackTrace();
}
return result;
}
}`
3 对于 Redis 配置阐明
3.1 对于 Redis 长久化的阐明
redis 默认条件下反对数据的长久化操作. 当 redis 中有数据时会定期将数据保留到磁盘中. 当 Redis 服务器重启时 会依据配置文件读取指定的长久化文件. 实现内存数据的复原.
3.2 长久化形式介绍
3.2.1 RDB 模式
特点:
1.RDB 模式是 redis 的默认的长久化策略.
2.RDB 模式记录的是 Redis 内存数据的快照 . 最新的快照会笼罩之前的内容 所有 RDB 长久化文件占用空间更小 长久化的效率更高.
3.RDB 模式因为是定期长久化 所以 可能导致数据的失落.
命令:
- save 要求立刻马上长久化 同步的操作 其余的 redis 操作会陷入阻塞的状态.
- bgsave 开启后盾运行 异步的操作 因为是异步操作, 所以无奈保障 rdb 文件肯定是最新的须要期待.
配置:
1. 长久化文件名称:
2. 长久化文件地位
dir ./ 相对路径的写法
dir /usr/local/src/redis 绝对路径写法
3.RDB 模式长久化策略
3.2.2 AOF 模式
特点:
1.AOF 模式默认条件下是敞开的, 须要用户手动的开启
- AOF 模式是异步的操作 记录的是用户的操作的过程 能够 避免用户的数据失落
- 因为 AOF 模式记录的是程序的运行状态 所以长久化文件绝对较大, 复原数据的工夫长. 须要人为的优化长久化文件
配置:
3.2.2 对于长久化操作的总结
1. 如果不容许数据失落 应用 AOF 形式
2. 如果谋求效率 运行大量数据失落 采纳 RDB 模式
3. 如果既要保障效率 又要保证数据 则应该配置 redis 的集群 主机应用 RDB 从机应用 AOF
3.3 对于 Redis 内存策略
3.3.1 对于内存策略的阐明
阐明:Redis 数据的存储都在内存中. 如果始终想内存中存储数据 必然会导致内存数据的溢出.
解决形式:
- 尽可能为保留在 redis 中的数据增加超时工夫.
- 利用算法优化旧的数据.
3.3.2 LRU 算法
特点: 最好用的内存优化算法.
LRU 是 Least Recently Used 的缩写,即 最近起码应用 ,是一种罕用的页面置换算法,抉择最近最久未应用的页面予以淘汰。该算法赋予每个页面一个拜访字段,用来记录一个页面自上次被拜访以来所经验的工夫 t,当须淘汰一个页面时,抉择现有页面中其 t 值最大的,即最近起码应用的页面予以淘汰。
维度: 工夫 T
3.3.3 LFU 算法
LFU(least frequently used (LFU) page-replacement algorithm)。即最不常常应用页置换算法 ,要求在页置换时置换援用计数最小的页,因为常常应用的页应该有一个较大的援用次数。然而有些页在开始时应用次数很多,但当前就不再应用,这类页将会长工夫留在内存中,因而能够将引 用计数寄存器定时右移一位 ,造成指数衰减的均匀应用次数。
维度: 应用次数
3.3.4 RANDOM 算法
随机删除数据
3.3.5 TTL 算法
把设定了超时工夫的数据将要移除的提前删除的算法.
3.3.6 Redis 内存数据优化
- 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 算法
8.noeviction 如果内存溢出了 则报错返回. 不做任何操作. 默认值
4 对于 Redis 缓存面试题
问题形容: 因为海量的用户的申请 如果这时 redis 服务器呈现问题 则可能导致整个零碎解体.
运行速度:
- tomcat 服务器 150-250 之间 JVM 调优 1000/ 秒
- NGINX 3- 5 万 / 秒
- REDIS 读 11.2 万 / 秒 写 8.6 万 / 秒 均匀 10 万 / 秒
4.1 缓存穿透
问题形容: 因为用户 高并发 环境下拜访 数据库中不存在的数据时 , 容易导致缓存穿透.
如何解决: 设定 IP 限流的操作 nginx 中 或者微软服务机制 API 网关实现.
4.2 缓存击穿
问题形容: 因为用户 高并发 环境下, 因为某个数据之前存在于内存中, 然而因为非凡起因 (数据超时 / 数据意外删除) 导致 redis 缓存生效. 而使大量的用户的申请间接拜访数据库.
俗语: 趁他病 要他命
如何解决:
1. 设定超时工夫时 不要设定雷同的工夫.
2. 设定多级缓存
4.3 缓存雪崩
阐明: 因为 高并发 条件下 有 大量的数据生效 . 导致 redis 的命中率太低. 而使得用户间接拜访数据库(服务器) 导致奔溃, 称之为缓存雪崩.
解决方案:
1. 不要设定雷同的超时工夫 随机数
2. 设定多级缓存.
3. 进步 redis 缓存的命中率 调整 redis 内存优化策略 采纳 LRU 等算法.