為什么要使用頁面緩存技術?
系統都是逐漸演進的,一個系統在運行中必須是根據場景逐漸地提高優化性能。高並發就是對資源的節約的考驗,這種考驗除了更換優秀和先進的技術,優化架構,還在於從小處出發,對盡可能節約的資源進行節約。而在一個系統的數據訪問中,系統的瓶頸往往是來自於數據庫,因此我們要盡可能減少對數據庫的訪問!在不影響用戶體驗的情況下,對於一些靜態或者變化不大的頁面,我們使用緩存來減少對數據庫的訪問!
緩存技術的原理
在一個請求中,邏輯越復雜,調用,依賴,訪問數據庫的越多,耗時也就越長,響應時間也就越長,性能也就越差!因此降低邏輯復雜度,減低耦合,提高內聚,減少數據庫訪問,將頻繁用到的變化不大的數據給緩存起來也就成為了提高性能的主要核心。
緩存雪崩-數據穿透問題
緩存穿透
緩存穿透,是指查詢一個數據庫一定不存在的數據。正常的使用緩存流程大致是,數據查詢先進行緩存查詢,如果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
下面。可能會有些小差別,在此說明一下。