(七)传输加载优化(前沿技术解决高访问量网站性能优化问题)
傳輸加載優化
- 啟用壓縮Gzip【必會的傳輸壓縮方案】
- Gzip
- 安裝ngnix
- 啟用Keep Alive【通過一個參數提速連接】
- Keep Alive
- HTTP資源緩存【必會的HTTP緩存方法】
- 看看第三方如何緩存的
- 一次性理解Service workers技術,給網站提速
- Service workers作用
- Service workers原理
- Service workers注意
- HTTP 2的性能提升
- HTTP/2的優勢
- 開啟http2
- 做自簽名的證書
- 開啟http2
- Server push(服務器推送)
- 搭建HTTP/2服務
- 用流行的SSR技術給前端減負
- 服務端渲染SSR的好處
- 客戶端渲染 vs 服務端渲染
- React SSR
- 是否使用SSR
啟用壓縮Gzip【必會的傳輸壓縮方案】
Gzip是用來做網絡資源壓縮,幫我們減小資源文件在網絡傳輸大小的技術,網絡傳輸的過程中,去進行這種實時的動態的一個壓縮,這個可以說是我們唯一可以選擇的一個技術,Gzip壓縮比和壓縮效率比較高
在傳輸層進行的動態壓縮和我們之前講的對資源文件的壓縮是不同的概念
Gzip
- 對傳輸資源進行體積壓縮,可高達90%
- 如何配置Nginx啟用Gzip
安裝ngnix
- 安裝homebrew
https://brew.sh/
- 安裝ngnix
- 運行ngnix
- 查看配置文件
看配置文件默認端口時8080
訪問localhost:8080
在用戶名目錄下新建文件夾,把打包后的內容放到文件夾里,建議不要放到Documents下,因為這樣會引起一些權限的問題,還要對權限進行相關的調整
然后修改配置文件的路徑
配置gzip
保存退出,重啟ngnix,sudo brew services start nginx
啟用Keep Alive【通過一個參數提速連接】
這個技術可以幫助對TCP鏈接進行復用,當我們和一臺服務器進行TCP建立連接之后,接下來的請求就不需要進行重復建立鏈接了,這樣對于請求量比較高的網站,就可以大大節約我們在網絡加載時候的開銷
它是我們http標準中的一部分,因為它多數情況下是有益無害,所以在http1.1開始,Keep Alive參數默認進行開啟
第一個資源有下圖的Initial connection,此為TCP鏈接的建立,后面的資源加載就沒有Initial connection
下圖頭部可以看到keep-alive參數
curl -v http://127.0.0.1:8080可以查看請求和響應的詳細信息
和Keep Alive相關的還有兩個比較重要的參數,通常我們要根據網站實際的請求量和用戶量進行相關配置,打開ng配置文件
為什么要設置以上的值呢?一直開啟不可以嗎?每個東西都是有開銷的,一個客戶端和服務端建立tcp鏈接,考慮用戶規模,上萬、十萬、百萬、千萬,服務器上要給這些用戶都保持住tcp鏈接,這個開銷還是非常大的,所以資源不再使用就要退出,以上參數要根據實際情況配置
Keep Alive
- 一個持久的TCP連接,節省了連接創建時間
- Nginx默認開啟keep alive
HTTP資源緩存【必會的HTTP緩存方法】
-
提高重復訪問時資源加載的速度
-
Cache-Control/Expires
-
Last-Modified+If-Modified-Since
與Etag+If-None-Match是等價的,Last-Modified+If-Modified-Since與時間相關,時間的精準性,客戶端服務端時間不同步,會帶來一些問題,要求不高可以使用,它們對http1.0是更兼容的 -
Etag+If-None-Match
第一個是匹配html,我們現在主要都是單頁應用,所有的資源文件都是通過html進行后續的加載,如果html緩存了,更新時緩存如果沒有過期就會通過html拿到舊的js、css,html不希望進行緩存,希望用戶始終拿到最新的html,這個文件本身也不大
Cache-Control是http1.1的標準,不需要進行緩存,需要這個文件時去服務端重新獲取,獲取完要進行重新驗證
配后面兩個是為了兼容性問題,老版本瀏覽器不支持http1.1,要考慮http1.0里Cache-Control沒有很好的實現,就需要Pragma參數,告訴只支持http1.0的瀏覽器也不要進行緩存,Expires配置0或者是負數的話,相當于無效值,告訴瀏覽器你這個文件立即就過期,下次再用時一定得去服務端拿
js、css7天內瀏覽器都緩存住,用戶重新去訪問時,直接去緩存去,不用來服務端取,結合webpack的緩存技術,js、css都用了hash的命名方法,當html進行有效更新后,html指向的css、js的url發生變化,這里過期時間就不重要了,url發生變化,瀏覽器就認為是新的資源文件,需要去服務器拿新的資源文件,舊的文件就被丟棄掉了,那能拿到的css、js也是最新的資源
雖然html沒有緩存,但狀態是304,會向服務器做請求,我要用的文件需不需要從你那拿,服務器告訴它你這個文件未發生變化,可以從緩存里拿,服務器就返回狀態304,瀏覽器就知道未發生變化,還可以用之前的緩存
服務端如何知道客戶端要請求的資源對客戶端而言有沒有發生變化
Etag(Response Headers)相當于是文件資源的唯一標識,是在服務端生成的,會告訴客戶端我們這個資源標識是什么,在第一次請求時就會把這個信息帶過來,再次進行請求時,會問下服務端我的Etag還匹不匹配(If-None-Match(Request Headers)),不匹配我就從你那拿最新的資源,還匹配就告訴我304,我就從緩存離直接取
Etag剛ng配置里沒有配,ng是默認開啟Etag緩存技術
js從磁盤的緩存里進行加載,有時候是內存緩存,這根據緩存策略不同
Expires(Response Headers)7天都從緩存去取,Date拿的日期是7月9日,過期時間Expires是7月16日
看看第三方如何緩存的
-
天貓
max-age設置正數,表示當前資源文件經過多少秒之后失效,0表示通過第一次獲取立即失效
s-maxage不是給瀏覽器進行設置的,是給到達瀏覽器之前的一些中間的一些緩存或者說是代理服務器進行緩存的設置 -
知乎
默認是public,public認為中間層或者代理服務器可以做一級緩存,也就是說,所有的用戶已經在代理啊服務器上進行了緩存,所有的用戶可以去向代理服務器獲取已經緩存的資源文件,private這個緩存只能在瀏覽器或者用戶的層面上,不可以去獲取一個代理服務器或者中間的緩存層上一個共享的緩存;再次進行訪問時狀態還是200,不是304,因為它這邊不是設置no-cache,設置的是更強的緩存策略no-store,并不關心文件有沒有發生變化,始終要跟服務器獲取最新的文件,強制讓你的緩存失效,而且不使用任何重新驗證的策略,所以這邊must-revalidate可能時沒有必要的,下面也沒有Etag+If-None-Match的設置了 -
google
重新訪問狀態也是200,不過這個200不是從服務器來的,而是從 ServiceWorker,ServiceWorker是一個緩存機制,在瀏覽器端建立了一個中間的緩存,后續的請求都是從這個中間的緩存里進行獲取,雖然Cache-Control設置了no-cache,no-cache是和服務器重新驗證下我這個資源有沒有過期,但是它并沒有真正去進行和服務器這樣的一個確認,因為它通過了ServiceWorker,ServiceWorker直接告訴它這是命中的緩存,你可以直接去使用,不需要和服務器進行重新的確認,也是因為Cache-Control默認時public,才會有這樣的效果
更多配置看https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers網站
一次性理解Service workers技術,給網站提速
Service workers作用
- 加速重復訪問
- 離線支持
用戶沒有網絡的情況下也可以讓用戶訪問到我們的網頁
將網絡情況切換到offline,網頁仍然可以正常訪問
如果建工程是使用create-react-app這個腳手架工程的話,默認會獲得serviceWorker的功能,使用很簡單,不需要進行配置或者實現,serviceWorker也有自己的生命周期,首先要注冊安裝激活才可以使用
打包后的目錄有個asset-manifest.json里定義了哪些資源要進行緩存,以及緩存文件的文件名
相關的版本信息會存在precache-manifest里,每個文件都有相關的版本信息,這些文件不可能手工去生成,會很復雜
需要借用webpack插件幫我們做這些事情,google幫我們去做了相關的實現
我們只需使用兩個插件,就可以生成serviceWorker和相關的配置文件,一個叫WorkboxWebpackPlugin,這是webpack插件,適用性和框架本身無關,另一個是ManifestPlugin,這里主要用它生成asset-manifest.json,決定哪些資源要進行緩存,會把所有靜態資源html、css、js全都進行緩存,你想在離線的時候進行使用,這些資源都是必須的,比較大的圖片或者視頻資源不會考慮在內,serviceWorker也要占用系統資源的,如果是用戶的移動設備,對資源是非常寶貴,如果存了很多很大的內容到serviceWorker,相當于給用戶手機安裝了很大的app,對用戶設備帶來很大的壓力
// serviceWorker.js // This optional code is used to register a service worker. // register() is not called by default.// This lets the app load faster on subsequent visits in production, and gives // it offline capabilities. However, it also means that developers (and users) // will only see deployed updates on subsequent visits to a page, after all the // existing tabs open on the page have been closed, since previously cached // resources are updated in the background.// To learn more about the benefits of this model and instructions on how to // opt-in, read https://bit.ly/CRA-PWAconst isLocalhost = Boolean(window.location.hostname === 'localhost' ||// [::1] is the IPv6 localhost address.window.location.hostname === '[::1]' ||// 127.0.0.0/8 are considered localhost for IPv4.window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) );export function register(config) {if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {// The URL constructor is available in all browsers that support SW.const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);if (publicUrl.origin !== window.location.origin) {// Our service worker won't work if PUBLIC_URL is on a different origin// from what our page is served on. This might happen if a CDN is used to// serve assets; see https://github.com/facebook/create-react-app/issues/2374return;}window.addEventListener('load', () => {const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;if (isLocalhost) {// This is running on localhost. Let's check if a service worker still exists or not.checkValidServiceWorker(swUrl, config);// Add some additional logging to localhost, pointing developers to the// service worker/PWA documentation.navigator.serviceWorker.ready.then(() => {console.log('This web app is being served cache-first by a service ' +'worker. To learn more, visit https://bit.ly/CRA-PWA');});} else {// Is not localhost. Just register service workerregisterValidSW(swUrl, config);}});} }function registerValidSW(swUrl, config) {navigator.serviceWorker.register(swUrl).then(registration => {registration.onupdatefound = () => {const installingWorker = registration.installing;if (installingWorker == null) {return;}installingWorker.onstatechange = () => {if (installingWorker.state === 'installed') {if (navigator.serviceWorker.controller) {// At this point, the updated precached content has been fetched,// but the previous service worker will still serve the older// content until all client tabs are closed.console.log('New content is available and will be used when all ' +'tabs for this page are closed. See https://bit.ly/CRA-PWA.');// Execute callbackif (config && config.onUpdate) {config.onUpdate(registration);}} else {// At this point, everything has been precached.// It's the perfect time to display a// "Content is cached for offline use." message.console.log('Content is cached for offline use.');// Execute callbackif (config && config.onSuccess) {config.onSuccess(registration);}}}};};}).catch(error => {console.error('Error during service worker registration:', error);}); }function checkValidServiceWorker(swUrl, config) {// Check if the service worker can be found. If it can't reload the page.fetch(swUrl, {headers: { 'Service-Worker': 'script' },}).then(response => {// Ensure service worker exists, and that we really are getting a JS file.const contentType = response.headers.get('content-type');if (response.status === 404 ||(contentType != null && contentType.indexOf('javascript') === -1)) {// No service worker found. Probably a different app. Reload the page.navigator.serviceWorker.ready.then(registration => {registration.unregister().then(() => {window.location.reload();});});} else {// Service worker found. Proceed as normal.registerValidSW(swUrl, config);}}).catch(() => {console.log('No internet connection found. App is running in offline mode.');}); }export function unregister() {if ('serviceWorker' in navigator) {navigator.serviceWorker.ready.then(registration => {registration.unregister();}).catch(error => {console.error(error.message);});} } // index.jsx import * as serviceWorker from './serviceWorker'; // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.register(); //注冊serviceWorkerService workers原理
在客戶端和服務端之間建立一個中間層,做了個存儲,即使拋開server之后,客戶端還可以正常工作和使用,因為從Service workers拿資源,Service workers所在的位置是我們的客戶端
Service workers注意
-
延長了首屏時間,但頁面總加載時間減少
-
兼容性
-
只能在localhost或者https下使用
為了保證安全性
HTTP 2的性能提升
h2代表http2
HTTP/2的優勢
- 二進制傳輸
http1.0和1.1是基于文本的,這種效率比較低,而且不安全,所以http2是二進制編碼傳輸,即安全而且進行了很好的壓縮,提高了傳輸效率;
對頭部做了壓縮,也可以保證傳輸更快 - 請求響應多路復用
- Server push(服務器推送)
開啟http2
打開ng配置文件
服務監聽端口843,默認443也是可以的
開啟ssl,https的證書,如何生成自簽名的證書,https是我們使用http2必須要求的條件,只能在https下開啟http2
做自簽名的證書
作為前端工程師必備的技能,很多新技術對安全性有很大的要求,限制我們使用這些技術必須在https下使用,這證書購買需要花費比較高的價格,作為開發者,我們可以做個自簽名的證書,作為開發用,生成證書只要下面4條指令就可以
openssl genrsa -des3 -passout pass:x -out server.pass.key 2048openssl rsa -passin pass:x -in server.pass.key -out server.key openssl req -new -key server.key -out server.csr openssl x509 -req -sha256 -days 3650 -in server.csr -signkey server.key -out server.crt
執行后得到server.crt和server.key,在工程目錄下新建ssl文件夾,把server.crt和server.key拷貝到文件夾下,ng進行配置
訪問https://localhost:843,會出現如下提示,因為我們使用的是自簽名的證書,chrome認為你這個證書本身不安全,懷疑這個地方是不是被人劫持了
這是直接鍵盤輸入thisisunsafe,頁面就可以繞過證書的驗證,直接出來
show overview顯示概覽圖,就是下面條形的圖,可以選中局部查看局部網絡加載的情況,這幾張圖片都是通過http1.1進行加載的,概覽圖中可以看到這里開啟了多個請求去完成圖片的加載,如果頁面上資源量比較多的話,會有很高的請求量,網絡壓力比較大
開啟http2
打開ng,輸入如下http2
保存退出,重啟服務
所有的網絡資源都變成http2的協議了,還有h3,h3都是對google外部資源的請求,google已經全面部署了http3,只不過http3還未正式發布
這里圖片變成只有兩個請求被發起,所有的圖片是通過一個請求去完成的,另外個請求是對goofle外部圖片進行的請求,所以http2在網絡請求了進行了減少,這就是http2的優勢:多路復用(Multiplexing)
http1.1雖然可以用keep-alive復用同個tcp鏈接,但是資源還是有一個這樣的順序,會形成這樣的堵塞問題,但在http2里,這不再是問題,真正做到了異步或并發的對資源進行傳輸,同一個時刻可以發起對多個資源的請求,同一時刻可以將不同資源的信息同時通過網絡傳回到瀏覽器
Server push(服務器推送)
正常客戶端向服務器請求,服務端再把資源推送給客戶端,這來回都是有消耗的,也就是常說的TTFB,如果能消除這過程中的一些時間,可以節約網絡開銷,作為開發者,了解哪些資源是非常重要,哪些資源是接下來需要,能不能提前讓服務器把這些東西推送到我們客戶端,這樣的話我們就不需要進行請求了,在接下來我們需要它的時候,瀏覽器已經拿到了它,就可以直接用
ng配置好后保存退出
重啟ng服務,發現這三張圖片沒有綠色部分,就是TTFB,沒有請求返還回路這樣的過程
資源是Reading push
Initiator圖片為Push,這種資源是通過server push提前push到我們瀏覽器的,瀏覽器把他們放在緩存里,需要的時候直接從緩存里進行讀取
右鍵可以勾選顯示哪些信息
當網站比較大,請求量很高,請求數很多,用戶網絡環境普遍不是很好的情況下,http2才可以發揮比較大的功效
搭建HTTP/2服務
-
HTTPS
HTTP/2只能工作在https下 -
適合較高的請求量
用流行的SSR技術給前端減負
服務端渲染SSR的好處
- 加速首屏加載
- 更好的SEO,搜索引擎優化
客戶端渲染 vs 服務端渲染
那我們在客戶端去渲染的時候呢,我們需要把這個頁面先請求過來,然后再去看頁面上它所關聯的所有的js,然后加載這些js再進行解析,然后才能讓用戶看到我們這個頁面上,真正要顯示的內容,這個過程勢必會延遲我們的這個首屏時間
我們如果使用服務端渲染的話。這個過程可以大大的提前,從服務端渲染完的頁面再傳到前端的時候,已經是渲染之后的html了,就不需要再經過我們客戶端渲染的這樣的一個復雜的過程了,很快就可以把這個內容呈現給用戶,另外就是由于我們同服務端傳到我們前端已經是現成的html,所以搜索引擎啊,可以很好地去進行索引
React SSR
- 基于Next.js實現SSR
npm init創建個新工程,npm install next react react-dom
這邊寫組件和我們之前在客戶端渲染時沒有太大的差別,不同在于現在我們寫完的內容是通過next在后端先進行渲染,渲染好之后的內容變成html才傳回我們前端去執行,給到我們瀏覽器
npm run dev
服務端渲染把所有頁面上顯示的內容都有,都在html里
前端渲染只有id為mian的div,后面看不到頁面顯示的內容,所有頁面上的內容都是動態渲染出來的,js解析之后會根據我們的需要,再對body下面的main進行相關的替換或者插入相關的內容;服務端渲染時這些內容已經在服務端渲染好了,后端直接把html返回給前端,前端會顯示得更快,因為可以直接立即給用戶顯示
前端渲染時路由可以帶給我們很好的跳轉體驗,跳轉的時候不會感覺重新刷新的感覺,改成服務端渲染,next有提供Link,也可以做到這一點
使用next.js進行后端渲染,相當于做了個重構的應用,前端渲染后端渲染可以做到無縫的銜接
是否使用SSR
- 架構-大型,動態頁面,面向公眾用戶
是否去用這個服務端渲染?其實還是圍繞他的主要的兩個優勢,你是不是非常關心首屏速度,那這個首屏速度其實也受你項目規模的影響,如果你要考慮服務端渲染,最最開始就要做好這個架構的決定,如果說我們做的這個項目是一個比較大型的項目,然后你這個頁面上面的內容其實都是一些動態的內容的話,最好是選擇這個服務端渲染,所謂動態內容就是還要去進行數據庫查詢,然后把這些數據拿出來重新進行組織,然后再把它渲染到頁面上,這種數據用客戶端渲染,還要再發單獨的請求,然后再去進行渲染,不如在服務端把這些都做好直接形成一個頁面再返回到瀏覽器效率高 - 搜索引擎排名很重要
有的前面的頁面使用靜態頁面,后面的頁面再用react、vue去實現動態加載
總結
以上是生活随笔為你收集整理的(七)传输加载优化(前沿技术解决高访问量网站性能优化问题)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 域名可以用多久
- 下一篇: 本地开发环境与生产环境布局有偏差问题