day---14

1.AOP缓存的实现
1.1 自定义注解
@Target({ElementType.METHOD}) //标识注解使用在方法中
@Retention(RetentionPolicy.RUNTIME) //什么时期有效
public @interface CacheFind {
//key-value方法的返回值
String key(); //要求用户必须指定key
int seconds() default -1; //设定超时时间 -1 无需超时
}
1.2 使用缓存注解
在这里插入图片描述

1.3 编辑RedisAOP
package com.jt.aop;

import com.jt.annotation.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 redis.clients.jedis.Jedis;

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

@Component //将对象交给Spring容器管理
@Aspect //标识AOP切面
public class RedisAOP {

@Autowired
private Jedis jedis;

//通知选择: 是否控制目标方法是否执行.  环绕通知
//切入点表达式: 控制注解 @annotation(语法....)

/**
 * 需求: 如何动态获取注解中的属性值.
 * 原理: 反射机制
 *       获取目标对象~~~~~获取方法对象~~~获取注解  原始API
 *
 * @param joinPoint
 * @return
 * @throws Throwable
 *
 * 向上造型:  父类  = 子类
 * 向下造型:  子类  = (强制类型转化)父类
 *
 * AOP中的语法规范1.:
 *   如果通知方法有参数需要添加,则joinPoint 必须位于第一位.
 *   报错信息: error at ::0 formal unbound in pointcut
 * AOP中的语法规范3:
 *   如果需要动态接受注解对象,则在切入点表达式中直接写注解参数名称即可
 *   虽然看到的是名称,但是解析时变成了包名.类型
 */
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) throws Throwable {
    Object result = null;
    //1.获取key="ITEM_CAT_PARENTID"
    String key = cacheFind.key();
    //2.动态拼接key 获取参数信息
    String args = Arrays.toString(joinPoint.getArgs());
    key += "::" + args;
    //3.redis缓存实现
    if(jedis.exists(key)){
        String json = jedis.get(key);
        //target标识返回值类型????
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Class returnType = methodSignature.getReturnType();
        result = ObjectMapperUtil.toObject(json, returnType);
        System.out.println("AOP缓存查询!!!");
    }else{
        //查询数据库 执行目标方法
        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查询数据库");
    }
    return result;
}

/* @Around("@annotation(com.jt.annotation.CacheFind)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//父转子 需要强转
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
CacheFind cacheFind = method.getAnnotation(CacheFind.class);
String key = cacheFind.key();
System.out.println(“获取key:”+key);
return joinPoint.proceed();
}*/

/*@Around("@annotation(com.jt.annotation.CacheFind)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

    //1.获取目标对象类型
    Class targetClass = joinPoint.getTarget().getClass();
    //2.获取方法
    String name = joinPoint.getSignature().getName();
    Object[] objArgs = joinPoint.getArgs();
    Class[] classArgs = new Class[objArgs.length];
    for (int i=0;i<objArgs.length;i++){
        Object obj = objArgs[i];
        classArgs[i] = obj.getClass();
    }
    Method method = targetClass.getMethod(name,classArgs);
    CacheFind cacheFind = method.getAnnotation(CacheFind.class);
    String key = cacheFind.key();
    System.out.println(key);
    return joinPoint.proceed();
}*/













/**
 * 1.定义切入点表达式
 * bean: 被spring容器管理的对象称之为bean
 * 1.1 bean(bean的ID) 按类匹配  1个
 *      bean(itemCatServiceImpl)
 * 1.2 within(包名.类名) 按类匹配  一堆
 *      within(com.jt.service.*)
 * 1.3 execution(返回值类型 包名.类名.方法名(参数列表))
 *     execution(* com.jt.service..*.*(..))
 *     解释:  返回值为任意类型 com.jt.service包所有的子孙包的类
 *            类中的任意方法,任意参数
 *     execution(Integer com.jt.service..*.add*(int))
 *     execution(int com.jt.service..*.add*(int))
 */

