宏觀視角下的瀏覽器:01 Chrome 架構: 僅僅打開了 1 個頁面?為什么有 4 個進程?


  前言:該篇說明:請見 說明 —— 瀏覽器工作原理與實踐 目錄

 

  無論你是想要設計高性能 Web 應用,還是要優化現有的 Web 應用,你都需要了解瀏覽器中的網絡流程、頁面渲染過程,JavaScript 執行流程,以及 Web 安全理論,而這些功能是分散在瀏覽器的各個功能組件中的,比較多、比較散,要怎樣學習才能掌握呢?通過瀏覽器的多進程架構的學習,你就可以把這些分散的知識點串起來,組成一張網,從而讓自己能站在更高的維度去理解 Web 應用。

 

  因此,學習瀏覽器的多進程架構是很有必要的。需要說明的是,在本專欄中,我所有的分析都是基於 Chrome 瀏覽器的。那么多瀏覽器,為什么偏偏選擇 Chrome 瀏覽器呢?因為 Chrome、微軟的 Edge 以及國內的大部分主流瀏覽器,都是基於 Chromium 二次開發而來;而 Chrome 是 Google 的官方發行版,特性和 Chromium 基本一樣,只存在一些產品層面差異;再加上 Chrome 是目前世界上使用率最高的瀏覽器,所以 Chrome 最具代表性

 

  在開始之前,我們一起看下,Chrome 打開一個頁面需要啟動多少進程?你可以點擊 Chrome 瀏覽器右上角的“選項”菜單,選擇“更多工具”子菜單,點擊“任務管理器”,這將打開 Chrome 的任務管理器的窗口,如下圖:

Chrome 的任務管理器窗口

 

  和 Windows 任務管理器一樣,Chrome 任務管理器也是用來展示運行中 Chrome 使用的進程信息的。從圖中可以看到,Chrome 啟動了 4 個進程,你也許會好奇,只是打開了 1 個頁面,為什么要啟動這么多進程呢?

 

  在解答這個問題之前,我們需要了解一下進程的概念,不過由於好多人容易把進程和線程的概念混淆,從而影響后續其他概念的理解,所以這里我就將這兩個概念以及它們之間的關系一並為你講解下。

 

進程與線程

   不過,在介紹進程和線程之前,我需要先講解下什么是並行處理,因為如果你理解了並行處理的概念,那么再理解進程和線程之間的關系就會變得輕松許多。

 

什么是並行處理

計算機中的並行處理就是同一時刻處理多個任務,比如我們要計算下面這三個表達式的值,並顯示出結果。

A = 1+2 B = 20/5
C = 7*8

在編寫代碼的時候,我們可以把這個過程拆分為四個任務:

  • 任務 1 是計算 A=1+2;
  • 任務 2 是計算 B=20/5;
  • 任務 3 是計算 C=7*8;
  • 任務 4 是顯示最后計算的結果。

正常情況下程序可以使用單線程來處理,也就是分四步按照順序分別執行這四個任務。

 

如果采用多線程,會怎么樣呢?我們只需分“兩步走”:第一步,使用三個線程同時執行前三個任務;第二步,再執行第四個顯示任務。

 

通過對比分析,你會發現用單線程執行需要四步,而使用多線程只需要兩步。因此,使用並行處理能大大提升性能。

 

線程 VS 進程

多線程可以並行處理任務,但是線程是不能單獨存在的,它是由進程來啟動和管理的。那什么又是進程呢?

 

一個進程就是一個程序的運行實例。詳細解釋就是: 啟動一個程序的時候,操作系統會為該程序創建一塊內存,用來存放代碼、運行中的數據和一個執行任務的主線程 ,我們把這樣的一個  運行環境  進程

 

為了讓你更好地理解上述計算過程,我畫了下面這張對比圖:

單線程與多線程的進程對比圖

 

從圖中可以看到,線程是依附於進程的,而進程中使用多線程並行處理能提升運算效率。

 

