AOP与Redis缓存实现

JayX

1. AOP实现缓存业务

1.1 业务需求

1). 自定义注解 @CacheFind(key=“xxx”,second=-1)
2). 使用自定义注解 标识业务方法 将方法的返回值保存到缓存中.
3). 利用AOP 拦截注解 利用环绕通知方法实现业务

1.2 自定义注解@CacheFind

image.png

1.3 注解标识

image.png

1.4 编辑AOP

package com.jt.aop;

import com.jt.anno.CacheFind;
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.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.lang.reflect.Method;
import java.util.Arrays;

/*@Service
@Controller
@Repository*/
@Component  //组件 将类交给spring容器管理
@Aspect     //表示我是一个切面
public class RedisAOP {

    @Autowired
    private Jedis jedis;

    /*
    * 实现AOP业务调用
    * 1.拦截指定的注解
    * 2.利用环绕通知实现
    * 实现步骤:
    *       1.获取KEY  必须先获取注解 从注解中获取key?
    *       2.校验redis中是否有值
    *     *
    * 3.知识点补充:
    *   指定参数名称进行传值,运行期绑定参数类型完成注解的拦截
    *   joinPoint必须位于参数的第一位.
    */
    @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 returnType = methodSignature.getReturnType();

            result = ObjectMapperUtil.toObj(json,returnType);
            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();
            }
        }

        return result;
    }


    /**
     *   //1.获取key 注解  方法对象  类 方法名称  参数
     *         Class targetClass = joinPoint.getTarget().getClass();
     *         //2.获取方法对象
     *         String methodName = joinPoint.getSignature().getName();
     *         Object[] args = joinPoint.getArgs();
     *         Class[] classArgs = new Class[args.length];
     *         for(int i=0;i<args.length;i++){
     *             classArgs[i] = args[i].getClass();
     *         }
     *         try {
     *             //反射实例化对象
     *             Method method = targetClass.getMethod(methodName,classArgs);
     *             CacheFind cacheFind = method.getAnnotation(CacheFind.class);
     *             String key = cacheFind.key();
     *             System.out.println(key);
     *         } catch (NoSuchMethodException e) {
     *             e.printStackTrace();
     *         }
     */


    //公式 aop = 切入点表达式   +   通知方法
    //@Pointcut("bean(itemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.*)")
    //@Pointcut("execution(* com.jt.service.*.*(..))")   //.* 当前包的一级子目录
   /* @Pointcut("execution(* com.jt.service..*.*(..))")  //..* 当前包的所有的子目录
    public void pointCut(){

    }*/

    //如何获取目标对象的相关参数?
    //ProceedingJoinPoint is only supported for around advice
   /* @Before("pointCut()")
    public void before(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);
    }*/
}

2. 关于Redis常规属性

2.1 Redis中持久化策略-RDB (Redis DataBase)

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 关于持久化配置

打开redis.conf文件:

cd  /usr/local/src/redis/            #进入redis目录
vim  redis.conf                      #打开redis.conf文件
:set  nu                             #显示行号
:/save                               #查找save
save 900 1900秒内,用户执行了一次更新操作时,那么就持久化一次
save 300 10300秒内,用户执行了10次更新操作. 那么就持久化一次
save 60 1000060秒内,用户执行了10000次的更新操作,则持久化一次
save 1 11秒内 1次更新 持久化一次!! 性能特别低

image.png

2.1.4 关于持久化文件名称设定

默认的条件下,持久化文件名称 dump.rdb

image.png

2.1.5 文件存储目录

./代表当前文件目录. 意义使用绝对路径的写法

image.png

2.2 Redis中持久化策略-AOF (Append-only file)

2.2.1 AOF特点

1).AOF模式默认的条件下是关闭状态.需要手动开启.
2).AOF模式记录的是用户的操作过程. 可以实现实时持久化.保证数据不丢失.
3).AOF模式维护的持久化文件占用的空间较大.所以持久化效率不高. 并且需要定期的维护持久化文件.
4).AOF模式一旦开启,则redis以AOF模式为主 读取的是AOF文件.

2.2.2 AOF配置

1).开启AOF模式

将appendonly中的 no 修改为 yes

:/appendonly 

image.png
image.png

2).持久化策略
always: 用户更新一次,则持久化一次.
everysec: 每秒持久化一次 效率更高
no: 不主动持久化. 操作系统有关. 几乎不用.
image.png

2.3 关于Redis面试题

2.3.1 关于flushAll操作