/* @Pointcut(“execution(* com.jt.service….(…))”)
public void pointCut(){

}

//如何理解什么是连接点? 被切入点拦截的方法
//ProceedingJoinPoint is only supported for around advice
//只有环绕通知可以控制目标方法
@Before("pointCut()")
public void before(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String className = joinPoint.getSignature().getDeclaringTypeName();
    Object[] args = joinPoint.getArgs();
    Object target = joinPoint.getTarget();
    System.out.println(methodName);
    System.out.println(className);
    System.out.println(args);
    System.out.println(target);
    System.out.println("我是一个前置通知");
}

@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("环绕开始");
    Object result =  joinPoint.proceed();    //执行下一个通知,目标方法
    System.out.println("环绕结束");
    return result;
}*/

}
2 关于Redis 持久化机制
2.1 业务需求
Redis中的运行环境在内存中, 但是内存特点 断电即擦除.
想法:能否保存redis中的内存数据不丢失呢?
持久化: 将内存数据定期保存到磁盘中.

2.2 RDB模式
2.2.1 关于RDB模式说明
1.Redis 定期 将内存数据保存到RDB文件中.
2.RDB模式 redis默认的规则.
3.RDB模式记录的是内存数据的快照 ,持久化效率更高(只保留最新数据)
4.RDB模式由于定期持久化,可能导致数据丢失.

2.2.2 RDB命令
1: 持久化操作 save 同步操作 可能其他线程陷入阻塞
2: 后端持久化 bgsave 异步操作 用户操作不会陷入阻塞 该操作什么时候完成不清楚.

2.2.3 RDB模式配置
save 900 1 900秒内用户更新1次 则持久化1次
save 300 10 300秒内用户更新10次 持久化 1次
save 60 10000 60秒内用户更新10000次 持久化1次
save 1 1 保证数据安全性 问题:效率极低 阻塞…
如果想让持久化性能更优,则需要通过监控的手段灵活运用.

用户操作越频繁,则持久化周期越短.

2.3 AOF模式
2.3.1 开启AOF模式
默认条件下AOF模式 默认关闭的.
在这里插入图片描述
开启AOF
在这里插入图片描述

2.3.2 AOF模式特点
说明: 当开启AOF策略之后,redis持久化以AOF为主.
特点:

  1. AOF文件默认关闭的,需要手动开启
  2. AOF文件记录的是用户的操作过程.则可以实现实时持久化操作.(几乎不丢数据)
  3. AOF文件做追加的操作,所有持久化文件较大.
  4. AOF持久化时,采用异步的方式进行.
  5. AOF文件需要定期清理.

2.3.3 AOF持久化原则
appendfsync always 用户执行一次操作,持久化一次
appendfsync everysec 每秒持久化一次
appendfsync no 不主动持久化

2.3.4关于AOF与RDB如何选择
业务:
1.如果用户追求速度,允许少量的数据丢失 首选RDB模式. 快
2.如果用户追求数据的安全性. 首选AOF模式.

面试题:
如果你在redis中执行了flushAll命令,如何挽救??
答案: 修改AOF文件中,删除flushAll的命令.重启redis即可.
注意事项: 一般条件下 Redis会开启AOF与RDB 2种模式
常规用法: 一般会配置redis主从结构 主机开启RDB模式 从机开启AOF模式

3 关于Redis 内存优化策略
3.1 关于内存优化的说明
Redis运行环境,在内存中. 但是内存资源有限的.不能一味的扩容.所以需要对内存数据优化.
Redis内存大小的设定:
在这里插入图片描述
最大内存设定:
在这里插入图片描述

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

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

3.4随机算法
说明: 随机生成挑选数据删除.

3.5 TTL算法
说明: 根据设定了超时时间的数据,将马上要超时的数据提前删除.

3.6 算法优化
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算法删除.
noeviction 默认不删除数据. 如果内存满了,则报错返回. 默认策略
在这里插入图片描述
4 关于Redis缓存常见面试题
4.1 什么是缓存穿透
说明: 在高并发的条件下,用户频繁访问一个不存在的数据.导致大量的请求直接发往数据库.导致服务器宕机.
在这里插入图片描述

