1 直接緩存分頁(yè)列表結(jié)果
這是最簡(jiǎn)單易懂的方案,我們按照不同的分頁(yè)條件查詢出結(jié)果后,直接緩存分頁(yè)結(jié)果 。
偽代碼如下:
publicListgetPageList(Stringparam,intpage,intsize){ Stringkey="productList"+page+"size:"+size+ "param:"+param; List dataList=cacheUtils.get(key); if(dataList!=null){ returndataList; } dataList=queryFromDataBase(param,page,size); if(dataList!=null){ cacheUtils.set(key,dataList,Constants.ExpireTime); } }
這種方案的優(yōu)點(diǎn)是工程簡(jiǎn)單,性能也快,但是有一個(gè)明顯的缺陷基因:列表緩存的顆粒度非常大 。
假如列表中數(shù)據(jù)發(fā)生增刪,為了保證數(shù)據(jù)的一致性,需要修改分頁(yè)列表緩存。
有兩種方式 :
1、依靠緩存過期來(lái)惰性的實(shí)現(xiàn) ,但業(yè)務(wù)場(chǎng)景必須包容;
2、使用 Redis 的 keys 找到該業(yè)務(wù)的分頁(yè)緩存,執(zhí)行刪除指令。但 keys 命令對(duì)性能影響很大,會(huì)導(dǎo)致 Redis 很大的延遲 。
生產(chǎn)環(huán)境使用 keys 命令比較危險(xiǎn),發(fā)生事故的幾率高,非常不推薦使用 。
2 查詢對(duì)象ID列表,再緩存每個(gè)對(duì)象條目
直接緩存分頁(yè)結(jié)果雖然好用,但緩存的顆粒度太大,保證數(shù)據(jù)一致性比較麻煩。
所以我們的目標(biāo)是更細(xì)粒度的控制緩存 。
我們先查詢出商品分頁(yè)對(duì)象ID列表,然后為每一個(gè)商品對(duì)象創(chuàng)建緩存 , 通過商品ID和商品對(duì)象緩存聚合成列表返回給前端。
偽代碼如下:
核心流程:
1、從數(shù)據(jù)庫(kù)中查詢分頁(yè) ID 列表
//從數(shù)據(jù)庫(kù)中查詢分頁(yè)商品ID列表 ListproductIdList=queryProductIdListFromDabaBase( param, page, size);
對(duì)應(yīng)的 SQL 類似:
SELECTidFROMproducts ORDERBYidASC LIMIT(page-1)*size,size
2、批量從緩存中獲取商品對(duì)象
MapcachedProductMap=cacheUtils.mget(productIdList);
假如我們使用本地緩存,直接一條一條從本地緩存中聚合也極快。
假如我們使用分布式緩存,Redis 天然支持批量查詢的命令 ,比如 mget ,hmget 。
3、組裝沒有命中的商品ID
ListnoHitIdList=newArrayList<>(cachedProductMap.size()); for(LongproductId:productIdList){ if(!cachedProductMap.containsKey(productId)){ noHitIdList.add(productId); } }
因?yàn)榫彺嬷锌赡芤驗(yàn)檫^期或者其他原因?qū)е戮彺鏇]有命中的情況,所以我們需要找到哪些商品沒有在緩存里。
4、批量從數(shù)據(jù)庫(kù)查詢未命中的商品信息列表,重新加載到緩存
首先從數(shù)據(jù)庫(kù)里批量 查詢出未命中的商品信息列表 ,請(qǐng)注意是批量 。
ListnoHitProductList=batchQuery(noHitIdList);
參數(shù)是未命中緩存的商品ID列表,組裝成對(duì)應(yīng)的 SQL,這樣性能更快 :
SELECT*FROMproductsWHEREidIN (1, 2, 3, 4);
然后這些未命中的商品信息存儲(chǔ)到緩存里 , 使用 Redis 的 mset 命令。
//將沒有命中的商品加入到緩存里 MapnoHitProductMap= noHitProductList.stream() .collect( Collectors.toMap(Product::getId,Function.identity()) ); cacheUtils.mset(noHitProductMap); //將沒有命中的商品加入到聚合map里 cachedProductMap.putAll(noHitProductMap);
5、 遍歷商品ID列表,組裝對(duì)象列表
for(LongproductId:productIdList){ Productproduct=cachedProductMap.get(productId); if(product!=null){ result.add(product); } }
當(dāng)前方案里,緩存都有命中的情況下,經(jīng)過兩次網(wǎng)絡(luò) IO ,第一次數(shù)據(jù)庫(kù)查詢 IO ,第二次 Redis 查詢 IO , 性能都會(huì)比較好。
所有的操作都是批量操作,就算有緩存沒有命中的情況,整體速度也較快。
”查詢對(duì)象ID列表,再緩存每個(gè)對(duì)象條目 “ 這個(gè)方案比較靈活,當(dāng)我們查詢對(duì)象ID列表 ,可以不限于數(shù)據(jù)庫(kù),還可以是搜索引擎,Redis 等等。
下圖是開源中國(guó)的搜索流程:
精髓在于:搜索的分頁(yè)結(jié)果只包含業(yè)務(wù)對(duì)象 ID ,對(duì)象的詳細(xì)資料需要從緩存 + MySQL 中獲取。
3 緩存對(duì)象ID列表,同時(shí)緩存每個(gè)對(duì)象條目
筆者曾經(jīng)重構(gòu)過類似朋友圈的服務(wù),進(jìn)入班級(jí)頁(yè)面 ,瀑布流的形式展示班級(jí)成員的所有動(dòng)態(tài)。
我們使用推模式將每一條動(dòng)態(tài) ID 存儲(chǔ)在 Redis ZSet 數(shù)據(jù)結(jié)構(gòu)中 。Redis ZSet 是一種類型為有序集合的數(shù)據(jù)結(jié)構(gòu),它由多個(gè)有序的唯一的字符串元素組成,每個(gè)元素都關(guān)聯(lián)著一個(gè)浮點(diǎn)數(shù)分值。
ZSet 使用的是 member -> score 結(jié)構(gòu) :
member : 成員,也是默認(rèn)的第二排序維度( score 相同時(shí),Redis 以 member 的字典序排列)
score : 分值,存儲(chǔ)類型是 double
如上圖所示:ZSet 存儲(chǔ)動(dòng)態(tài) ID 列表 , member 的值是動(dòng)態(tài)編號(hào) , score 值是創(chuàng)建時(shí)間 。
通過 ZSet 的 ZREVRANGE 命令 就可以實(shí)現(xiàn)分頁(yè)的效果。
ZREVRANGE 是 Redis 中用于有序集合(sorted set)的命令之一,它用于按照成員的分?jǐn)?shù)從大到小返回有序集合中的指定范圍的成員。
為了達(dá)到分頁(yè)的效果,傳遞如下的分頁(yè)參數(shù) :
通過 ZREVRANGE 命令,我們可以查詢出動(dòng)態(tài) ID 列表。
查詢出動(dòng)態(tài) ID 列表后,還需要緩存每個(gè)動(dòng)態(tài)對(duì)象條目,動(dòng)態(tài)對(duì)象包含了詳情,評(píng)論,點(diǎn)贊,收藏這些功能數(shù)據(jù) ,我們需要為這些數(shù)據(jù)提供單獨(dú)做緩存配置。
無(wú)論是查詢緩存,還是重新寫入緩存,為了提升系統(tǒng)性能,批量操作效率更高。
若緩存對(duì)象結(jié)構(gòu)簡(jiǎn)單,使用 mget 、hmget 命令;若結(jié)構(gòu)復(fù)雜,可以考慮使用 pipleline,Lua 腳本模式 。 筆者選擇的批量方案是 Redis 的 pipleline 功能。
我們?cè)賮?lái)模擬獲取動(dòng)態(tài)分頁(yè)列表的流程:
使用 ZSet 的 ZREVRANGE 命令 ,傳入分頁(yè)參數(shù),查詢出動(dòng)態(tài) ID 列表 ;
傳遞動(dòng)態(tài) ID 列表參數(shù),通過 Redis 的 pipleline 功能從緩存中批量獲取動(dòng)態(tài)的詳情,評(píng)論,點(diǎn)贊,收藏這些功能數(shù)據(jù) ,組裝成列表 。
4 總結(jié)
本文介紹了實(shí)現(xiàn)分頁(yè)列表緩存的三種方式:
直接緩存分頁(yè)列表結(jié)果
查詢對(duì)象ID列表,只緩存每個(gè)對(duì)象條目
緩存對(duì)象ID列表,同時(shí)緩存每個(gè)對(duì)象條目
這三種方式是一層一層遞進(jìn)的,要訣是:細(xì)粒度的控制緩存 和批量加載對(duì)象 。
審核編輯:劉清
-
存儲(chǔ)器
+關(guān)注
關(guān)注
38文章
7403瀏覽量
163395 -
SQL
+關(guān)注
關(guān)注
1文章
751瀏覽量
43987 -
MYSQL數(shù)據(jù)庫(kù)
+關(guān)注
關(guān)注
0文章
95瀏覽量
9372 -
Redis
+關(guān)注
關(guān)注
0文章
370瀏覽量
10810
原文標(biāo)題:分頁(yè)列表緩存就該這樣設(shè)計(jì)!
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論