京淘项目013

1.利用Redis缓存实现商品分类查询

1.1 编辑ItemCatController

@RequestMapping("/list")
	public List<EasyUITree> findItemCatList(Long id){
		
		Long parentId = (id==null?0L:id);  //根据parentId=0 查询一级商品分类信息
		//Long  parentId = 0L;
		//return itemCatService.findItemCatListByParentId(parentId);  //版本号 1.0.2 调用次方法 开发人员为xxxx
		return itemCatService.findItemCatCache(parentId);
	}

1.2 编辑ItemCatService

package com.jt.service;

@Override
	public List<EasyUITree> findItemCatListByParentId(Long parentId){
		QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
		queryWrapper.eq("parent_id",parentId);
		List<ItemCat> itemCatList = itemCatMapper.selectList(queryWrapper);
		List<EasyUITree> treeList = new ArrayList<>();  //先准备一个空集合.
		//需要将数据一个一个的格式转化.
		for(ItemCat itemcat : itemCatList){
			Long id = itemcat.getId();	//获取ID
			String text = itemcat.getName();	//获取文本
			//如果是父级,则默认应该处于关闭状态 closed, 如果不是父级 则应该处于打开状态. open
			String state = itemcat.getIsParent()?"closed":"open";
			//利用构造方法 为VO对象赋值  至此已经实现了数据的转化
			EasyUITree tree = new EasyUITree(id,text,state);
			treeList.add(tree);
		}

		//用户需要返回List
		return treeList;
	}


	@Override
	public List<EasyUITree> findItemCatCache(Long parentId) {
		//1.准备key
		String key = "ITEM_CAT_LIST::" + parentId;
		List<EasyUITree> treeList = new ArrayList<>();
		Long  startTime = System.currentTimeMillis();	//记录开始时间
		//2.判断redis中是否有数据
		if(jedis.exists(key)){
			//表示key已存在不是第一次查询.直接从redis中获取数据.返回数据
			String json = jedis.get(key);
			treeList = ObjectMapperUtil.toObject(json, treeList.getClass());
			Long endTime = System.currentTimeMillis();
			System.out.println("查询缓存的时间为:"+(endTime-startTime)+"毫秒");
		}else{
			//表示key不存在,执行数据库查询
			treeList = findItemCatListByParentId(parentId);
			Long endTime = System.currentTimeMillis();
			System.out.println("查询数据库的时间为:"+(endTime-startTime)+"毫秒");

			//2.将数据转化为json
			String  json = ObjectMapperUtil.toJSON(treeList);
			//3.将返回值结果,保存到redis中. 是否需要设定超时时间???  业务
			jedis.set(key, json);

		}
		return treeList;
	}

2.AOP案例说明

2.1 AOP入门案例

package com.jt.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component  //1.我是一个javaBean
@Aspect     //2.我是一个切面
public class CacheAOP {

    //1.定义切入点表达式
    @Pointcut("bean(itemCatServiceImpl)") //只拦截xxx类中的方法
    public void pointCut(){

    }

    /**
     * 2.定义通知方法
     * 需求:
     *  1.想获取目标方法名称
     *  2.获取目标方法对象
     *  3.获取用户传递的参数
    */

    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        System.out.println("我是前置通知");
        //1.获取类名称
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        //2.获取对象
        Object target = joinPoint.getTarget();
        //3.获取参数
        Object[] objs = joinPoint.getArgs();
        System.out.println("类名名称:"+className);
        System.out.println("方法名称:"+methodName);
        System.out.println("对象名称:"+target);
        System.out.println("方法参数:"+objs);
    }

}


2.2 AOP实现缓存业务

2.2.1 自定义注解@CacheFind

说明:该注解由于使用的业务较多,所以将改注解写入Common中.

package com.jt.anno;

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 {

    String key();              //1.设定key 用户自己设定
    int seconds() default  0;  //2.可以指定超时时间,也可以不指定.

}


2.2.2 使用自定义注解

京淘项目013_第1张图片

2.2.3 切换代码执行

京淘项目013_第2张图片

2.3 利用AOP实现缓存业务

package com.jt.aop;

import com.jt.anno.CacheFind;
import com.jt.pojo.ItemCat;
import com.jt.unit.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;
import java.util.List;

@Component  //1.我是一个javaBean
@Aspect     //2.我是一个切面
public class CacheAOP {

    //引入redis缓存配置
    @Autowired
    private Jedis jedis;


    /**
     * AOP缓存实现的业务策略
     * 1.切入点表达式应该拦截  @CacheFind注解
     * 2.通知方法: 环绕通知
     * 注意事项:  如果使用环绕通知,则必须在第一个参数的位置添加 ProceedingJoinPoint
     *
     * 动态获取注解参数的步骤:
     *  1.@annotation(cacheFind)   切入点表达式要求拦截一个类型为cacheFind注解.
     *  2.并且利用连接点为参数中的cacheFind赋值.
     * */