解决方案:

  1. 只要能够保障用户访问的数据 数据库中一定存在. 布隆过滤器
  2. IP限流 黑名单. 微服务中可以通过API网关进行控制

4.2 什么是缓存击穿
说明: 在高并发条件下 用户频繁访问一个热点数据. 但是该热点数据在缓存中失效.导致用户直接访问数据库.
俗语: 称它病,要它命
在这里插入图片描述

4.3 什么是缓存雪崩
说明: 在高并发的条件下, 有大量的缓存数据失效了. 导致redis缓存服务器命中率太低.从而导致用户直接访问数据库.
主要问题: 热点数据失效.
解决方案:
1.为热点数据设定超时时间时 采用随机数. 10秒+随机数 保障数据不会同时失效.
2. 设定多级缓存
在这里插入图片描述

4.4 布隆过滤器
4.4.1 介绍
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

4.4.2 计算机进制换算
1字节 = 8比特
1kb = 1024字节
1mb = 1024kb
在这里插入图片描述

4.4.3 业务场景
假设数据库中有1000万数据,每条记录大约2kb 如果该数据都是热点数据,则需要多大的内存空间进行存储? 19G数据!!!

问题: 能否优化内存数据的存储呢??? 尽可能少占用内存空间.
想法: 如果使用1个**bit(01)**代表一个数据, 问占用多大空间 1.19M

4.4.4 布隆过滤器原理
核心1: 很长的二进制向量
核心2: 多个hash函数
解决问题: 校验数据是否存在!!!
在这里插入图片描述
1).数据加载过程
在这里插入图片描述
2).数据校验
在这里插入图片描述
3).存在问题
好的布隆算法可以将误判率 降低到 < 0.03%
在这里插入图片描述

5 Redis分片机制
5.1 为什么需要分片
说明: 如果有海量的内存数据需要保存,但是都把数据保存到1个redis中,查询的效率太低.如果这台redis服务器宕机,.则整个缓存将不能使用.
解决方案: 采用redis分片机制.
在这里插入图片描述

5.2 Redis分片搭建
5.2.1 准备配置文件
在这里插入图片描述

5.2.2 修改端口号
说明: 分别将6379/6380/6381的端口号进行配置
在这里插入图片描述

5.2.3 启动三台redis
[root@localhost shards]# redis-server 6379.conf & redis-serve r 6380.conf & redis-server 6381.conf &
校验redis
在这里插入图片描述

5.2.4 redis分片入门案例
/**
* 3台redes key如何存储?? 79/80/81
*/
@Test
public void testShards(){
List 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);
shardedJedis.set(“shards”, “redis分片机制”);
System.out.println(shardedJedis.get(“shards”));
}
5.3 一致性hash算法
5.3.1 介绍
一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 [1] 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 [2] 。
核心知识:

  1. 一致性hash解决了数据与节点的映射关系(数据归谁管理)
  2. 节点增加/减少时,数据可以弹性伸缩.

5.3.2 一致性hash算法原理
在这里插入图片描述

5.3.3 特性-平衡性
说明: 平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题
解决策略: 引入虚拟节点
在这里插入图片描述

5.3.4 特性-单调性
②单调性是指在新增或者删减节点时,不影响系统正常运行 [4] 。
说明: 无论节点增/减,数据都能找到与之匹配的node进行数据的挂载.

5.3.5 特性-分散性
③分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据 [4] 。
谚语: 鸡蛋不要放到一个篮子里.

百度原理图1:
在这里插入图片描述
百度原理图2:
在这里插入图片描述

5.4 SpringBoot整合Redis分片
5.4.1 编辑配置文件

单台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
5.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.*;

import java.util.ArrayList;
import java.util.List;