业务场景:
小丽是一个的实习生.你是他的项目主管. 由于小丽业务不熟,在生产环境中无意执行了flushAll操作. 问如何补救??
场景1: redis中的服务只开启了默认的持久策略 RDB模式.

解决方案:

场景一:
redis中的服务开启了AOF模式.

1.关闭现有的redis服务器.
2.检查RDB文件是否被覆盖,如果文件没有覆盖,则重启redis即可.(希望渺茫)
3.如果flushAll命令,同时执行了save操作,则RDB模式无效.

cd  /usr/local/src/redis/shards/
redis-cli  -p  6379  shutdown
vim  dump.rdb

场景二:
redis中的服务开启了AOF模式.

解决方案:
1.关闭redis服务器.
2.编辑redis 持久化文件,将flushAll命令删除,保存退出.
3.重启redis服务器

cd  /usr/local/src/redis/shards/
redis-cli  -p  6379  shutdown
vim  appendonly.aof

一般条件下: RDB模式和AOF模式都会开启. 通过save命令执行rdb持久化方式.

2.3.2 单线程Redis为什么快

1).redis运行环境在内存中,纯内存操作.
2).单线程操作 避免频繁的上下文切换. 避免了开关链接的开销.
3).采用了非阻塞I/O(BIO|NIO) 多路复用的机制(动态感知).
image.png

4). Redis最新版本 6.0版本 6.0以前的版本都是单线程操作方式. 6.0以后支持多线程操作方式. (执行时依旧是单线程操作).

2.4 关于Redis内存优化策略

2.4.1 业务场景

如果频繁使用redis,不停的向其中保存数据,并且不做删除操作,则内存必然溢出. 能否优化内存策略.
能否自动的删除不用的数据,让redis中保留热点数据!!!.

2.4.2 LRU算法

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

说明:LRU算法是内存优化中最好用的算法.

2.4.3 LFU算法

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

2.4.4 随机算法

随机删除数据.

2.4.5 TTL算法

说明:将剩余存活时间排序,将马上要被删除的数据,提前删除.
image.png

2.4.6 Redis默认的内存优化策略

Redis中采用的策略定期删除+惰性删除策略

说明:

  1. 定期删除策略: redis默认每隔100ms 检查是否有过期的key, 检查时随机的方式进行检查.(不是检查所有的数据,因为效率太低.)

问题: 由于数据众多,可能抽取时没有被选中.可能出现 该数据已经到了超时时间,但是redis并没有马上删除数据.

  1. 惰性删除策略: 当用户获取key的时候,首先检查数据是否已经过了超时时间. 如果已经超时,则删除数据.

问题: 由于数据众多, 用户不可能将所有的内存数据都get一遍.必然会出现 需要删除的数据一直保留在内存中的现象.占用内存资源.
3.可以采用上述的内存优化手段,主动的删除.

内存优化算法说明:

volatile-lru在设定超时时间的数据 采用LRU算法进行优化
allkeys-lru在所有的数据采用LRU算法进行优化
volatile-lfu在设定了超时时间的数据中采用LFU算法优化
allkeys-lfu在所有的数据中采用LFU算法进行优化
volatile-random在设定了超时时间的数据 采用随机算法
allkeys-random所有数据采用 随机算法
volatile-ttl设定超时时间的TTl算法
noeviction不主动删除数据,如果内存溢出则报错返回

image.png

3. Redis分片机制

3.1 业务需求

说明: 单台redis存储的数据容量有限的. 如果需要存储海量的缓存数据,则使用单台redis肯定不能满足要求.为了满足数据扩容的需求.则可以采用分片的机制实现.
image.png

3.2 Redis分片机制实现

分片的目的: 为了给redis内存进行扩容。

3.2.1搭建策略

分别准备3台redis 6379/6380/6381

3.2.2 准备文件目录

image.png

3.2.2 复制配置文件

说明: 将redis的配置文件放到shards目录中.
image.png

修改配置文件端口号,依次修改 6380/6381
image.png

启动3台redis:
redis-server 6379.conf
redis-server 6380.conf
redis-server 6381.conf

校验服务器:
image.png

3.2.3 Redis分片入门案例

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 TestRedisShards {

    @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当做1台使用  内存容量扩大3倍.  79/80/81???
        shardedJedis.set("shards", "redis分片测试");
        System.out.println(shardedJedis.get("shards"));
    }
}

3.3 一致性hash算法

3.3.1 算法介绍

一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题 。在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题。

3.3.2 算法说明

常识:

  1. 如果数据相同,则hash结果必然相同.
  2. 常见hash值 由8位16进制数组成. 共用多少种可能性? 2^32

