SpringBoot part4 day12

1 AOP实现redis缓存

1.1 业务需求

需要通过自定义注解的形式动态实现缓存操作.通过注解获取其中的key.超时时间.

1.1.1自定义注解

package com.jt.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheFind {
    public String key();
    public int seconds() default 0;//不需要设定超时时间
}

1.1.2添加注解
在这里插入图片描述
1.1.3编辑CacheAop
运行时会动态获取路径
在这里插入图片描述
在这里插入图片描述

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.util.Arrays;

@Aspect      //标识是一个切面
@Component  //交给容器管理
public class CacheAOP {
    @Autowired
    private Jedis jedis;
      /**
     * AOP缓存实现的业务策略
     *1.切入点表达式应该拦截 @CacheFind
     * 2.通知方法:环绕通知
     * 注意事项:如果使用环绕通知,则必须在第一个参数的位置添加ProceedingJoinPoint
     * 
     * 动态获取注解参数的步骤:
     *1. @annotation(cacheFind)  切入点表达式要求拦截一个类型为CacheFind名称为cacheFind的注解
     *2.并且利用连接点为参数中的cacheFind赋值
     * */
   // @Around("@annotation(com.jt.annotation.CacheFind)")
    @Around("@annotation(cacheFind)")
    public Object doAround(ProceedingJoinPoint joinPoint, CacheFind cacheFind){
        Object result=null;
        //1.获取key前缀
        String key=cacheFind.key();
        //2.获取方法参数
        String argsString=Arrays.toString(joinPoint.getArgs());
         key=key+"::"+argsString;
        System.out.println(key);
        System.out.println("环绕通知开始");
        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("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("执行数据库调用");
            }

        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new RuntimeException(throwable);
        }
        System.out.println("环绕通知结束");
        return result;
    }











  /*  //切面=切入点表达式+通知方法
    //表达式1: @Pointcut("bean(itemCatServiceImpl)") ItemCatServiceImpl类
    //表达式2: @Pointcut("within(com.jt.service.*)")
    //表达式3: @Pointcut("execution(* com.jt.service.*add(..))") .*一级包下的类  ..*所有子孙后代的包和类
     //返回值类型任意   com.jt.service.下所有add方法
    //execution(* com.jt.service..*.*(long))  参数类型严格区分大小写
    @Pointcut("bean(itemCatServiceImpl)")
    public void pointCut(){};
  *//**
   *joinPoint  代表连接点对象,一般使用于除around之外的通知
   *ProceedingJoinPoint  只用于around通知
   * *//*
   @Before("pointCut()")
    public void before(JoinPoint joinPoint){
       //1.获取目标对象
       Object target=joinPoint.getTarget();
       System.out.println(target);
       //2.获取目标对象的路径  包名.类名
       String className=joinPoint.getSignature().getDeclaringTypeName();
       String method=joinPoint.getSignature().getName();
       System.out.println("目标方法的路径:"+(className+"."+method));
       //获取带参方法的参数
       System.out.println(Arrays.toString(joinPoint.getArgs()));
   }

   @Pointcut("bean(itemCatServiceImpl)")
    public void testAop(){}
   @Around("testAop()")
    public Object testAround(ProceedingJoinPoint joinpoint){
       System.out.println("环绕通知执行");
       Object data=null;
       try {
           data=joinpoint.proceed();//执行目标方法
       } catch (Throwable throwable) {
           throwable.printStackTrace();
       }
       return data;
   }
*/

}

tomcat 连接缓存的配置,缓存从本地库中获取数据

在这里插入图片描述
在这里插入图片描述

1.2商品分类名称优化

优化商品分类名称的名称. 在业务层添加缓存注解.

在这里插入图片描述

2 Redis属性说明

2.1Redis持久化策略

2.1.1为什么要持久化
Redis中的记录都保存在内存中,如果内存断电或者服务器宕机,则内存数据直接丢失.业务中不允许发生. 所以需要将数据定期进行维护.

2.1.2 RDB模式
说明: RDB模式是Redis的默认的持久化策略.无需手动的开启.
特点:
1.Redis会定期的执行RDB持久化操作. 缺点:可能导致内存数据丢失.
2.RDB记录的是内存数据的快照,并且后续的快照会覆盖之前的快照.每次只保留最新数据.效率更高.