總結來說,進程和線程之間的關系有以下 4 個特點:

 

1. 進程中的任意一線程執行出錯,都會導致整個進行的崩潰。

我們可以模擬以下場景:

 

A = 1+2
B = 20/0
C = 7*8

 

我把上述三個表達式稍作修改,在計算 B 的值的時候,我把表達式的分母改成 0 ,當線程執行到 B = 20/0 時,由於分母為 0 , 線程會執行出錯,這樣就會導致整個進程的崩潰,當然另外兩個線程執行的結果也沒有了。

 

2. 線程之間共享進程中的數據。

如下圖所示,線程之間可以對進程的公共數據進行讀寫操作。

線程之間共享進程中的數據示意圖

從上圖可以看出,線程1、線程 2、線程 3 分別把執行的結果寫入 A、B、C 中,然后線程 2 繼續從 A、B、C 中讀取數據,用來顯示執行結果。

 

3. 當一個進程關閉之后,操作系統會回收進程所占用的內存。

當一個進程退出時,操作系統會回收該進程所申請的所有資源;即使其中任意線程因為操作不當導致內存泄露,當進程退出時,這些內存也會被正確回收。

 

比如之前的 IE 瀏覽器,支持很多插件,而這些插件很容易導致內存泄露,這意味着只要瀏覽器開着,內存占用就有可能會越來越多,但是當關閉瀏覽器進程時,這些內存就都會被系統回收掉。

 

4. 進程之間的內容相互隔離。

進程隔離是為保護操作系統中進程互不干擾的技術,每一個進程只能訪問自己占有的數據,也就避免出現進程 A 寫入數據到進程 B 的情況。正是因為進程之間的數據是嚴格隔離的,所以一個進程如果崩潰了,或者掛起了,是不會影響到其他進程的。如果進程之間需要進行數據的通信,這時候,就需要使用用於進程間通信(IPC)的機制了。

 

單進程瀏覽器時代

在了解了進程和線程之后,我們再來一起看下單進程瀏覽器的架構。顧名思義,單進程瀏覽器是指瀏覽器的所有功能模塊都是運行在同一個進程里,這些模塊包含了網絡、插件、JavaScript 運行環境、渲染引擎和頁面等。其實早在 2007 年之前,市面上瀏覽器都是單進程的。單進程瀏覽器的架構如下圖所示:

單進程瀏覽器架構示意圖

 

如此多的功能模塊運行在一個進程里,是導致單進程瀏覽器不穩定不流暢不安全的一個主要因素。下面我就來一一分析下出現這些問題的原因。

 

問題1:不穩定

早期瀏覽器需要借助於插件來實現諸如 Web 視頻、Web游戲等各種強大的功能,但是插件是最容易出問題的模塊,並且還運行在瀏覽器進程之中,所以一個插件的意外崩潰會引起整個瀏覽器的崩潰。

 

除了插件之外,渲染引擎模塊也是不穩定的,通常一個復雜的 JavaScript 代碼都有可能引起渲染引擎模塊的崩潰。和插件一樣,渲染引擎的崩潰也會導致整個瀏覽器的崩潰。

 

問題2:不流暢

從上面的“單進程瀏覽器架構示意圖”可以看出,所有頁面的渲染模塊、JavaScript 執行環境以及插件都是運行在同一個線程中的,這就意味着同一時刻只能有一個模塊可以執行。

 

比如,下面這個無限循環的腳本:

 

function freeze() {
  while (1) {
    console.log("freeze");
  }
}
freeze();

 

如果讓這個腳本運行在一個單進程瀏覽器的頁面里,你感覺會發生什么?

 

因為這個腳本是無限循環的,所以當其執行時,它會獨占整個線程,這樣導致其他運行在該線程中的模塊就沒有機會被執行。因為瀏覽器中所有的頁面都運行在該線程中,所以這些頁面都沒有機會去執行任務,這樣就會導致整個瀏覽器失去響應,變卡頓。這塊內容要繼續往深的地方講就到頁面的事件循環系統了,具體相關內容我會在后面的模塊中為你深入講解。

 