    @Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint, CacheFind cacheFind){
        try {
            Object result = null;
            //1.如何动态获取注解中的数据
            String prekey = cacheFind.key();
            //2.动态获取方法中的参数  将数组转化为字符串
            String args = Arrays.toString(joinPoint.getArgs());
            String key = prekey + "::" + args;
            //3.检验redis中是否有数据
            if(jedis.exists(key)){
                //有缓存  从redis缓存中获取json 之后还原对象返回
                String json = jedis.get(key);
                //target代表这目标方法的返回值类型......
                //动态获取目标方法的返回值类型??   向上造型 不用强转   向下造型
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
               Class returnClass = methodSignature.getReturnType();
                //将json数据转化为对象
                result = ObjectMapperUtil.toObject(json, returnClass);
                System.out.println("AOP实现缓存查询!!!!");

            }else{
                //第一次查询数据库.
                result = joinPoint.proceed();    //执行目标方法.
                System.out.println("AOP执行数据库操作");
                //2.将数据保存到redis中
                String json = ObjectMapperUtil.toJSON(result);
                if(cacheFind.seconds()>0) //表示需要设定超时时间
                    jedis.setex(key, cacheFind.seconds(), json);
                else
                    //不需要超时
                    jedis.set(key, json);
            }
            return result;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new RuntimeException(throwable);  //将检查异常,转化为运行时异常
        }
    }



  /*  //1.定义切入点表达式
    @Pointcut("bean(itemCatServiceImpl)") //只拦截xxx类中的方法
    public void pointCut(){

    }*/

    /**
     * 2.定义通知方法
     * 需求:
     *  1.想获取目标方法名称
     *  2.获取目标方法对象
     *  3.获取用户传递的参数
    */

   /* @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        System.out.println("我是前置通知");
        //1.获取类名称
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        //2.获取对象
        Object target = joinPoint.getTarget();
        //3.获取参数
        Object[] objs = joinPoint.getArgs();
        System.out.println("类名名称:"+className);
        System.out.println("方法名称:"+methodName);
        System.out.println("对象名称:"+target);
        System.out.println("方法参数:"+objs);
    }*/
}


2.4 AOP缓存注解案例2

2.4.1 业务描述

业务说明:用户在查询商品列表时.由于ajax业务调用动态的获取商品分类名称进行数据的展现.每次获取都需要查询数据库性能低.
优化策略: 利用Redis缓存实现.
京淘项目013_第3张图片

2.4.2 代码优化

京淘项目013_第4张图片

3. redis常见面试题

3.1 缓存穿透

特点: 用户高并发环境下,访问数据库中根本不存在的数据.
影响:由于用户高并发访问,则数据库可能存在宕机的风险.
京淘项目013_第5张图片

3.2 缓存击穿

说明: 由于用户高并发的访问. 访问的数据刚开始有缓存,但是由于特殊原有 导致缓存失效.(数据’ ‘单个’ ’)
京淘项目013_第6张图片

3.3缓存雪崩

说明: 由于高并发的环境下.大量的用户访问服务器. redis中有大量的数据在同一时间超时(删除).
解决方案:不要同一时间删除数据.
京淘项目013_第7张图片

3.4 Redis持久化问题

3.4.1 问题说明

说明:Redis中的数据都保存在内存中.如果服务关闭或者宕机则内存资源直接丢失.导致缓存失效.

3.4.2 持久化原理说明

说明:Redis中有自己的持久化策略.Redis启动时根据配置文件中指定的持久化方式进行持久化操作. Redis中默认的持久化的方式为RDB模式.

3.4.3 RDB模式

特点说明:
1.RDB模式采用定期持久化的方式. 风险:可能丢失数据.
2.RDB模式记录的是当前Redis的内存记录快照. 只记录当前状态. 持久化效率最高的
3.RDB模式是默认的持久化方式.

持久化命令:
命令1: save 同步操作. 要求记录马上持久化. 可能对现有的操作造成阻塞
名来2: bgsave 异步操作. 开启单独的线程实现持久化任务.

持久化周期:
save 900 1 在900秒内,如果执行一次更新操作,则持久化一次.
save 300 10 在300秒内,如果执行10次更新操作,则持久化一次.
save 60 10000 在60秒内,如果执行10000次更新操作,则持久化一次.
save 1 1 ???不可以 容易阻塞 性能太低.不建议使用.
用户操作越频繁,则持久化周期越短.

3.4.4 AOF模式

特点:
1.AOF模式默认是关闭状态 如果需要则手动开启.
2.AOF能够记录程序的执行过程 可以实现数据的实时持久化. AOF文件占用的空间较大.回复数据的速度较慢.
3.AOF模式开启之后.RDB模式将不生效.

京淘项目013_第8张图片

3.4.5 redis中如何选择持久化方式

思路: 如果允许数据少量的丢失,则首选RDB.(快),如果不允许数据丢失则使用AOF模式.

3.4.6 情景题

小张在双11前夜误操作将Redis服务器执行了flushAll命令. 问项目经理应该如何解决??

A: 痛批一顿 ,让其提交离职申请.
B: 批评教育, 让其深刻反省,并且请主管 捏脚.
C:项目经理快速解决.并且通知全部门注意.

解决方案:
修改aof文件中的命令.删除flushAll之后重启redis即可.

3.5 Redis内存优化策略

3.5.1 修改Redis内存

京淘项目013_第9张图片

3.5.2 场景说明

Redis运行的空间是内存.内存的资源比较紧缺.所以应该维护redis内存数据,将改让redis保留热点数据.

3.5.3 LRU算法

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

3.5.3 LFU算法

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

3.5.4 RANDOM算法

随机算法

3.5.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 内存满了 不做任何操作.报错返回.
京淘项目013_第10张图片

你可能感兴趣的:(京淘项目)