為什麼要使用頁面快取技術?
系統都是逐漸演進的,一個系統在執行中必須是根據場景逐漸地提高優化效能。高併發就是對資源的節約的考驗,這種考驗除了更換優秀和先進的技術,優化架構,還在於從小處出發,對儘可能節約的資源進行節約。而在一個系統的資料訪問中,系統的瓶頸往往是來自於資料庫,因此我們要儘可能減少對資料庫的訪問!在不影響使用者體驗的情況下,對於一些靜態或者變化不大的頁面,我們使用快取來減少對資料庫的訪問!
快取技術的原理
在一個請求中,邏輯越複雜,呼叫,依賴,訪問資料庫的越多,耗時也就越長,響應時間也就越長,效能也就越差!因此降低邏輯複雜度,減低耦合,提高內聚,減少資料庫訪問,將頻繁用到的變化不大的資料給快取起來也就成為了提高效能的主要核心。
快取雪崩-資料穿透問題
快取穿透
快取穿透,是指查詢一個資料庫一定不存在的資料。正常的使用快取流程大致是,資料查詢先進行快取查詢,如果key不存在或者key已經過期,再對資料庫進行查詢,並把查詢到的物件,放進快取。如果資料庫查詢物件為空,則不放進快取。
想象一下這個情況,如果傳入的引數為-1,會是怎麼樣?這個-1,就是一定不存在的物件。就會每次都去查詢資料庫,而每次查詢都是空,每次又都不會進行快取。假如有惡意攻擊,就可以利用這個漏洞,對資料庫造成壓力,甚至壓垮資料庫。即便是採用UUID,也是很容易找到一個不存在的KEY,進行攻擊。
快取雪崩
快取雪崩是指因為資料未載入到快取中,或者快取同一時間大面積的失效,在某一時刻大量的快取沒有命中,從而導致所有請求都去查資料庫,導致資料庫CPU和記憶體負載過高,甚至當機.
解決方法:做電商專案的時候,一般是採取不同分類商品,快取不同週期。在同一分類中的商品,加上一個隨機因子。這樣能儘可能分散快取過期時間,而且,熱門類目的商品快取時間長一些,冷門類目的商品快取時間短一些,也能節省快取服務的資源。
快取擊穿
快取擊穿,是指一個key非常熱點,在不停的扛著大併發,大併發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大併發就穿破快取,直接請求資料庫,就像在一個屏障上鑿開了一個洞。
解決方法:對主打商品都是早早的做好了準備,讓快取永不過期。即便某些商品自己發酵成了爆款,也是直接設為永不過期就好了。
下面是一個我在開發秒殺專案的時候,一些優化的地方,例如在進入商品列表的時候,我是直接採用Thymeleaf模板動態渲染了,這樣的處理效率不高,不適合高併發,所以採用頁面快取進行優化,將頁面的原始碼快取到Redis中,這樣可以減少對資料庫的訪問,從而減少對資料庫訪問的耗時。
如下程式碼是進入商品列表的原始碼,只是簡單的將資料放進model裡,然後返回頁面,但是每次載入的時候,都會進行一次資料庫訪問,如果在是高併發的情況下,會加劇資料庫的訪問,造成載入時間變慢,甚至導致資料庫當機等情況發生,所以我們需要採用一些快取技術進行優化。
@RequestMapping("to_list")
public String toList(Model model,MiaoshaUser user){
if(user == null)
return "login";
model.addAttribute("user",user);
List<GoodsVo> goodsVoList = goodsService.getGoodsVoList();
model.addAttribute("goodsList",goodsVoList);
return "goods_list";
}
如下程式碼是經過了頁面快取優化後的程式碼,每次進入方法前,先取快取,看看快取中是否含有之前快取的html原始碼,如果有,則返回。如果沒有,則進行資料庫訪問等操作,然後用thymeleafViewResolver
進行手動渲染,將渲染後的結果放進html
中,然後放進快取,再返回給瀏覽器解析。
注意:快取的有效時間不宜過長,也不宜過短,本文中的優化,我是設定了60s的快取有效時間,使用者看到60s前的頁面,也還是正常的。
@RequestMapping(value ="/to_list",produces = "text/html")
@ResponseBody
public String toList(HttpServletRequest request,HttpServletResponse response, Model model, MiaoshaUser user){
model.addAttribute("user",user);
//取快取
String html = redisService.get(GoodsKey.getGoodsList,"",String.class);
if (!StringUtils.isEmpty(html)){
return html;
}
//查詢商品列表
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList",goodsList);
IWebContext ctx = new WebContext(request,response,
request.getServletContext(),request.getLocale(),model.asMap());
//手動渲染
html = thymeleafViewResolver.getTemplateEngine().process("goods_list",ctx);
if (!StringUtils.isEmpty(html)){
redisService.set(GoodsKey.getGoodsList,"",html);
}
return html;
}
上面的程式碼有一處地方需要注意一下,有些同學用的WebContext
是SpringWebContext
由於我採用的是thymeleaf.spring5
的版本,大部分API被移到了IWebContext
下面。可能會有些小差別,在此說明一下。