1. 程式人生 > >Web開發須知的瀏覽器內幕 快取與儲存篇(2)

Web開發須知的瀏覽器內幕 快取與儲存篇(2)

3. HTTP Cache

綜述

HTTP Cache是完全按照IETF規範實現的,最新的RFC規範地址是
https://tools.ietf.org/html/rfc7234。它的作用就是儲存可快取的響應以備重新使用,在下次請求時可減少響應時間和網路頻寬。只有GET和HEAD method會快取。

瀏覽器的優化

瀏覽器是過濾了部分沒有意義進行快取的響應頭才儲存到磁碟,例如Connection(keep-alive)、www-authenticate等。這能減少耗時較多的磁碟IO時間。另外Cookie也不會儲存在HTTP Cache,而是存到專門的Cookie Storage。

容量

容量是以整體計算的,不區分Domain。

PC上的Chromium是以80MB為基準結合磁碟可用空間來考慮的,最大是320MB。演算法是:

// js虛擬碼
if (可用空間 < 100MB)
  容量 = 80%的可用空間  // < 80MB
else if (可用空間 < 800MB)
  容量 = 80MB
else if (可用空間 < 2000MB)  // 約2GB
  容量 = 10%的可用空間  // < 200MB
else if (可用空間 < 20000MB)  // 約20GB
  容量 = 200MB
else  // >= 20000MB
  容量 = Math.min(1%的可用空間, 320
MB) // 200MB <= 容量 <= 320MB

其它基於Chromium開發的瀏覽器可能會修改這個演算法,特別是移動瀏覽器會擴大那個常量值,以更高容量來提高資源複用數(嗯,不是複用率)。

淘汰

淘汰演算法是一般是LRU,但在一些場景會結合檔案大小、時間因素等做進一步優化。Cache的管理模組會記錄總的快取大小,每次建立新的快取時會判斷是否快取是否超出容量限制,滿了就會按LRU淘汰一定比例的快取。

瀏覽器需要對快取的檔案進行索引,如果這個索引損壞,瀏覽器會刪除所有的快取。使用者也可以通過設定介面來刪除。此外,第三方程式也會做清理。

Revalidation

使用者點選重新整理按鈕,會強制走revalidate流程,其它的情況都按照規範來執行。

RFC規範不只是為伺服器和瀏覽器設計的,還考慮了網路中的各種節點,比如代理伺服器、CDN伺服器、智慧路由等。

Chromium肯定是嚴格遵守RFC規範的,但第三方瀏覽器通常會破壞這些規範來獲得一定的效能提升。比如更寬鬆的快取條件。

Chromium程式碼參考:http_response_headers.cc : RequiresValidation

後端優化

後端需要在響應中新增Cache-Control來利用瀏覽器快取。

  • Expires不要超過一年。
  • 穩定的檔案應該加上max-age。max-age不要大於2^31,以免大於int32而變成負數。
  • Some HTTP/1.0 caches might not implement Cache-Control.對HTTP 1.0可以使用Pragma

沒有任何與過期相關的指令的話,是由client端決定是否快取的。Chromium有快取,但再次請求的時候並不會走Revalidate流程,還是會得到200 OK。

因為HTTP Cache以URL為key,所以不想用以前的快取,則可以更換URL,例如加不同的query、改檔名(如加上MD5或版本號)等。URL是忽略錨點的。

要做效能優化的同學,可以在協議文件裡淘金。鑑於網上也有不少文章,這裡不做整理了。

4. Cookie Storage

綜述

因為Cookie是在多個請求頭中複用資料的,所以需要從響應頭中抽取出來另外儲存。而且Cookie有自己的生命週期管理語法,就有獨立的模組來管理。Cookie資料同時儲存在記憶體和磁碟。

容量

容量是規範裡就有建議最小值的。最新規範是RFC6265,它引用兩個比較舊的規範RFC2965RFC2109

其中最老的規範RFC2109的6.3.1節中就有說明:

拒絕服務攻擊
   瀏覽器應該按照host或域名設定Cookie的數量和資料大小上限。
Denial of Service Attacks
   User agents may choose to set an upper bound on the number of cookies
   to be stored from a given host or domain name or on the size of the
   cookie information.  Otherwise a malicious server could attempt to
   flood a user agent with many cookies, or large cookies, on successive
   responses, which would force out cookies the user agent had received
   from other servers.  However, the minima specified above should still
   be supported.

Chromium的實現是:
- 每個Cookie的最大長度為4096 bytes。大於這個長度的Cookie將不被處理,即不會儲存。
- 每個域的最大數量是180個
- 總體的個數是3300個

記憶體快取

從容量可知,所有Cookie佔用的最大記憶體為3300*4K ≈ 13M。這點記憶體在手機上也是支撐得了的,所以Chromium會把硬碟上的全部Cookie資料都讀到記憶體,每次傳送請求都是在記憶體中查詢,所以速度很快。在收到響應,需要建立或更新Cookie時,Chromium才會去寫硬碟。而這個寫操作是在非網路執行緒中完成的,避免慢速的檔案IO佔用網路執行緒的時間。

記憶體中的組織是以eTLD+1為key放在multimap裡。

Chromium用SQLite存放cookie。在PC上是對value加密的。在iOS不加密,因為它的沙箱機制足夠完善了,除非越獄。

Chromium把增刪改表示為操作,向資料庫發指令,而不是全部寫一次。它是在後臺執行緒flush。每30秒或滿512次操作就直接Flush。

(注:本節的描述經過簡化,非真實完整的實現)

淘汰

每次建立或更新Cookie就會進行垃圾回收的判斷。有下列的規則:
1. 先淘汰過期的。即超出Max-Age指定時間。
2. 如果超出容量,則會按LRU規則(這裡的used是指accessed)淘汰掉300個Cookie。
3. 如果最近30天內有訪問過,即使超出容量也不會淘汰掉。

下面是Chromium原始碼中的部分註釋供參考。

// Any cookies accessed more recently than kSafeFromGlobalPurgeDays will not
// be evicted by global garbage collection, even if we have more than
// kMaxCookies.  This does not affect domain garbage collection.
const int CookieMonster::kSafeFromGlobalPurgeDays = 30;


const size_t CookieMonster::kPurgeCookies = 300;

const size_t CookieMonster::kDomainCookiesQuotaLow = 30;
const size_t CookieMonster::kDomainCookiesQuotaMedium = 50;
const size_t CookieMonster::kDomainCookiesQuotaHigh =
    kDomainMaxCookies - kDomainPurgeCookies - kDomainCookiesQuotaLow -
    kDomainCookiesQuotaMedium;

開發建議

  • 瀏覽器可能會被使用者設定成禁用Cookie。當確實需要Cookie而發現獲取不了時,請做好一定的提示,提升使用者體驗。
  • 設好max-age,不要讓冗餘的cookie加入到請求頭裡,可加速連網過程。
  • 因為都在記憶體,Cookie操作的時耗較少,但太大的cookie會在連網階段造成較高的延時。還是乖乖地加上Expire吧。