1. AOP实现redis缓存
1.1 业务需求
需要通过自定义注解的形式动态实现缓存操作.通过注解获取其中的key.超时时间.
1.2 自定义注解的用法
1.3 编辑CacheAOP
package com.jt.aop;
import com.jt.annotation.CacheFind;
import com.jt.pojo.ItemDesc;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import sun.misc.Cache;
import java.util.Arrays;
@Aspect //标识我是一个切面
@Component //将对象交给spring容器管理 cacheAOP
public class CacheAOP {
@Autowired
private Jedis jedis;
/**
* 实现思路:
* 1.动态获取key 用户自定义的前缀+用户的参数[0,xx]
* 2.判断key是否存在
* 存在: 直接从redis中获取数据 JSON, 需要将json转化为具体对象 按照返回值类型进行转化
* 不存在: 执行目标方法.获得返回值数据. 将返回值结果转化为JSON格式. 之后保存到缓存中.
* @param joinPoint
* @return
*/
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) throws NoSuchMethodException {
Object result = null;
//1.获取key的前缀
String key = cacheFind.key();
//2.获取方法参数
String argString = Arrays.toString(joinPoint.getArgs());
key = key + "::" + argString;
try {
//3.判断缓存中是否有数据
if(jedis.exists(key)){
String json = jedis.get(key);
//5.获取返回值类型
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
result = ObjectMapperUtil.toObject(json,methodSignature.getReturnType());
System.out.println("AOP查询redis缓存");
}else{
//表示缓存中没有数据,执行目标方法
result = joinPoint.proceed();
String json = ObjectMapperUtil.toJSON(result);
//4.判断数据中是否有超时时间
if(cacheFind.seconds()>0){
jedis.setex(key,cacheFind.seconds(),json);
}else{
jedis.set(key,json);
}
System.out.println("AOP执行数据库调用!!!!!");
}
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new RuntimeException(throwable);
}
return result;
}
//切面 = 切入点表达式 + 通知方法
//表达式1: bean(itemCatServiceImpl) ItemCatServiceImpl类
//@Pointcut("bean(itemCatServiceImpl)")
//@Pointcut("within(com.jt.service.*)")
// .* 一级包下的类 ..* 所有子孙后代的包和类
//返回值类型任意, com.jt.service包下的所有类的add方法参数类型任意类型
//写参数类型时注意类型的大小写
/* @Pointcut("execution(* com.jt.service..*.*(..))")
public void pointcut(){
}*/
/**
*
* joinPoint代表连接点对象,一般适用于前四大通知类型(除around之外的)
* 客人 路人
*/
/*@Before("pointcut()")
public void before(JoinPoint joinPoint){
//1.获取目标对象
Object target = joinPoint.getTarget();
System.out.println(target);
//2.获取目标对象的路径 包名.类名.方法名
String className = joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
System.out.println("目标方法的路径:"+(className+"."+methodName));
//3.获取参数类型
System.out.println(Arrays.toString(joinPoint.getArgs()));
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint){
System.out.println("环绕通知执行");
Object data = null;
try {
data = joinPoint.proceed(); //执行目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return data;
}*/
}
1.4 商品分类名称优化
优化商品分类名称的名称. 在业务层添加缓存注解.
2 Redis属性说明
2.1 Redis持久化策略
2.1.1 为什么要持久化
Redis中的记录都保存在内存中,如果内存断电或者服务器宕机,则内存数据直接丢失.业务中不允许发生. 所以需要将数据定期进行维护.
2.1.2 RDB模式
说明: RDB模式是Redis的默认的持久化策略.无需手动的开启.
特点:
1.Redis会定期的执行RDB持久化操作. 缺点:可能导致内存数据丢失.
2.RDB记录的是内存数据的快照,并且后续的快照会覆盖之前的快照.每次只保留最新数据.效率更高.
命令:
1).save 命令 要求立即执行持久化操作 save会造成线程的阻塞.
2).bgsave 命令 后台执行持久化操作 后台运行不会造成阻塞. 异步操作, 不能保证立即执行
2.1.3 AOF模式
说明: AOF模式默认条件下是关闭的,需要手动的开启,如果开启了AOF模式则RDB模式将失效.但是如果手动执行save命令,则也会生成RDB文件.
1).开启AOF模式
特点:
1.AOF模式记录程序的执行的过程.所以可以保证数据不丢失.
2.由于AOF记录程序运行的过程,所以整个持久化文件相对大,所以需要定期维护. 效率低
2.1.4 RDB与AOF模式持久化对比
1).RDB模式
save 900 1 如果在900秒内,执行了一次更新操作则持久化一次
save 300 10
save 60 10000 操作越快 ,持久化的周期越短.
2).AOF模式
appendfsync always 用户执行一次更新操作,则持久化一次 异步操作
appendfsync everysec 每秒操作一次
appendfsync no 不主动操作 一般不用.
2.1.5 关于RDB与AOF总结
策略: 如果数据允许少量丢失,首选RDB模式,
如果数据不允许丢失则首选AOF模式.
企业策略: 又要满足效率,同时满足数据不丢失.
主机: 采用RDB模式
从机: 采用AOF模式
2.1.6面试题
题目: 小丽是公司特别漂亮的妹子,误操作将redis服务器执行了flushAll命令,问你作为项目经理如何处理??
A. 训斥一顿,之后HR开除.
B. 秀一下自己的技术,让小丽崇拜 一起过上了幸福的生活
解决方案: 需要将从库中的AOF文件 进行编辑,删除多余的flushAll命令,之后重启redis即可.
问题2: 小丽在执行完上述操作之后,由于好奇 误将aof文件一并删除,问如何处理???
答: 杀人祭天!!!
2.2 Redis内存策略
2.2.1 LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
判断维度: 时间T
2.2.2 LFU算法
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
判断维度: 使用次数
2.2.3 随机算法
随机算法
2.2.4 TTL算法
将剩余时间短的数据,提前删除.
2.2.5 Redis的内存优化策略
- volatile-lru 在设定超时时间的数据中采用LRU算法
- allkeys-lru 所有的数据采用LRU算法删除
- volatile-lfu 设定了超时时间的数据采用LFU算法删除
- allkeys-lfu 所有数据采用LFU算法删除
- volatile-random 设定了超时时间的数据采用随机算法
- allkeys-random 所有数据的随机算法
- volatile-ttl 设定了超时时间之后采用TTL算法
- noeviction 不做任何操作,只是返回报错信息.
2.3 关于Redis常见面试题
业务场景: 高并发环境下.用户长时间对服务器进行操作,可能产生如下的问题.
2.3.1 什么是缓存穿透
说明: 用户高并发环境下访问数据库和缓存中都不存在的数据称之为缓存穿透现象.
解决方案:
1). 禁用IP 限制IP访问.
2). 限流 每秒最多访问3次
3). 布隆过滤器
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
原理:
布隆过滤器优化:
问题:如何解决hash碰撞问题
知识点: 由于hash碰撞问题,可能由多个key有相同的位置,所以得出结论,布隆过滤器认为数据存在,那么数据可能存在.如果布隆过滤器认为数据不存在,则数据一定不在.
如何降低hash碰撞的几率:
答:
1.扩容二进制向量位数.
2.增加hash函数的个数
当位数增加/函数适当增加,则可以有效的降低hash碰撞的几率. 默认值 0.03
2.3.2 什么是缓存击穿
说明: **某个(一个)热点数据在缓存中突然失效.**导致大量的用户直接访问数据库.导致并发压力过高造成异常.
解决方案:
1.尽可能将热点数据的超时时间 设定的长一点
2.设定多级缓存 超时时间采用随机算法.
2.3.3 什么是缓存雪崩
说明: 在缓存服务器中,由于大量的缓存数据失效,导致用户访问的命中率过低.导致直接访问数据库.
问题分析:
1. fluashAll命令可能导致缓存雪崩.
2. 设定超时时间时,应该采用随机算法
3. 采用多级缓存可以有效防止.
3 redis分片机制
3.1 redis性能优化
说明: 单台redis内存容量是有限的.但是如果有海量的数据要求实现缓存存储,则应该使用多个Redis节点.
3.2 Redis分片机制定义
3.3 Redis分片机制配置
3.3.1 配置规划
说明: 准备3台redis服务器 分别为 6379/6380/6381
3.3.2 准备3个配置文件
修改各自端口号 改为6380 6381
3.3.3 启动redis服务器
3.3.4 检查redis启动状态
3.3.5 redis分片入门案例
3.3 Redis分片机制配置
说明:根据redis节点个数.拼接字符串
#配置单台redis
#redis.host=192.168.126.129
#redis.port=6379
#配置redis分片机制
redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381
3.4 编辑RedisConfig配置类
@Configuration //标识我是一个配置类 一般与@Bean注解联用
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {
@Value("${redis.nodes}")
private String nodes; //node,node,node
@Bean
public ShardedJedis shardedJedis(){
List<JedisShardInfo> shards = new ArrayList<>();
String[] nodeArray = nodes.split(",");
for( String node :nodeArray){ //node=host:port
String host = node.split(":")[0];
int port = Integer.parseInt(node.split(":")[1]);
JedisShardInfo info = new JedisShardInfo(host,port);
shards.add(info);
}
return new ShardedJedis(shards);
}
}