关于redis:AOP与Redis缓存实现

39次阅读

共计 6801 个字符,预计需要花费 18 分钟才能阅读完成。

1. AOP 实现缓存业务

1.1 业务需要

1). 自定义注解 @CacheFind(key=“xxx”,second=-1)
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.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

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 300 10 300 秒内, 用户执行了 10 次更新操作. 那么就长久化一次
3.save 60 10000 60 秒内, 用户执行了 10000 次的更新操作, 则长久化一次.
4.save 1 1 1 秒内 1 次更新 长久化一次!! 性能特地低.

2.1.4 对于长久化文件名称设定

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

2.1.5 文件存储目录

./ 代表以后文件目录. 意义应用绝对路径的写法.

2.2 Redis 中长久化策略 -AOF

2.2.1 AOF 特点

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

2.2.2 AOF 配置

1). 开启 AOF 模式

2). 长久化策略
always: 用户更新一次, 则长久化一次.
everysec: 每秒长久化一次 效率更高
no: 不被动长久化. 操作系统无关. 简直不必.

2.3 对于 Redis 面试题

2.3.1 对于 flushAll 操作

业务场景:
小丽是一个特地丑陋的实习生. 你是他的我的项目主管. 因为小丽业务不熟, 在生产环境中无心执行了 flushAll 操作. 问如何补救??
场景 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(BIO|NIO) 多路复用的机制(动静感知).

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.

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

2.4.3 LFU 算法

LFU(least frequently used (LFU) page-replacement algorithm)。即 最不常常应用页置换算法 ,要求在页置换时置换援用计数最小的页,因为常常应用的页应该有一个较大的援用次数。然而有些页在开始时应用次数很多,但当前就不再应用,这类页将会长工夫留在内存中,因而能够将援用计数寄存器定时 右移 一位,造成指数衰减的均匀应用次数。
维度: 援用次数
常识: 计算机左移 扩充倍数
计算机右移 放大倍数

2.4.4 随机算法

随机删除数据.

2.4.5 TTL 算法

阐明: 将残余存活工夫排序, 将马上要被删除的数据, 提前删除.

2.4.6 Redis 默认的内存优化策略

阐明 1: Redis 中采纳的策略定期删除 + 惰性删除策略
阐明 2:
1. 定期删除: redis 默认 每隔 100ms 查看是否有过期的 key, 查看时随机的形式进行查看.(不是查看所有的数据, 因为效率太低.)
问题: 因为数据泛滥, 可能抽取时没有被选中. 可能呈现 该数据曾经到了超时工夫, 然而 redis 并没有马上删除数据.

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

问题: 因为数据泛滥, 用户不可能将所有的内存数据都 get 一遍. 必然会呈现 须要删除的数据始终保留在内存中的景象. 占用内存资源.
3. 能够采纳上述的内存优化伎俩, 被动的删除.

内存优化算法阐明:
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 算法
8.noeviction 不被动删除数据, 如果内存溢出则报错返回.

3. Redis 分片机制

3.1 业务需要

阐明: 单台 redis 存储的数据容量无限的. 如果须要存储海量的缓存数据, 则应用单台 redis 必定不能满足要求. 为了满足数据扩容的需要. 则能够采纳分片的机制实现.

3.2 Redis 分片机制实现

3.2.1 搭建策略

别离筹备 3 台 redis 6379/6380/6381

3.2.2 筹备文件目录

3.2.2 复制配置文件

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

批改配置文件端口号 顺次批改 6380/6381

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

校验服务器:

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 年由麻省理工学院提出,是一种非凡的哈希算法,目标是解决分布式缓存的问题。[1] 在移除或者增加一个服务器时,可能尽可能小地扭转已存在的服务申请与解决申请服务器之间的映射关系。一致性哈希解决了简略哈希算法在分布式哈希表(Distributed Hash Table,DHT) 中存在的动静伸缩等问题 [2]。

3.3.2 算法阐明

常识:

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

正文完
 0