除了上述腳本或者插件會讓單進程瀏覽器變卡頓外,頁面的內存泄露也是單進程變慢的一個重要原因。通常瀏覽器的內核都是非常復雜的,運行一個復雜點的頁面再關閉頁面,會存在內存不能完全回收的情況,這樣導致的問題是使用時間越長,內存占用越高,瀏覽器會變得越慢。

 

問題3:不安全

這里依然可以從插件和頁面腳本兩個方面來解釋該原因。

 

插件可以使用 C/C++ 等代碼編寫,通過插件可以獲取到操作系統的任意資源,當你在頁面運行一個插件時也就意味着這個插件能完全操作你的電腦。如果是個惡意插件,那么它就可以釋放病毒、竊取你的賬號密碼,引發安全性問題。

 

至於頁面腳本,它可以通過瀏覽器的漏洞來獲取系統權限,這些腳本獲取系統權限之后也可以對你的電腦做一些惡意的事情,同樣也會引發安全問題。

 

以上這些就是當時瀏覽器的特點,不穩定,不流暢,而且不安全。這是一段不堪回首的過去,也許你沒有經歷過,不過你可以想象一下這樣的場景:當你正在用瀏覽器打開多個頁面時,突然某個頁面崩潰了或者失去響應,隨之而來的是整個瀏覽器的崩潰或者無響應,然后你發現你給老板寫的郵件頁面也隨之消失了,這時你的心情會不會和頁面一樣崩潰呢?

 

多進程瀏覽器時代

好在現代瀏覽器已經解決了這些問題,是如何解決的呢?這就得聊聊我們這個“多進程瀏覽器時代”了。

 

早期多進程架構

可以先看看下面這張圖,這是 2008 年 Chrome 發布時的進程架構。

早期 Chrome 進程架構圖

 

從圖中可以看出,Chrome 的頁面是運行在單獨的渲染進程中的,同時頁面里的插件也是運行在單獨的插件進程之中,而進程之間是通過 IPC 機制進行通信(如圖中虛線部分)。

 

我們先看看如何解決不穩定的問題。由於進程是相互隔離的,所以當一個頁面或者插件崩潰時,影響到的僅僅是當前的頁面進程或者插件進程,並不會影響到瀏覽器和其他頁面,這就完美地解決了頁面或者插件的崩潰會導致整個瀏覽器崩潰,也就是不穩定的問題。

 

接下來再來看看不流暢的問題是如何解決的。同樣,JavaScript 也是運行在渲染進程中的,所以即使 JavaScript 阻塞了渲染進程,影響到的也只是當前的渲染頁面,而並不會影響瀏覽器和其他頁面,因為其他頁面的腳本是運行在它們自己的渲染進程中的。所以當我們再在 Chrome 中運行上面那個死循環的腳本時,沒有響應的僅僅是當前的頁面。

 

對於內存泄漏的解決方法那就更簡單了,因為當關閉一個頁面時,整個渲染進程也會被關閉,之后該進程所占用的內存都會被系統回收,這樣就輕松解決了瀏覽器頁面的內存泄漏問題。

 

最后我們再來看看上面的兩個安全問題是怎么解決的。采用多進程架構的額外好處是可以使用安全沙箱,你可以把沙箱看成是操作系統給進程上了一把鎖,沙箱里面的程序可以運行,但是不能在你的硬盤上寫入任何數據,也不能在敏感位置讀取任何數據,例如你的文檔和桌面。Chrome 把插件進程和渲染進程鎖在沙箱里面,這樣即使在渲染進程或者插件進程里面執行了惡意程序,惡意程序也無法突破沙箱去獲取系統權限。

 

