1.AOP实现缓存业务
1.1业务需求
1).自定义注解 @CacheFind(key=“xxxx”,second=-1) K-V
2.使用自定义的注解 标识业务方法 将方法的返回值保存到缓存中
3利用AOP拦截注解 利用环绕通知方法实现业务
1.2自定义注解@CacheFind
1.3注解标识
1.4编辑AOP
package com.jt.aop;
import com.jt.anno.CacheFind;
import com.jt.util.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//组件 将类交给spring容器管理
@Aspect//表示我是一个切面
public class RedisAOP {
@Autowired(required = false)//该项不是必须注入的spring容器启动时能注入则注入 ,注入不了则跳过
private Jedis jedis;
//公式aop=切入点表达式+通知方法
//表达式一 @Pointcut("bean(itemCateServiceImpl)")
//表达式二 @Pointcut("within(com.jt.service.*)")
//表达式三
// @Pointcut("execution(* com.jt.service..*.*(..))")
// public void pointCut(){
//
// }
//如何获取,目标对象的相关参数????
//注意这里只能用JoinPoint 因为ProceedingJoinPoint is only supported for around advice
//也就是说因为ProceedingJoinPoint只能用于环绕通知
// @Before("pointCut()")
// public void befor(JoinPoint joinPoint){//连接点
// Object target=joinPoint.getTarget();//获取目标对象
// Object[] args=joinPoint.getArgs();//获取方法参数
// String className=joinPoint.getSignature().getDeclaringTypeName();//获取类名称
// String methodName=joinPoint.getSignature().getName();//获取方法名称
// System.out.println("目标对象"+target);
// System.out.println("方法参数"+ Arrays.toString(args));
// System.out.println("类名称"+className);
// System.out.println("方法名称"+methodName);
// System.out.println("我是前置通知!!!!!!!");
// }
/**
* 实现AOP业务调用
* 1.拦截指定注解
* 2.利用环绕通知实现
* 实现步骤:
* 1.获取key 必须先获取注解 从注解中获取key
*实现步骤:
* 1.获取key 必须先获取注解 从注解中获取key
* 2.校验redis中是否由值
* 知识点补充:
* 指定参数名称进行传值,运行期绑定参数类型完成注解的拦截
* ProceedingJoinPoint 必须位于参数的第一位
*/
@Around("@annotation(cacheFind)")//
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind ){
Object result=null;
//key业务::参数
String key=cacheFind.key();
String args=Arrays.toString( joinPoint.getArgs());
key=key+"::"+args;
//2.校验是否有值
if(jedis.exists(key)){
//缓存中存在数据
String json=jedis.get(key);
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Class resultType = methodSignature.getReturnType();
result=ObjectMapperUtil.toObj(json,resultType);
System.out.println("AOP查询redis缓存");
}else {
//redis中没有数据,所以需要查询数据库,将数据保存到缓存中
try {
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查询数据库");
}catch (Throwable throwable){
throwable.printStackTrace();
}
}
System.out.println(key);
return result;
}
}
2.关于redis常规属性
2.1redis中持久化策略
2.1.1需求说明
说明:Redis中架构数据保存到内存中,但是内存的特点断电及擦除,为了保证redis中的缓存数据不丢失,则需要
将内存数据定期进行持久化操作。
持久化:将内存数据写到磁盘中
2.1.2 RDB模式
特点:
1.RDB模式是redis默认的持久化规则
2.RDB模式记录的是Redis内存数据快照(只保留最新数据)
3.RDB模式定期持久化(时间可调)可能会导致数据丢失
4.RDB模式备份效率是最高的
5.RDB模式备份阻塞式的 在备份时不允许其他用户进行操作,保证数据安全性
命令:
1.主动备份 save 会阻塞用户操作
2.后台备份 bgsave 异步的方式进行持久化操作 不会阻塞
2.1.3关于持久化的配置
1.save 900 1 在900秒内,用户执行了一次更新操作时那么久持久化一次
2.save 60 10 在300秒内,用户执行了10次更新,那么久持久化一次
3.save 60 10000 在60秒内,用户执行了10000次更新操作,则持久化一次
4.save 1 1 1秒内1次更新 持久化一次!!性能特别低
2.1.4关于持久化文件名称的设定
默认条件下,持久化文件名称文dump.rdb
2.1.5文件存储目录
./代表当前文件目录 意义使用绝对路径的写法
2.2redis中持久化策略-AOF
2.2.1AOF特点
1.AOF模式默认条件下是关闭状态,需要手动开启
2.AOF模式记录的是用户的操作过程,可实现实时持久化,保证数据不丢失
3.AOF模式维护的持久化文件占用的空间较大,所以持久化效率不高,并且需要定期的维护持久化文件
4.AOF模式一旦开启,则redis以AOF模式为主,读取的是AOF文件
2.2.2 AOF配置
1.开启AOF模式
2.持久化策略
always:用户更新一次,持久化一次
everysec:每秒持久化一次 效率跟高
no:不主动持久化,操作系统有关,几乎都不用
2.3关于redis面试题
关于flushall操作
业务场景:
小丽是一个漂亮的实习生,你是他的项目主管,由于小丽业务不熟,在生产环境中无意执行leflushall操作,问如何补救?
A.直接开除 B.通过个人技术手段维护,但是存在风险 C.让小丽跑路
如何补救:
场景1:redis中的服务只开启了默认的持久化策略RDB模式
解决方案:1.关闭现有的redis服务器
2.检查RDB文件是否被覆盖,如果文件没有被覆盖,则重启redis即可(希望渺茫)
3.如果执行了flushall,同时执行了save操作,则RDB模式无效(无药可救了)
场景2:redis的服务开启了AOF模式
解决方案:1.关闭redis服务器
2.编辑redis持久化文件,将flushall命令删除
3.重启redis服务器
一般条件下:RDB模式和AOF模式都会开启,通过save命令执行rdb持久化方式
2.3.2单线程的redis为什么快
1.redis运行环境在内存中,存内存操作
2.单线程操作,避免频繁的上下文切换,避免了开关链接的开销
3.采用了非阻塞I/O多路复用的机制(动态感知)
4.Redis最新版本6.0版本,6.0以前的版本都是单线程操作方式,6.0以后支持多线程的方式,(执行时依旧是单线程的操作)
2.4关于redis内存优化策略
2.4.1业务场景
如果频繁使用redis不停地向其中保存数据,并且不做删除的操作,则内存必然溢出,能否优化内存策略
能否自动删除不用的数据,让redis中保留热点数据!!!!
2.4.2LRU算法
计算的维度:自上一次经历以的时间t
说明:LUR算法是内存优化中最好的算法
2.4.3LFU算法
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
维度:使用次数
常识:计算机左移 扩大倍数
计算机右移 缩小倍数
2.4.4随机算法
随机删除数据
2.4.5TTL算法
说明:将剩余存活时间排序,将马上要被删除的数据提前删除。
2.4.6redis默认的内存优化策略
说明:redis中采用的策略定期删除+惰性删除策略
说明:
1.定期删除:redis默认每隔100ms检查是否有过期的key,检查是是随机的方法进行检查(不是检查所有的数据,因为效率太低了)
存在的问题:由于数据众多,可能抽取时间没有被选中,可能出现改数据已经到了超时时间,如果已经超时,但是redis并没有马上删除数据
2.惰性策略:当用户获取key的时候,首先检查数据是否已经过了超时时间,如果已经超时,则删除数据,
存在的问题:由于数据众多,用户不可能将所有的内存数据都get一遍,必然会出现需要删除的数据一直保留在内存中的现象,占用内存资源。
3.可以采用上述的内存优化手段,主动的删除
内存优化算法说明:
1.volatile-lru 在设定超时时间的数据,采用LRU算法进行优化
2.allkeys-url在所有的数据采用LRU算法进行优化
3.volatile-lfu 在设定了超时时间的数据中采用LFU算法优化
4.allkeys-lfu 在所欲的数据中采用LFU算法进行优化
5.valatile-random在设定超时时间的数据 采用随机算法优化
6.allkeys-random所有数据采用随机算法
7.volatile-ttl设定超时时间的TTL算法
8.noevication不主动删除数据,如果内存溢出则报错返回
注意:进入文件中快速查找要修改的内容使用快捷键 :/+搜素的内容
3.Redis分片机制
3.1业务需求
说明:单台事务redis存储数据容量有限,需要存储海量的数据,则使用单台的redis肯定不能满足要求,为了满足数据扩容的需求可以采用分片的机制实现。
3.2Redis分片机制的实现
3.2.1搭建策略
分别准备3台redis 3679/6380/6381
准备文件目录shards
3.2.2复制配置文件
说明:将redis的配置文件存放到shards目录中
修改配置文件的端口号 依次修改6380/6381
启动3台redis服务器:
redis-server 6379.conf
redis-server 6380.conf
redis-server 6381.conf
校验服务器:
3.2.3redis分片的入门案例
package com.jt.test;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import java.util.ArrayList;
import java.util.List;
public class TestRediShards {
@Test
public void testShards(){
List<JedisShardInfo> shards=new ArrayList<>();
shards.add(new JedisShardInfo("192.168.126.129",6379));
shards.add(new JedisShardInfo("192.168.126.129",6380));
shards.add(new JedisShardInfo("192.168.126.129",6381));
ShardedJedis shardedJedis=new ShardedJedis(shards);
//3台redis当做一台Redis使用,内存容量扩大3倍
shardedJedis.set("shards","redis分片测试");
System.out.println(shardedJedis.get("shards"));
}
}
3.3一致性的hash算法
3.3.1算法介绍
一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 [1] 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 [2] 。
解决了缓存数据怎么存的问题
3.3.2算法说明
常识:
1.如果数据相同,则hash结果必然相同
2.常见hash值由8位16进制数组成,共有多少种可能性?2^32
3.3.3平衡性
平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题 [4] 。
说明:通过虚拟节点实现数据的平衡
3.3.4单调性
单调性是指在新增或者减少节点时不影响系统正常运行
原则:如果节点新增/减少 应该尽可能保证原始数据可能不变
3.3.5分散性
分散性是指数据应该分散的存放在分布式集群中的各个节点(节点自己可以备份的)不必每个节点都存储所有的数据
将数据分散存储如果即使将来服务器宕机,则影响只是一部分,而不是全部
谚语:鸡蛋不要放到一个篮子里
3.4 springboot整合redis分片的机制
3.4.1编辑pro配置文件
3.4.2编辑配置类
3.4.3修改aop中配置
4. redis哨兵机制
4.1Redis分片存在问题
说明:redis分片机制虽然可以实现redis内存扩容,但是redis节点并没有实现高可用,如果节点宕机,则整合redis
分片将不可使用。
4.2redis主从结构的搭建
规定6379主机 /6380/6381 从机
4.2.1复制文件目录
4.2.2删除持久化文件
4.2.3启动3台redis服务器
1.redis-server 6379.conf
2.redis-server 6380.conf
3.redis-server 6381.conf
4.2.4实现redis主从挂载
注意:从机不能写操作只能读操作,主机即可以写操作也可读
命令1:slaveof host port
命令说明:在从机中执行上述命令 挂载的是主机地址
命令2:检查状态命令 info replication
主从结构关系:
4.3Redis哨兵工作原理
4.3.1工作流程图
原理说明:
1.哨兵监控主机运行状态,通过心跳检测机制(PING-PONG)如果连续3次节点没有反应。则断定主机宕机,哨兵开始进行选举
2.哨兵通过连接主机,获取主机的相关配置(包括主从结构),挑选链接当前的从机,根据随机算法挑选出新的主机,并将其他的节点设置为新主机的从机
4.3.2 编辑哨兵的配置文件
1.复制哨兵的配置文件 cp sentinel.conf sentinel
2.关闭保护模式
3.开启后端运行
4.设置哨兵的投票数
5.修改选举的超时时间
6.修改哨兵的状态
4.3.3哨兵的测试
哨兵命令:redis-sentinel sentinel.conf
检查redis服务:
redis高可用的测试
1.关闭redis主机6379
2.等待10秒 检查6380/6381到底谁当了主机
3.重启6379服务器,检查是否充当了新的的主机的从机
4.3.4哨兵的入门案例
@Test
public void test01(){
//定义哨兵的集合信息
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.126.129:26379");
//定义链接池信息
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200); //链接池 最多200个链接
poolConfig.setMaxIdle(20); //最大空闲链接数20
poolConfig.setMinIdle(10); //最小空闲链接数10
JedisSentinelPool pool = new JedisSentinelPool("mymaster",sentinels,poolConfig);
//动态获取jedis链接
Jedis jedis = pool.getResource();
jedis.set("abc", "redis赋值操作");
System.out.println(jedis.get("abc"));
jedis.close();
}
4.4关于分片/哨兵的总结
1.分片机制:可以实现内存数据的扩容,但是本身没有实现高可用的效果
2.哨兵机制:哨兵可以实现redis节点的高可用,但是哨兵本身没有实现高可用的效果
需求:1.不依赖与第三方实现高可用
2.实现内存数据扩容
3.各个节点可以高可用
4.redis集群搭建功能包含上述的3种机制,一般公司中会采用集群的方式部署redis