@Configuration //标识为一个配置类, 一般整合第三方
@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);
    //2.编辑redis配置文件,调整链接数量
    /*JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    jedisPoolConfig.setMinIdle(10); //最小空闲数量
    jedisPoolConfig.setMaxIdle(40); //最大的空闲数量
    jedisPoolConfig.setMaxTotal(1000);
    ShardedJedisPool shardedJedisPool =
            new ShardedJedisPool(jedisPoolConfig, shards);
    return shardedJedisPool.getResource();*/
}

/* @Value(" r e d i s . h o s t " ) p r i v a t e S t r i n g h o s t ; @ V a l u e ( " {redis.host}") private String host; @Value(" redis.host")privateStringhost;@Value("{redis.port}")
private Integer port;

@Bean   //将该方法的返回值,交给Spring容器管理
public Jedis jedis(){

    return new Jedis(host,port);
}*/

}

5.4.3 修改RedisAOP
在这里插入图片描述

6.Redis哨兵机制
6.1 关于Redis分片特点
Redis分片可以实现内存数据的扩容,但是如果节点宕机则直接影响程序的运行.
问题关键: 如果节点宕机,需要实现高可用.

6.2 Redis数据同步配置
6.2.1 复制目录
说明: 将shards目录复制 并且改名为sentinel
在这里插入图片描述

6.2.2 启动3台redis
说明: 1.删除原有的持久化文件
2.启动3台redis
在这里插入图片描述

6.2.3 主从结构搭建
6379 主机
6380/6381 从机
在这里插入图片描述
2.主从挂载命令
slaveof 192.168.126.129 6379
在这里插入图片描述

6.3 Redis哨兵实现
6.3.1 哨兵原理
在这里插入图片描述
说明:
1.当哨兵启动时,会链接redis主节点,获取所有节点的相关信息.
2.当哨兵通过心跳检测机制 PING -PONG 命令校验服务器是否正常.
3.如果哨兵连续3次发现服务器没有响应,则断定当前主机宕机.
4.之后由哨兵采用随机算法挑选其中的一个从机当选主机.并且其他的节点当做新主机的从.

6.3.2 配置哨兵服务
1).复制文件
cp sentinel.conf sentinel/
2).关闭保护模式
在这里插入图片描述

3).开启后台运行
在这里插入图片描述
4).哨兵监控
在这里插入图片描述
5).修改宕机时间
在这里插入图片描述

6.3.3 哨兵命令
1.启动 redis-sentinel sentinel.conf
2.关闭哨兵 ps -ef |grep redis kill -9 PID号
在这里插入图片描述

6.3.4 哨兵高可用测试
1).关闭6379主机,检查从机是否当选主机
2).检查从机的配置文件 是否以后关联了主机
在这里插入图片描述
如果搭建错误,.则需要删除最后一条主从关系.,之后重启服务器.重新搭建.

6.4 SpringBoot整合哨兵
6.4.1 入门案例
/**
* 测试哨兵API
*/
@Test
public void testSentinel(){
Set sets = new HashSet<>();
sets.add(“192.168.126.129:26379”);
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMinIdle(10);
poolConfig.setMaxIdle(40);
poolConfig.setMaxTotal(1000);
JedisSentinelPool pool = new JedisSentinelPool(“mymaster”,sets,poolConfig);
Jedis jedis = pool.getResource();
jedis.set(“AAA”, “您好Redis”);
System.out.println(jedis.get(“AAA”));
jedis.close();
}
6.5 关于Redis分片/哨兵总结
1.分片作用: 扩大内存实现海量数据数据的存储. 缺点: 没有实现高可用
2.哨兵作用: 实现了节点的高可用. 缺点: 1.哨兵不能实现扩容 2.哨兵本身没有高可用
想法: 能否有一种机制 既可以实现内存扩容,又可以实现高可用(不需要第三方)

7.Redis集群规则
7.1关于集群搭建错误说明
1).关闭redis集群
2).删除多余的配置文件
在这里插入图片描述
3).重启redis
4).搭建redis集群

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

健子bui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值