好了,分析完早期的 Chrome 瀏覽器后,相信你已經了解了瀏覽器采用多進程架構的必要性。

 

目前多進程架構

不過Chrome 的發展是滾滾向前的,相較之前,目前的架構又有了很多新的變化。我們先看看最新的 Chrome 進程架構,你可以參考下圖:

最新的 Chrome 進程架構圖

 

從圖中可以看出,最新的 Chrome 瀏覽器包括:1 個瀏覽器(Browser)主進程、1 個 GPU 進程、1 個網絡(NetWork)進程、多個渲染進程和多個插件進程。

 

下面我們來逐個分析下這幾個進程的功能。

  • 瀏覽器進程。主要負責界面顯示、用戶交互、子進程管理,同時提供存儲等功能。
  • 渲染進程。核心任務是將 HTML、CSS 和 JavaScript 轉換為用戶可以與之交互的網頁,排版引擎 Blink 和 JavaScript 引擎 V8 都是運行在該進程中,默認情況下,Chrome 會為每個 Tab 標簽創建一個渲染進程。處於安全考慮,渲染進程都是運行在沙箱模式下。
  • GPU進程。其實,Chrome 剛開始發布的時候是沒有 GPU 進程的。而 GPU 的使用初衷是為了實現 3D CSS的效果,只是隨后網頁、Chrome 的 UI 界面都選擇采用 GPU 來繪制,這使得 GPU 成為瀏覽器普遍的需求。最后,Chrome 在其多進程架構上也引入了 GPU進程。
  • 網絡進程。主要負責頁面的網絡資源加載,之前是作為一個模塊運行在瀏覽器進程里面的,直至最近才獨立出來,成為一個單獨的進程。
  • 插件進程。主要是負責插件的運行,因插件易崩潰,所以需要通過插件進程來隔離,以保證插件進程崩潰不會對瀏覽器和頁面造成影響。

 

講到這里,現在你應該就可以回答文章開頭提到的問題了:僅僅打開了 1 個頁面,為什么有 4 個進程?因為打開 1 個頁面至少需要 1 個網絡進程、1 個瀏覽器進程、 1 個GPU進程 以及 1個 渲染進程,共 4 個;如果打開的頁面有運行插件的話,還需要加上 1 個插件進程。

 

不過凡事都有兩面性,雖然多進程模型提升了瀏覽器的穩定性、流暢性和安全性,但同樣不可避免地帶來了一些問題:

  • 更高的資源占用。因為每個進程都會包含公共基礎結構的副本(如 JavaScript 運行環境),這就意味着瀏覽器會消耗更多的內存資源。
  • 更復雜的體系架構。瀏覽器各模塊之間耦合性高、擴展性差等問題,會導致現在的架構已經很難適應新的需求了。

 

對於上面這兩個問題,Chrome 團隊一直在尋求一種彈性方案,既可以解決資源占用高的問題,也可以解決復雜的體系架構的問題。

 

未來面向服務的架構

了解決這些問題,在 2016 年,Chrome 官方團隊使用 “面向服務的架構”(Services Oriented Architecture,簡稱 SOA )的思想設計了新的 Chrome 架構。也就是說 Chrome 整體架構會朝向現代操作系統所采用的“面向服務的架構” 方向發展,原來的各種模塊會被重構成獨立的服務(Service),每個服務(Service)都可以在獨立的進程中運行,訪問服務(Service)必須使用定義好的接口,通過 IPC 來通信,從而構建一個更內聚、松耦合、易於維護和擴展的系統,更好實現Chrome 簡單、穩定、高速、安全的目標。如果你對面向服務的架構感興趣,你可以去網上搜索下資料,這里就不過多介紹了。

 

Chrome 最終要把 UI、數據庫、文件、設備、網絡等模塊重構為基礎服務,類似操作系統底層服務,下面是 Chrome“面向服務的架構”的進程模型圖:

Chrome “面向服務的架構” 進程模型圖

 

