1.利用Redis緩存實作商品分類查詢
1.1 編輯ItemCatController
(1)

(2)
(3)
2.AOP案例說明
以上功能寫法代碼耦合性太高了,如果将來出現一個比redis更好的插件,那修改的時候就要把含有redis的所有地方都修改了;這樣就不科學了,是以引入AOP,降低程式的耦合性
2.1 業務描述
說明:由于将代碼直接寫Service業務層中,會導緻代碼的耦合性高,不便于擴充,為了實作代碼的松耦合可以利用AOP
2.2AOP知識回顧
2.2.1 什麼是AOP
2.2.2 通知方法
2.2.3 切入點表達式
2.2.4 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.3 AOP實作緩存業務
2.3.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.3.2 使用自定義注解
2.3.3 切換代碼執行
2.3.4 利用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
*
* 動态擷取注解參數的步驟:
* [email protected](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);
}*/
}
3. redis常見面試題
3.1 緩存穿透
特點: 使用者高并發環境下,通路資料庫中根本不存在的資料.
影響:由于使用者高并發通路,資料庫每次查詢都是白忙活一場,則資料庫可能存在當機的風險.
3.2 緩存擊穿
說明: 由于使用者高并發的通路. 通路的資料剛開始有緩存,但是由于特殊原有 導緻緩存失效.(資料’‘單個’’)
(資料‘單個’表示少量資料的意思)
解決方案:
使用多個redis,即如圖串聯兩個redis,當使用者通路第一個redisA的時候沒有資料,就去通路第二個redis B,如果有資料就直接由第二個redis傳回資料,(前提是要設定兩個redis緩存清除有一定的時間差,即保證兩個redis緩存不在同一時間清除,也就是緩存的逾時時間要設定不一樣),這樣資料通路的時候,至少有一個redis中有資料;如果兩個redis都沒有資料,進而才通路資料庫
3.3緩存雪崩
說明: 由于高并發的環境下.大量的使用者通路伺服器. redis中有大量的資料在同一時間逾時(删除).
解決方案:(設定兩個redis)不要同一時間删除資料.
3.4 Redis持久化問題
3.4.1 問題說明
說明:Redis中的資料都儲存在記憶體中.如果服務關閉或者當機則記憶體資源直接丢失.導緻緩存失效.
這就引入了redis持久化的問題!!!
3.4.2 持久化原理說明
說明:Redis中有自己的持久化政策.Redis啟動時根據配置檔案中指定的持久化方式進行持久化操作.
Redis中持久化的方式有兩種:
(1)RDB模式 (2)AOF模式
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配置:
持久化周期配置:
appendfsync always 實時持久化.
appendfsync everysec 每秒持久化一次 略低于rdb模式
appendfsync no 自己不主動持久化(被動:由作業系統解決)
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保留熱點資料.
redis記憶體優化算法:①LRU算法②LFU算法③RANDOM算法
3.5.3 LRU算法
LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換算法,選擇最近最久未使用的頁面予以淘汰。該算法賦予每個頁面一個通路字段,用來記錄一個頁面自上次被通路以來所經曆的時間 t,當須淘汰一個頁面時,選擇現有頁面中其 t 值最大的,即最近最少使用的頁面予以淘汰。
次元: 自上一次使用的時間T
最為理想的記憶體置換算法.
3.5.4 LFU算法
LFU(least frequently used (LFU) page-replacement algorithm)。即最不經常使用頁置換算法,要求在頁置換時置換引用計數最小的頁,因為經常使用的頁應該有一個較大的引用次數。但是有些頁在開始時使用次數很多,但以後就不再使用,這類頁将會長時間留在記憶體中,是以可以将引用計數寄存器定時右移一位,形成指數衰減的平均使用次數。
least frequently used (LFU) page-replacement algorithm
即最不經常使用頁置換算法,要求在頁置換時置換引用計數最小的頁,因為經常使用的頁應該有一個較大的引用次數。但是有些頁在開始時使用次數很多,但以後就不再使用,這類頁将會長時間留在記憶體中,是以可以将引用計數寄存器定時右移一位,形成指數衰減的平均使用次數。
次元: 引用次數
3.5.5 RANDOM算法
随機算法
3.5.6 記憶體政策優化
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 記憶體滿了 不做任何操作.報錯傳回.