網頁靜態化
簡介
隨着網站的內容的增多和用戶訪問量的增多,無可避免的是網站加載會越來越慢,受限於帶寬和服務器同一時間的請求次數的限制,我們往往需要在此時對我們的網站進行代碼優化和服務器配置的優化。
一般情況下會從以下方面來做優化
- 動態頁面靜態化
- 優化數據庫
- 使用負載均衡
- 使用緩存
- 使用CDN加速
此文以樂優電商網站爲例(處理高併發問題)
我們的頁面是通過Thymeleaf模板引擎渲染後返回到客戶端。在後臺需要大量的數據查詢,而後渲染得到HTML頁面。會對數據庫造成壓力,並且請求的響應時間過長,併發能力不高。
大家能想到什麼辦法來解決這個問題?
首先我們能想到的就是緩存技術,比如Redis。不過Redis適合數據規模比較小的情況。假如數據量比較大,例如我們的商品詳情頁。每個頁面如果10kb,100萬商品,就是10GB空間,對內存佔用比較大。此時就給緩存系統帶來極大壓力,如果緩存崩潰,接下來倒黴的就是數據庫了。
所以緩存並不是萬能的,某些場景需要其它技術來解決,比如靜態化。
什麼是靜態化
靜態化是指把動態生成的HTML頁面變爲靜態內容保存,以後用戶的請求到來,直接訪問靜態頁面,不再經過服務的渲染。
靜態處理後又給網站帶來了哪些好處?
- 靜態頁面相對於動態頁面更容易被搜索引擎收錄。
- 訪問靜態頁面不需要經過程序處理,因此可以提高運行速度。
- 減輕服務器負擔。
- 靜態的HTML頁面可以部署在nginx中,從而大大提高併發能力
如何實現靜態化
目前,靜態化頁面都是通過模板引擎來生成,而後保存到nginx服務器來部署。常用的模板引擎比如:
- Freemarker
- Velocity
- Thymeleaf
我們之前就使用的Thymeleaf,來渲染html返回給用戶。Thymeleaf除了可以把渲染結果寫入Response,也可以寫到本地文件,從而實現靜態化。
Thymeleaf實現靜態化
概念
先說下Thymeleaf中的幾個概念:
- Context:運行上下文
- TemplateResolver:模板解析器
- TemplateEngine:模板引擎
Context
上下文: 用來保存模型數據,當模板引擎渲染時,可以從Context上下文中獲取數據用於渲染。
當與SpringBoot結合使用時,我們放入Model的數據就會被處理到Context,作爲模板渲染的數據使用。
TemplateResolver
模板解析器:用來讀取模板相關的配置,例如:模板存放的位置信息,模板文件名稱,模板文件的類型等等。
當與SpringBoot結合時,TemplateResolver已經由其創建完成,並且各種配置也都有默認值,比如模板存放位置,其默認值就是:templates。比如模板文件類型,其默認值就是html。
TemplateEngine
模板引擎:用來解析模板的引擎,需要使用到上下文、模板解析器。分別從兩者中獲取模板中需要的數據,模板文件。然後利用內置的語法規則解析,從而輸出解析後的文件。來看下模板引擎進行處理的函數:
templateEngine.process("模板名", context, writer);
三個參數:
- 模板名稱
- 上下文:裏面包含模型數據
- writer:輸出目的地的流
在輸出時,我們可以指定輸出的目的地,如果目的地是Response的流,那就是網絡響應。如果目的地是本地文件,那就實現靜態化了。
而在SpringBoot中已經自動配置了模板引擎,因此我們不需要關心這個。現在我們做靜態化,就是把輸出的目的地改成本地文件即可!
具體實現
Service代碼:
@Service
public class GoodsHtmlService {
@Autowired
private GoodsService goodsService;
@Autowired
private TemplateEngine templateEngine;
private static final Logger LOGGER = LoggerFactory.getLogger(GoodsHtmlService.class);
/**
* 創建html頁面
*
* @param spuId
* @throws Exception
*/
public void createHtml(Long spuId) {
PrintWriter writer = null;
try {
// 獲取頁面數據
Map<String, Object> spuMap = this.goodsService.loadModel(spuId);
// 創建thymeleaf上下文對象
Context context = new Context();
// 把數據放入上下文對象
context.setVariables(spuMap);
// 創建輸出流
File file = new File("C:\\project\\nginx-1.14.0\\html\\item\\" + spuId + ".html");
writer = new PrintWriter(file);
// 執行頁面靜態化方法
templateEngine.process("item", context, writer);
} catch (Exception e) {
LOGGER.error("頁面靜態化出錯:{},"+ e, spuId);
} finally {
if (writer != null) {
writer.close();
}
}
}
/**
* 新建線程處理頁面靜態化
* @param spuId
*/
public void asyncExcute(Long spuId) {
ThreadUtils.execute(()->createHtml(spuId));
/*ThreadUtils.execute(new Runnable() {
@Override
public void run() {
createHtml(spuId);
}
});*/
}
}
線程工具類:
public class ThreadUtils {
private static final ExecutorService es = Executors.newFixedThreadPool(10);
public static void execute(Runnable runnable) {
es.submit(runnable);
}
}
什麼時候創建靜態文件
我們編寫好了創建靜態文件的service,那麼問題來了:什麼時候去調用它呢
想想這樣的場景:
假如大部分的商品都有了靜態頁面。那麼用戶的請求都會被nginx攔截下來,根本不會到達我們的leyou-goods-web
服務。只有那些還沒有頁面的請求,纔可能會到達這裏。
因此,如果請求到達了這裏,我們除了返回頁面視圖外,還應該創建一個靜態頁面,那麼下次就不會再來麻煩我們了。
所以,我們在GoodsController中添加邏輯,去生成靜態html文件:
@GetMapping("{id}.html")
public String toItemPage(@PathVariable("id")Long id, Model model){
// 加載所需的數據
Map<String, Object> map = this.goodsService.loadModel(id);
// 把數據放入數據模型
model.addAllAttributes(map);
// 頁面靜態化
this.goodsHtmlService.asyncExcute(id);
return "item";
}
注意:生成html 的代碼不能對用戶請求產生影響,所以這裏我們使用額外的線程進行異步創建。
重啓測試:
訪問一個商品詳情,然後查看nginx目錄:
nginx代理靜態頁面
接下來,我們修改nginx,讓它對商品請求進行監聽,指向本地靜態頁面,如果本地沒找到,才進行反向代理:
server {
listen 80;
server_name www.leyou.com;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location /item {
# 先找本地
root html;
if (!-f $request_filename) { #請求的文件不存在,就反向代理
proxy_pass http://127.0.0.1:8084;
break;
}
}
location / {
proxy_pass http://127.0.0.1:9002;
proxy_connect_timeout 600;
proxy_read_timeout 600;
}
}
重啓測試:
發現請求速度得到了極大提升:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8NmB0IdI-1574157385161)(1532758206086.png)]
break;
}
}
location / {
proxy_pass http://127.0.0.1:9002;
proxy_connect_timeout 600;
proxy_read_timeout 600;
}
}
重啓測試:
發現請求速度得到了極大提升:
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20191119175729155.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1pob3VYaWFuQmlhbw==,size_16,color_FFFFFF,t_70)