目前Chrome 正處在老的架構向服務化架構過渡階段,這將是一個漫長的迭代過程。

 

Chrome 正在逐步構建 Chrome 基礎服務(Chrome Foundation Service),如果你認為 Chrome 是“便攜式操作系統”,那么 Chrome 基礎服務便可以被視為該操作系統的“基礎”系統服務層。

 

同時 Chrome 還提供靈活的彈性架構,在強大性能設備上會以多進程的方式運行基礎服務,但是如果在資源受限的設備上(如下圖),Chrome 會將很多服務整合到一個進程中,從而節省內存占用。

在資源不足的設備上,將服務合並到瀏覽器進程中

 

總結

好了,今天就到這里,下面我來簡要梳理並總結今天的內容。

 

本文我主要是從 Chrome 進程架構的視角,分析了瀏覽器的進化史。

 

最初的瀏覽器都是單進程的,它們不穩定、不流暢且不安全,之后出現了 Chrome,創造性地引入了多進程架構,並解決了這些遺留問題。隨后 Chrome 試圖應用到更多業務場景,如移動設備、VR、視頻等,為了支持這些場景,Chrome 的架構體系變得越來越復雜,這種架構的復雜性倒逼 Chrome 開發團隊必須進行架構的重構,最終 Chrome 團隊選擇了面向服務架構(SOA)形式,這也是 Chrome 團隊現階段的一個主要任務。

 

鑒於目前架構的復雜性,要完整過渡到面向服務架構,估計還需要好幾年時間才能完成。不過 Chrome 開發是一個漸進的過程,新的特性會一點點加入進來,這也意味着我們隨時能看到 Chrome 新的變化。

 

總體說來,Chrome 是以一個非常快速的速度在進化,越來越多的業務和應用都逐漸轉至瀏覽器來開發,身為開發人員,我們不能坐視不管,而應該緊跟其步伐,收獲這波技術紅利。

 

思考時間

最后,給你留個思考題:回顧瀏覽器的進化路線,你認為推動瀏覽器發展的主要動力是什么?

一個角度來說,最大動力就是chrome的出現。曾經的IE像極了諾基亞,chrome就像是橫空出世的iPhone ,當着IE的面告訴IE,瀏覽器應該這么玩兒。
另一個角度也是互聯網的發展需要,人們所需要的不再是只是簡單展示個頁面的瀏覽器,需要有復雜的交互,瀏覽器應該能做更多的事情,這對瀏覽器的穩定性、以及性能都有了新的要求。所以出來一個性能符合要求的瀏覽器也是必須的。
還有就是11年后相對規范的es5的出現,再之后es6.7,web能做的事情越來越多了,web工程化,再后來node的出現,前端體系越來越龐大,

 

問題記錄:

1、即使是如今的多進程架構,我偶爾還會碰到一些由於單個頁面卡死最終崩潰導致所有頁面崩潰的情況,請問這是什么原因呢?

