@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);
}
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;
}
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);
}
}
說明:該註解由於使用的業務較多,所以將改註解寫入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.可以指定超時時間,也可以不指定.
}
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);
}*/
}
業務說明:使用者在查詢商品列表時.由於ajax業務呼叫動態的獲取商品分類名稱進行數據的展現.每次獲取都需要查詢數據庫效能低.
優化策略: 利用Redis快取實現.
特點: 使用者高併發環境下,存取數據庫中根本不存在的數據.
影響:由於使用者高併發存取,則數據庫可能存在宕機的風險.
說明: 由於使用者高併發的存取. 存取的數據剛開始有快取,但是由於特殊原有 導致快取失效.(數據’ ‘單個’ ’)
說明: 由於高併發的環境下.大量的使用者存取伺服器. redis中有大量的數據在同一時間超時(刪除).
解決方案:不要同一時間刪除數據.
說明:Redis中的數據都儲存在記憶體中.如果服務關閉或者宕機則記憶體資源直接丟失.導致快取失效.
說明:Redis中有自己的持久化策略.Redis啓動時根據組態檔中指定的持久化方式進行持久化操作. Redis中預設的持久化的方式爲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 ???不可以 容易阻塞 效能太低.不建議使用.
使用者操作越頻繁,則持久化週期越短.
特點:
1.AOF模式預設是關閉狀態 如果需要則手動開啓.
2.AOF能夠記錄程式的執行過程 可以實現數據的實時持久化. AOF檔案佔用的空間較大.回覆 回復數據的速度較慢.
3.AOF模式開啓之後.RDB模式將不生效.
思路: 如果允許數據少量的丟失,則首選RDB.(快),如果不允許數據丟失則使用AOF模式.
小張在雙11前夜誤操作將Redis伺服器執行了flushAll命令. 問專案經理應該如何解決??
A: 痛批一頓 ,讓其提交離職申請.
B: 批評教育, 讓其深刻反省,並且請主管 捏腳.
C:專案經理快速解決.並且通知全部門注意.
解決方案:
修改aof檔案中的命令.刪除flushAll之後重新啓動redis即可.
Redis執行的空間是記憶體.記憶體的資源比較緊缺.所以應該維護redis記憶體數據,將改讓redis保留熱點數據.
LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換演算法,選擇最近最久未使用的頁面予以淘汰。該演算法賦予每個頁面一個存取欄位,用來記錄一個頁面自上次被存取以來所經歷的時間 t,當须淘汰一個頁面時,選擇現有頁面中其 t 值最大的,即最近最少使用的頁面予以淘汰。
維度: 自上一次使用的時間T
最爲理想的記憶體置換演算法.
LFU(least frequently used (LFU) page-replacement algorithm)。即最不經常使用頁置換演算法,要求在頁置換時置換參照計數最小的頁,因爲經常使用的頁應該有一個較大的參照次數。但是有些頁在開始時使用次數很多,但以後就不再使用,這類頁將會長時間留在記憶體中,因此可以將參照計數暫存器定時右移一位,形成指數衰減的平均使用次數。
least frequently used (LFU) page-replacement algorithm
即最不經常使用頁置換演算法,要求在頁置換時置換參照計數最小的頁,因爲經常使用的頁應該有一個較大的參照次數。但是有些頁在開始時使用次數很多,但以後就不再使用,這類頁將會長時間留在記憶體中,因此可以將參照計數暫存器定時右移一位,形成指數衰減的平均使用次數。
維度: 參照次數
隨機演算法
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 記憶體滿了 不做任何操作.報錯返回.