關(guān)于 etag 的生成需要滿足幾個條件,至少是寬松滿足
- 當(dāng)文件更改時,etag 值必須改變。
- 盡量便于計算,不會特別耗 CPU。這樣子利用摘要算法生成 (MD5, SHA128, SHA256) 需要慎重考慮,因為他們是 CPU 密集型運算
- 必須橫向擴展,分布式部署時多個服務(wù)器節(jié)點上生成的 etag 值保持一致。這樣子 inode 就排除了
以上幾個條件是理論上的成立條件,那在真正實踐中,應(yīng)該如何處理?
我們來看一下 Nginx 中是如何做的
nginx 中 ETag 的生成
我翻閱了 nginx 的源代碼,并翻譯成偽代碼如下:由 last_modified 與 content_length 拼接而成
etag = header.last_modified + header.content_lenth
可見源碼位置,并在以下貼出: ngx_http_core_modules.c
etag->value.len = ngx_sprintf(etag->value.data, ""%xT-%xO"",
r->headers_out.last_modified_time,
r->headers_out.content_length_n)
- etag->value.data;
總結(jié):nginx 中 etag 由響應(yīng)頭的 Last-Modified 與 Content-Length 表示為十六進制組合而成。
隨手在我的k8s集群里找個 nginx 服務(wù)測試一下
$ curl --head 10.97.109.49
HTTP/1.1 200 OK
Server: nginx/1.16.0
Date: Tue, 10 Dec 2019 06:45:24 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 23 Apr 2019 10:18:21 GMT
Connection: keep-alive
ETag: "5cbee66d-264"
Accept-Ranges: bytes
由 etag 計算 Last-Modified 與 Content-Length,使用 js 計算如下,結(jié)果相符
> new Date(parseInt('5cbee66d', 16) * 1000).toJSON()
"2019-04-23T10:18:21.000Z"
> parseInt('264', 16)
612
Nginx 中的 ETag 算法及其不足
協(xié)商緩存用來計算資源是否返回 304,我們知道協(xié)商緩存有兩種方式
- Last-Modified/if-Modified-Since
- ETag/If-None-Match
既然在 nginx 中 ETag 由 Last-Modified 和 Content-Length 組成,那它便算是一個加強版的 Last-Modified 了,那加強在什么地方呢?
Last-Modified 是由一個 unix timestamp 表示,則意味著它只能作用于秒級的改變,而 nginx 中的 ETag 添加了文件大小的附加條件
那下一個問題:如果 http 響應(yīng)頭中 ETag 值改變了,是否意味著文件內(nèi)容一定已經(jīng)更改
答案:不能。
因此使用 nginx 計算 304 有一定局限性:在 1s 內(nèi)修改了文件并且保持文件大小不變。但這種情況出現(xiàn)的概率極低就是了,因此在正常情況下可以容忍一個不太完美但是高效的算法。
文章出自:??前端餐廳??,如有轉(zhuǎn)載本文請聯(lián)系前端餐廳ReTech今日頭條號。
github:https://github.com/zuopf769