作者回答:是這樣的,通常情況下是一個頁面使用一個進程,但是,有一種情況,叫"同一站點(same-site)",具體地講,我們將“同一站點”定義為根域名(例如,geekbang.org)加上協議(例如,https:// 或者http://),還包含了該根域名下的所有子域名和不同的端口,比如下面這三個:

https://time.geekbang.org
https://www.geekbang.org
https://www.geekbang.org:8080
都是屬於同一站點,因為它們的協議都是https,而根域名也都是geekbang.org。你也許了解同源策略,但是同一站點和同源策略還是存在一些不同地方,在這里你需要了解它們不是同一件事就行了。

Chrome的默認策略是,每個標簽對應一個渲染進程。但是如果從一個頁面打開了新頁面,而新頁面和當前頁面屬於同一站點時,那么新頁面會復用父頁面的渲染進程。官方把這個默認策略叫process-per-site-instance。

直白的講,就是如果幾個頁面符合同一站點,那么他們將被分配到一個渲染進程里面去。

所以,這種情況下,一個頁面崩潰了,會導致同一站點的頁面同時崩潰,因為他們使用了同一個渲染進程。

為什么要讓他們跑在一個進程里面呢?

因為在一個渲染進程里面,他們就會共享JS的執行環境,也就是說A頁面可以直接在B頁面中執行腳本。因為是同一家的站點,所以是有這個需求的。

 

2、單進程瀏覽器開多個頁面,渲染線程也只有一個嗎?感覺一個頁面開一個線程不是更合理嗎?

作者回答:之前回答的有點籠統,下面是我整理過后的回答:

首先這個問題提的很好,我們從IE6開始講起,IE6時代,瀏覽器是單進程的,所有頁面也都是運行在一個主線程中的,當時IE6就是這樣設計,而且此時的IE6是單標簽,也就是說一個頁面一個窗口。

這時候,國內有很多國產瀏覽器,都是基於IE6來二次開發的,而IE6原生架構就是所有頁面跑在單線程里面的,意味着,所有的頁面都共享着同一套JavaScript運行環境,同樣,對於存儲Cookie也都是在一個線程里面操作的。
而且這些國產瀏覽器由於需要,都采用多標簽的形式,所以其中的一個標簽頁面的卡頓都會影響到整個瀏覽器。

基於卡頓的原因,國內瀏覽器就開始嘗試支持頁面多線程,也就是讓部分頁面運行在單獨的線程之中,運行在單獨的線程之中,意味着每個線程擁有單獨的JavaScript執行環境,和Cookie環境,這時候問題就來了:
比如A站點頁面登陸一個網站,保存了一些Cookie數據到磁盤上,再在當前線程環境中保存部分Session數據,由於Session是不需要保存到硬盤上的,所以Session只會保存在當前的線程環境中。這時候再打開另外一個A站點的頁面,假設這個頁面在另外一個線程中里面,那么它首先讀取硬盤上的Cookie信息,但是,由於Session信息是保存在另外一個線程里面的,無法直接讀取,這樣就要實現一個Session同步的問題,由於IE並沒有源代碼,所以實現起來非常空難,國內瀏覽器花了好長一點時間才解決這個問題的。

Session問題解決了,但是假死的問題依然有,因為進程內使用了一個窗口,這個窗口是依附到瀏覽器主窗口之上的,所以他們公用一套消息循環機制,消息循環我們后面會詳細地講,這也就意味這一個窗口如果卡死了。也會導致整個瀏覽器的卡死。

國產瀏覽器又出了一招,就是把頁面做成一個單獨的彈窗,如果這個頁面卡死了,就把這個彈窗給隱藏掉。

這里還要提一下為什么Chrome中的一個頁面假死不會影響到主窗口呢?
這是因為chrome輸出的實際上圖片,然后瀏覽器端把圖片貼到自己的窗口上去,在Chrome的渲染進程內,並沒有一個渲染窗口,輸出的只是圖片,如果卡住了,頂多圖片不更新了。

國產瀏覽器這一套技術花了四五年時間,等這套技術差不多成熟時,Chrome發布了 :(

 

3、請問老師,如果打開了 2個頁面,會有幾個進程呢?是 1 個網絡進程、1 個瀏覽器進程、1 個 GPU 進程以及 2個渲染進程,共 5個嗎?這些進程是可以在瀏覽器開發者中被實際觀察到的嗎?

作者回復: 通常情況下會是五個,但是有很多其他情況:
1:如果頁面里有iframe的話,iframe也會運行在單獨的進程中!
2:如果頁面里有插件,同樣插件也需要開啟一個單獨的進程!
3:如果你裝了擴展的話,擴展也會占用進程
4:如果2個頁面屬於同一站點的話,並且從a頁面中打開的b頁面,那么他們會公用一個渲染進程

這些進程都可以通過chrome的任務管理器來查看。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM