無論你是想要設計高性能 Web 應用,還是要優(yōu)化現(xiàn)有的 Web 應用,你都需要了解瀏覽器中的網絡流程、頁面渲染過程,JAVAScript 執(zhí)行流程,以及 Web 安全理論,而這些功能是分散在瀏覽器的各個功能組件中的,比較多、比較散,要怎樣學習才能掌握呢?通過瀏覽器的多進程架構的學習,你就可以把這些分散的知識點串起來,組成一張網,從而讓自己能站在更高的維度去理解 Web 應用。
因此,學習瀏覽器的多進程架構是很有必要的。需要說明的是,我所有的分析都是基于 Chrome 瀏覽器的。那么多瀏覽器,為什么偏偏選擇 Chrome 瀏覽器呢?因為 Chrome、微軟的 Edge 以及國內的大部分主流瀏覽器,都是基于 Chromium 二次開發(fā)而來;而 Chrome 是 google 的官方發(fā)行版,特性和 Chromium 基本一樣,只存在一些產品層面差異;再加上 Chrome 是目前世界上使用率最高的瀏覽器,所以Chrome 最具代表性。
在開始之前,我們一起看下,Chrome 打開一個頁面需要啟動多少進程?你可以點擊 Chrome 瀏覽器右上角的“選項”菜單,選擇“更多工具”子菜單,點擊“任務管理器”,這將打開 Chrome 的任務管理器的窗口,如下圖:
Chrome 的任務管理器窗口
和 windows 任務管理器一樣,Chrome 任務管理器也是用來展示運行中 Chrome 使用的進程信息的。從圖中可以看到,Chrome 啟動了 4 個進程,你也許會好奇,只是打開了 1 個頁面,為什么要啟動這么多進程呢?
在解答這個問題之前,我們需要了解一下進程的概念,不過由于好多人容易把進程和線程的概念混淆,從而影響后續(xù)其他概念的理解,所以這里我就將這兩個概念以及它們之間的關系一并為你講解下。
進程和線程
不過,在介紹進程和線程之前,我需要先講解下什么是并行處理,因為如果你理解了并行處理的概念,那么再理解進程和線程之間的關系就會變得輕松許多。
什么是并行處理
計算機中的并行處理就是同一時刻處理多個任務,比如我們要計算下面這三個表達式的值,并顯示出結果。
A = 1+2B = 20/5C = 7*8復制代碼
在編寫代碼的時候,我們可以把這個過程拆分為四個任務:
- 任務 1 是計算 A=1+2;
- 任務 2 是計算 B=20/5;
- 任務 3 是計算 C=7*8;
- 任務 4 是顯示最后計算的結果。
正常情況下程序可以使用單線程來處理,也就是分四步按照順序分別執(zhí)行這四個任務。
如果采用多線程,會怎么樣呢?我們只需分“兩步走”:第一步,使用三個線程同時執(zhí)行前三個任務;第二步,再執(zhí)行第四個顯示任務。
通過對比分析,你會發(fā)現(xiàn)用單線程執(zhí)行需要四步,而使用多線程只需要兩步。因此,使用并行處理能大大提升性能。
線程 VS 進程
多線程可以并行處理任務,但是線程是不能單獨存在的,它是由進程來啟動和管理的。那什么又是進程呢?
一個進程就是一個程序的運行實例。詳細解釋就是,啟動一個程序的時候,操作系統(tǒng)會為該程序創(chuàng)建一塊內存,用來存放代碼、運行中的數(shù)據和一個執(zhí)行任務的主線程,我們把這樣的一個運行環(huán)境叫進程。
為了讓你更好地理解上述計算過程,我畫了下面這張對比圖:
單線程與多線程的進程對比圖
從圖中可以看到,線程是依附于進程的,而進程中使用多線程并行處理能提升運算效率。
總結來說,進程和線程之間的關系有以下 4 個特點。
1. 進程中的任意一線程執(zhí)行出錯,都會導致整個進程的崩潰。
我們可以模擬以下場景:
A = 1+2B = 20/0C = 7*8復制代碼
我把上述三個表達式稍作修改,在計算 B 的值的時候,我把表達式的分母改成 0,當線程執(zhí)行到 B = 20/0 時,由于分母為 0,線程會執(zhí)行出錯,這樣就會導致整個進程的崩潰,當然另外兩個線程執(zhí)行的結果也沒有了。
2. 線程之間共享進程中的數(shù)據。
如下圖所示,線程之間可以對進程的公共數(shù)據進行讀寫操作。
線程之間共享進程中的數(shù)據示意圖
從上圖可以看出,線程 1、線程 2、線程 3 分別把執(zhí)行的結果寫入 A、B、C 中,然后線程 2 繼續(xù)從 A、B、C 中讀取數(shù)據,用來顯示執(zhí)行結果。
3. 當一個進程關閉之后,操作系統(tǒng)會回收進程所占用的內存。
當一個進程退出時,操作系統(tǒng)會回收該進程所申請的所有資源;即使其中任意線程因為操作不當導致內存泄漏,當進程退出時,這些內存也會被正確回收。
比如之前的 IE 瀏覽器,支持很多插件,而這些插件很容易導致內存泄漏,這意味著只要瀏覽器開著,內存占用就有可能會越來越多,但是當關閉瀏覽器進程時,這些內存就都會被系統(tǒng)回收掉。
4. 進程之間的內容相互隔離。
進程隔離是為保護操作系統(tǒng)中進程互不干擾的技術,每一個進程只能訪問自己占有的數(shù)據,也就避免出現(xiàn)進程 A 寫入數(shù)據到進程 B 的情況。正是因為進程之間的數(shù)據是嚴格隔離的,所以一個進程如果崩潰了,或者掛起了,是不會影響到其他進程的。如果進程之間需要進行數(shù)據的通信,這時候,就需要使用用于進程間通信(IPC)的機制了。
單進程瀏覽器時代
在了解了進程和線程之后,我們再來一起看下單進程瀏覽器的架構。顧名思義,單進程瀏覽器是指瀏覽器的所有功能模塊都是運行在同一個進程里,這些模塊包含了網絡、插件、JavaScript 運行環(huán)境、渲染引擎和頁面等。其實早在 2007 年之前,市面上瀏覽器都是單進程的。單進程瀏覽器的架構如下圖所示:
單進程瀏覽器架構示意圖
如此多的功能模塊運行在一個進程里,是導致單進程瀏覽器不穩(wěn)定、不流暢和不安全的一個主要因素。下面我就來一一分析下出現(xiàn)這些問題的原因。
問題 1:不穩(wěn)定
早期瀏覽器需要借助于插件來實現(xiàn)諸如 Web 視頻、Web 游戲等各種強大的功能,但是插件是最容易出問題的模塊,并且還運行在瀏覽器進程之中,所以一個插件的意外崩潰會引起整個瀏覽器的崩潰。
除了插件之外,渲染引擎模塊也是不穩(wěn)定的,通常一些復雜的 JavaScript 代碼就有可能引起渲染引擎模塊的崩潰。和插件一樣,渲染引擎的崩潰也會導致整個瀏覽器的崩潰。
問題 2:不流暢
從上面的“單進程瀏覽器架構示意圖”可以看出,所有頁面的渲染模塊、JavaScript 執(zhí)行環(huán)境以及插件都是運行在同一個線程中的,這就意味著同一時刻只能有一個模塊可以執(zhí)行。
比如,下面這個無限循環(huán)的腳本:
function freeze() { while (1) { console.log("freeze"); }}freeze();復制代碼
如果讓這個腳本運行在一個單進程瀏覽器的頁面里,你感覺會發(fā)生什么?
因為這個腳本是無限循環(huán)的,所以當其執(zhí)行時,它會獨占整個線程,這樣導致其他運行在該線程中的模塊就沒有機會被執(zhí)行。因為瀏覽器中所有的頁面都運行在該線程中,所以這些頁面都沒有機會去執(zhí)行任務,這樣就會導致整個瀏覽器失去響應,變卡頓。這塊內容要繼續(xù)往深的地方講就到頁面的事件循環(huán)系統(tǒng)了,具體相關內容我會在后面的模塊中為你深入講解。
除了上述腳本或者插件會讓單進程瀏覽器變卡頓外,頁面的內存泄漏也是單進程變慢的一個重要原因。通常瀏覽器的內核都是非常復雜的,運行一個復雜點的頁面再關閉頁面,會存在內存不能完全回收的情況,這樣導致的問題是使用時間越長,內存占用越高,瀏覽器會變得越慢。
問題 3:不安全
這里依然可以從插件和頁面腳本兩個方面來解釋該原因。
插件可以使用 C/C++ 等代碼編寫,通過插件可以獲取到操作系統(tǒng)的任意資源,當你在頁面運行一個插件時也就意味著這個插件能完全操作你的電腦。如果是個惡意插件,那么它就可以釋放病毒、竊取你的賬號密碼,引發(fā)安全性問題。
至于頁面腳本,它可以通過瀏覽器的漏洞來獲取系統(tǒng)權限,這些腳本獲取系統(tǒng)權限之后也可以對你的電腦做一些惡意的事情,同樣也會引發(fā)安全問題。
以上這些就是當時瀏覽器的特點,不穩(wěn)定,不流暢,而且不安全。這是一段不堪回首的過去,也許你沒有經歷過,不過你可以想象一下這樣的場景:當你正在用瀏覽器打開多個頁面時,突然某個頁面崩潰了或者失去響應,隨之而來的是整個瀏覽器的崩潰或者無響應,然后你發(fā)現(xiàn)你給老板寫的郵件頁面也隨之消失了,這時你的心情會不會和頁面一樣崩潰呢?
多進程瀏覽器時代
好在現(xiàn)代瀏覽器已經解決了這些問題,是如何解決的呢?這就得聊聊我們這個“多進程瀏覽器時代”了。
早期多進程架構
你可以先看看下面這張圖,這是 2008 年 Chrome 發(fā)布時的進程架構。
早期 Chrome 進程架構圖
從圖中可以看出,Chrome 的頁面是運行在單獨的渲染進程中的,同時頁面里的插件也是運行在單獨的插件進程之中,而進程之間是通過 IPC 機制進行通信(如圖中虛線部分)。
我們先看看如何解決不穩(wěn)定的問題。由于進程是相互隔離的,所以當一個頁面或者插件崩潰時,影響到的僅僅是當前的頁面進程或者插件進程,并不會影響到瀏覽器和其他頁面,這就完美地解決了頁面或者插件的崩潰會導致整個瀏覽器崩潰,也就是不穩(wěn)定的問題。
接下來再來看看不流暢的問題是如何解決的。同樣,JavaScript 也是運行在渲染進程中的,所以即使 JavaScript 阻塞了渲染進程,影響到的也只是當前的渲染頁面,而并不會影響瀏覽器和其他頁面,因為其他頁面的腳本是運行在它們自己的渲染進程中的。所以當我們再在 Chrome 中運行上面那個死循環(huán)的腳本時,沒有響應的僅僅是當前的頁面。
對于內存泄漏的解決方法那就更簡單了,因為當關閉一個頁面時,整個渲染進程也會被關閉,之后該進程所占用的內存都會被系統(tǒng)回收,這樣就輕松解決了瀏覽器頁面的內存泄漏問題。
最后我們再來看看上面的兩個安全問題是怎么解決的。采用多進程架構的額外好處是可以使用安全沙箱,你可以把沙箱看成是操作系統(tǒng)給進程上了一把鎖,沙箱里面的程序可以運行,但是不能在你的硬盤上寫入任何數(shù)據,也不能在敏感位置讀取任何數(shù)據,例如你的文檔和桌面。Chrome 把插件進程和渲染進程鎖在沙箱里面,這樣即使在渲染進程或者插件進程里面執(zhí)行了惡意程序,惡意程序也無法突破沙箱去獲取系統(tǒng)權限。
好了,分析完早期的 Chrome 瀏覽器后,相信你已經了解了瀏覽器采用多進程架構的必要性。
目前多進程架構
不過 Chrome 的發(fā)展是滾滾向前的,相較之前,目前的架構又有了很多新的變化。我們先看看最新的 Chrome 進程架構,你可以參考下圖:
最新的 Chrome 進程架構圖
從圖中可以看出,最新的 Chrome 瀏覽器包括:1 個瀏覽器(Browser)主進程、1 個 GPU 進程、1 個網絡(NetWork)進程、多個渲染進程和多個插件進程。
下面我們來逐個分析下這幾個進程的功能。
- 瀏覽器進程。主要負責界面顯示、用戶交互、子進程管理,同時提供存儲等功能。
- 渲染進程。核心任務是將 html、css 和 JavaScript 轉換為用戶可以與之交互的網頁,排版引擎 Blink 和 JavaScript 引擎 V8 都是運行在該進程中,默認情況下,Chrome 會為每個 Tab 標簽創(chuàng)建一個渲染進程。出于安全考慮,渲染進程都是運行在沙箱模式下。
- GPU 進程。其實,Chrome 剛開始發(fā)布的時候是沒有 GPU 進程的。而 GPU 的使用初衷是為了實現(xiàn) 3D CSS 的效果,只是隨后網頁、Chrome 的 UI 界面都選擇采用 GPU 來繪制,這使得 GPU 成為瀏覽器普遍的需求。最后,Chrome 在其多進程架構上也引入了 GPU 進程。
- 網絡進程。主要負責頁面的網絡資源加載,之前是作為一個模塊運行在瀏覽器進程里面的,直至最近才獨立出來,成為一個單獨的進程。
- 插件進程。主要是負責插件的運行,因插件易崩潰,所以需要通過插件進程來隔離,以保證插件進程崩潰不會對瀏覽器和頁面造成影響。
講到這里,現(xiàn)在你應該就可以回答文章開頭提到的問題了:僅僅打開了 1 個頁面,為什么有 4 個進程?因為打開 1 個頁面至少需要 1 個網絡進程、1 個瀏覽器進程、1 個 GPU 進程以及 1 個渲染進程,共 4 個;如果打開的頁面有運行插件的話,還需要再加上 1 個插件進程。
不過凡事都有兩面性,雖然多進程模型提升了瀏覽器的穩(wěn)定性、流暢性和安全性,但同樣不可避免地帶來了一些問題:
- 更高的資源占用。因為每個進程都會包含公共基礎結構的副本(如 JavaScript 運行環(huán)境),這就意味著瀏覽器會消耗更多的內存資源。
- 更復雜的體系架構。瀏覽器各模塊之間耦合性高、擴展性差等問題,會導致現(xiàn)在的架構已經很難適應新的需求了。
對于上面這兩個問題,Chrome 團隊一直在尋求一種彈性方案,既可以解決資源占用高的問題,也可以解決復雜的體系架構的問題。
未來面向服務的架構
為了解決這些問題,在 2016 年,Chrome 官方團隊使用“面向服務的架構”(Services Oriented Architecture,簡稱SOA)的思想設計了新的 Chrome 架構。也就是說 Chrome 整體架構會朝向現(xiàn)代操作系統(tǒng)所采用的“面向服務的架構” 方向發(fā)展,原來的各種模塊會被重構成獨立的服務(Service),每個服務(Service)都可以在獨立的進程中運行,訪問服務(Service)必須使用定義好的接口,通過 IPC 來通信,從而構建一個更內聚、松耦合、易于維護和擴展的系統(tǒng),更好實現(xiàn) Chrome 簡單、穩(wěn)定、高速、安全的目標。如果你對面向服務的架構感興趣,你可以去網上搜索下資料,這里就不過多介紹了。
Chrome 最終要把 UI、數(shù)據庫、文件、設備、網絡等模塊重構為基礎服務,類似操作系統(tǒng)底層服務,下面是 Chrome“面向服務的架構”的進程模型圖:
Chrome“面向服務的架構”進程模型圖
目前 Chrome 正處在老的架構向服務化架構過渡階段,這將是一個漫長的迭代過程。
Chrome 正在逐步構建 Chrome 基礎服務(Chrome Foundation Service),如果你認為 Chrome 是“便攜式操作系統(tǒng)”,那么 Chrome 基礎服務便可以被視為該操作系統(tǒng)的“基礎”系統(tǒng)服務層。
同時 Chrome 還提供靈活的彈性架構,在強大性能設備上會以多進程的方式運行基礎服務,但是如果在資源受限的設備上(如下圖),Chrome 會將很多服務整合到一個進程中,從而節(jié)省內存占用。
在資源不足的設備上,將服務合并到瀏覽器進程中
總結
好了,今天就到這里,下面我來簡要梳理并總結今天的內容。
本文我主要是從 Chrome 進程架構的視角,分析了瀏覽器的進化史。
最初的瀏覽器都是單進程的,它們不穩(wěn)定、不流暢且不安全,之后出現(xiàn)了 Chrome,創(chuàng)造性地引入了多進程架構,并解決了這些遺留問題。隨后 Chrome 試圖應用到更多業(yè)務場景,如移動設備、VR、視頻等,為了支持這些場景,Chrome 的架構體系變得越來越復雜,這種架構的復雜性倒逼 Chrome 開發(fā)團隊必須進行架構的重構,最終 Chrome 團隊選擇了面向服務架構(SOA)形式,這也是 Chrome 團隊現(xiàn)階段的一個主要任務。
鑒于目前架構的復雜性,要完整過渡到面向服務架構,估計還需要好幾年時間才能完成。不過 Chrome 開發(fā)是一個漸進的過程,新的特性會一點點加入進來,這也意味著我們隨時能看到 Chrome 新的變化。
總體說來,Chrome 是以一個非常快速的速度在進化,越來越多的業(yè)務和應用都逐漸轉至瀏覽器來開發(fā),身為開發(fā)人員,我們不能坐視不管,而應該緊跟其步伐,收獲這波技術紅利。
思考時間
最后,給你留個思考題:回顧瀏覽器的進化路線,你認為推動瀏覽器發(fā)展的主要動力是什么?
歡迎在留言區(qū)與我分享你的想法,也歡迎你在留言區(qū)記錄你的思考過程。感謝閱讀,如果你覺得這篇文章對你有幫助的話,也歡迎把它分享給更多的朋友。