命令:
1).save 命令 要求立即执行持久化操作 save会造成线程的阻塞.
2).bgsave 命令 后台执行持久化操作 后台运行不会造成阻塞. 异步操作, 不能保证立即执行

在这里插入图片描述
命令:
1)save 命令
在这里插入图片描述
save会造成线程的阻塞,当用户同时执行set操作会有线程问题
2)bgsave
在这里插入图片描述
删除持久化文件关闭会自动生成持久化文件
在这里插入图片描述
若关闭redis再删除,则不能恢复数据
bgsave 后台执行持久化操作 后台运行不会造成阻塞,异步操作,不能保证立即执行
2.1.3 AOF模式
说明: AOF模式默认条件下是关闭的,需要手动的开启,如果开启了AOF模式则RDB模式将失效.但是如果手动执行save命令,则也会生成RDB文件.

修改配置文件redis.conf,开启AOF模式
在这里插入图片描述
在这里插入图片描述
重启redis

特点:
1.AOF模式记录的是程序执行的过程,所以可以保证数据不丢失
2.由于AOF记录程序运行的过程,所以整个持久化文件相对较大,所以需要定期维护
2.1.4RDB 与AOF模式持久化对比
1)RDB模式 操作越快持久化周期越短
在这里插入图片描述
save 900 1 如果在900秒内,执行了一次更新操作则持久化一次
save 300 10
save 60 10000 操作越快 ,持久化的周期越短.
2)AOF
appendfsync always 用户执行一次更新操作,则持久化一次 异步操作
appendfsync everysec 每秒操作一次
appendfsync no 不主动操作 一般不用.

2.1.4关于RDB与AOF总结
策略: 如果数据允许少量丢失,首选RDB模式,
如果数据不允许丢失则首选AOF模式.

企业策略: 又要满足效率,同时满足数据不丢失.
主机: 采用RDB模式
从机: 采用AOF模式
2.1.6面试题
题目: 小丽是公司特别漂亮的妹子,误操作将redis服务器执行了flushAll命令,问你作为项目经理如何处理??

A. 训斥一顿,之后HR开除.
B. 秀一下自己的技术,让小丽崇拜 一起过上了幸福的生活

解决方案: 需要将从库中的AOF文件 进行编辑,删除多余的flushAll命令,之后重启redis即可.
之后将从库的aof文件导入到主机,修改主机的配置文件,运行save生成rdb文件后修改配置文件将持久化方式改为rdb
在这里插入图片描述
问题2: 小丽在执行完上述操作之后,由于好奇 误将aof文件一并删除,问如何处理???
答: 杀人祭天!!!
在这里插入图片描述

2.2Redis内存策略

2.2.1LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
判断维度: 时间T
时间维度:距离上一次访问的时间
底层时链表结构
在这里插入图片描述

2.2.2LFU算法

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

2.2.4TTL算法
将剩余时间短的数据提前删除
2.2.5Redis中内存优化策略
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 不做任何操作,只是返回报错信息.
在这里插入图片描述

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.2缓存雪崩

说明: 在缓存服务器中,由于大量的缓存数据失效,导致用户访问的命中率过低.导致直接访问数据库.
问题分析:

  1. flushAll命令可能导致缓存雪崩.
  2. 设定超时时间时,应该采用随机算法
  3. 采用多级缓存可以有效防止.
    在这里插入图片描述

3 Redis分片机制

3.2Redis分片机制的配置

3.2.1配置规划
在这里插入图片描述
3.2.2复制三个配置文件
在这里插入图片描述
3.2.3修改各自端口号
:/port 搜索端口号
:set nu 显示序列号
在这里插入图片描述
3.2.4启动三台redis并检查启动状态
在这里插入图片描述

3.3入门案例

 @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);
        shardedJedis.set("shards", "redis分片操作");

        System.out.println(shardedJedis.get("shards"));

    }

3.4商品目录查询分片机制优化

编辑配置文件

#配置单台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

RedsiConfig文件

@Configuration//标识是一个配置类  一般与@Bean注解连用
@PropertySource("classpath:/properties/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) {
             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);
    }

换注入方式:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值