京淘專案013

2020-08-14 19:09:35

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<EasyUITree>
		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 使用自定義註解

在这里插入图片描述

2.2.3 切換程式碼執行

在这里插入图片描述

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快取實現.
在这里插入图片描述

2.4.2 程式碼優化

在这里插入图片描述

3. redis常見面試題

3.1 快取穿透

特點: 使用者高併發環境下,存取數據庫中根本不存在的數據.
影響:由於使用者高併發存取,則數據庫可能存在宕機的風險.
在这里插入图片描述

3.2 快取擊穿

說明: 由於使用者高併發的存取. 存取的數據剛開始有快取,但是由於特殊原有 導致快取失效.(數據’ ‘單個’ ’)
在这里插入图片描述

3.3快取雪崩

說明: 由於高併發的環境下.大量的使用者存取伺服器. redis中有大量的數據在同一時間超時(刪除).
解決方案:不要同一時間刪除數據.
在这里插入图片描述

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模式將不生效.

AOF配置:

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記憶體

在这里插入图片描述

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 記憶體滿了 不做任何操作.報錯返回.
在这里插入图片描述