image.png

3.3.3 平衡性

平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题。
说明:通过虚拟节点实现数据的平衡
在这里插入图片描述

3.3.4 单调性

单调性是指在新增或者删减节点时,不影响系统正常运行。
原则: 如果节点新增/减少 应该尽可能保证原始数据尽可能不变.

3.3.5 分散性

分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据
将数据分散存储,即使将来服务器宕机,则影响只是一部分,.而不是全部.
谚语: 鸡蛋不要放到一个篮子里.
在这里插入图片描述

3.3.6 负载(Load)

负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

3.4 SpringBoot整合Redis分片机制

3.4.1 编辑 properties 配置文件

# 准备redis节点信息
redis.host=192.168.126.129
redis.port=6379

# 准备3台redis
redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381

3.4.2 编辑配置类

package com.jt.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import java.util.ArrayList;
import java.util.List;
@Configuration //表示一个配置类 一般会与@bean注解一起使用
@PropertySource("classpath:redis.properties") //导入配置文件
public class RedisConfig {
    @Value("${redis.nodes}")
    private String nodes;
    @Bean
    public ShardedJedis shardedJedis(){
        List<JedisShardInfo> shards = new ArrayList<>();
        String[] nodeArray = nodes.split(",");
        for (String node:nodeArray) { //host:node
            //分割出ip
            String host = node.split(":")[0];
            //分割出端口,并转为int类型
            int port = Integer.parseInt(node.split(":")[1]);
            shards.add(new JedisShardInfo(host,port));
        }
        return new ShardedJedis(shards);
    }
}

3.4.3 修改AOP中的配置

@Component //组件 将类交给spring容器管理
@Aspect //表示我是一个切面
public class RedisAOP {

//    @Autowired
//    private Jedis jedis;  //使用单台中redis
      @Autowired
      private ShardedJedis jedis; //使用分片多台redis

4. Redis哨兵机制

4.1 Redis分片存在问题

说明:Redis分片机制,虽然可以实现Redis Redis内存扩容,但是redis 节点并没有实现高可用.如果节点宕机,则整合redis分片将不可使用.

4.2 Redis主从结构搭建

规定: 6379主机 /6380/6381 从机

4.2.1 复制文件目录

如果redis已开启,先关闭
进入目录,复制

cd  /usr/local/src/redis/
cp  -r  shards  sentinel

在这里插入图片描述

4.2.2 删除持久化文件

cd  sentinel
rm  -f  appendonly.aof  aump.rdb

在这里插入图片描述

4.2.3 启动3台Redis服务器

1.redis-server 6379.conf
2.redis-server 6380.conf
3.redis-server 6381.conf

ps  -ef  |  grep  redis

在这里插入图片描述

4.2.4 实现redis主从挂载

进入从机

redis-cli  -p  6380

命令1: slaveof host port (slaveof 主机IP 端口)

slaveof  192.168.126.129  6379
info  replication

命令说明: 在从机中执行上述命令 挂载的是主机的地址.
在这里插入图片描述

命令2: info replication
在这里插入图片描述

挂载第二台从机

redis-cli  -p  6381
slaveof  192.168.126.129  6379
info  replication
exit
redis-cli  -p  6379
info  replication

主从结构关系:
在这里插入图片描述

4.3 Redis哨兵工作原理

4.3.1 工作流程图

在这里插入图片描述
原理说明:
1.哨兵监控主机的运行的状态. 通过心跳检测机制(PING-PONG)如果连续3次节点没有响应,则断定主机宕机,哨兵开始进行选举.
2.哨兵通过链接主机,获取主机的相关配置信息(包含主从结构),挑选链接当前主机的从机.根据随机算法挑选出新的主机. 并且将其他的节点设置为新主机的从.

4.3.2 编辑哨兵配置文件

1).复制哨兵的配置文件
在这里插入图片描述
2).关闭保护模式
在这里插入图片描述
3).开启后端运行
在这里插入图片描述
4).设定哨兵的投票数
在这里插入图片描述
5).修改选举的超时时间
在这里插入图片描述
6).修改哨兵的状态
在这里插入图片描述

4.3.3 哨兵测试

哨兵命令: redis-sentinel sentinel.conf
检查redis服务:
在这里插入图片描述
redis高可用测试:
1.关闭redis主机6379
2.等待10秒 检查6380/6381到底谁是主机.
3.重启6379服务器,检查是否充当了新主机的从

阅读 350
4 声望
0 粉丝
0 条评论
你知道吗?

4 声望
0 粉丝
宣传栏