JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

內容傳遞網路:把內容搬到更近的地方

快取很有用,但離你近的快取在有人填滿它之前都是空的——而源伺服器可能遠在一片大洋之外。CDN 的解法,是悄悄地把內容的副本,放進數千座離你很近的「倉庫」裡,讓答案永遠不會太遠。

光靠快取解決不了的那個問題

本級的第一篇,把一個讓人不太舒服的事實敲進了腦袋:距離就是延遲,而你打不過光速。一個送往 15,000 公里外伺服器的請求,在每一趟來回上都要付出一筆肥厚的傳播延遲(propagation delay),而一個網頁,是一場由許多次來回構成的漫長對話。第二篇則展示了 HTTP 快取如何反擊——靠著保留一份本地副本,讓一個還新鮮的請求根本不必離開你的機器,或者只回一個小小的 304 而不是整個檔案。快取很強大,但它有個盲點。

這個盲點,就是「冷快取」。瀏覽器快取或一台共用的前向代理,只對「已經有人抓過」的內容有幫助。一個頁面真正的第一位訪客——或某座城市裡的第一位,或內容變更之後的第一位——遇到的是一個空的快取,於是仍然得走完那趟長途、跑去源伺服器。對一個擁有數百萬個各異物件、又不斷更新的熱門影音網站來說,有極大比例的請求都是冷的。快取把「重複的旅程」削減得漂亮極了;但它對「第一趟旅程」幾乎毫無作用,而第一趟,正是那趟要橫渡大洋的旅程。

於是,這就帶出了內容傳遞網路要回答的那個問題。與其乾等使用者去填滿他們附近的快取,不如反過來:如果內容供應者「事先」就把副本推送出去——推進世界各地、實體上離使用者很近的機器裡——讓即使是某地區的第一位訪客,也能發現答案就等在不遠的街角,那會怎樣?這就是整個點子,而本篇接下來,就要拆解它究竟是怎麼運作的。

一連串的在地倉庫

想像一家全球零售商。它可以把每一筆訂單都從一座巨型工廠出貨,每個包裹都付上好幾天的運輸時間——那就是單一的源伺服器。它也可以蓋幾百座區域倉庫,每座都預先備好熱門商品,讓大多數訂單都從一小時車程外的倉庫出貨。內容傳遞網路(CDN)對網頁內容來說,就正是後面那種設計。零售商的中央工廠,是源伺服器(origin server);倉庫,則是邊緣伺服器(edge server)(也叫代理伺服器或快取伺服器),散布在全球數千個地點,每一座都存著內容的副本。

一台邊緣伺服器,骨子裡就是一台反向代理——和上一篇講的是同一個模式,只是被部署到了行星級的規模。反向代理站在源伺服器前面、代它回應;用戶端以為自己是在跟真正的網站對話。CDN 業者把這些反向代理擺進使用者所在的網路裡頭、或靠得很近——擺進大城市的資料中心,而且常常直接擺進某個 ISP 的網路內部。當你索取一張圖片,你抵達的是邊緣伺服器,而不是遙遠的源伺服器,於是你的來回時間,從好幾百毫秒掉到區區幾毫秒。

內容如何抵達邊緣

邊緣伺服器有兩種互補的方式被填滿。常見的是「拉取(pull)」:邊緣一開始是空的,當它所在區域的某位使用者,第一次索取一個它沒有的物件時,它就去源伺服器抓那麼一次、交給使用者,並替後面所有人留下一份副本。這種做法懶惰而便宜——熱門內容自然會在所有被需要的地方被快取下來,冷門內容則永遠不浪費空間。較少見的是「推送(push)」:對於大型的、預先規劃好的事件,供應者會在人潮湧入之前,主動把選定的檔案預先載入到各個邊緣,好讓即使是最最開頭的那位觀眾,也能命中一個溫熱的快取。

請注意,拉取把冷快取問題,從「每位使用者一次成本」變成了「每個區域一次成本」。沒有 CDN 時,東京的一萬名使用者,各自向維吉尼亞的源伺服器走自己那趟長途。有了拉取式 CDN,只有東京的第一位使用者付出那趟長途;邊緣則替另外 9,999 人從城裡的另一頭就近供應。捕捉這件事的指標,是快取命中率(cache hit ratio)——邊緣能用自己副本回答的請求所占的比例。一台運作良好的 CDN 邊緣,常常能在本地回答遠超過 90% 的請求,這就是為什麼即使在巨大負載下,源伺服器也幾乎不費吹灰之力。

找到附近的邊緣:那個 DNS 小把戲

我們已經在全球擺好了數千台邊緣伺服器。但你的瀏覽器只知道一個像 images.example.com 這樣的名稱——它究竟是怎麼最後跟「對的」邊緣對話的?也就是離你最近的那一台,而不是另一個半球的那一台?最普遍的答案,重複利用了一件你早已見過的機械裝置:網域名稱系統。竅門在於:同一個名稱,可以被安排成「依照是誰、從哪裡發問」而解析到不同的 IP 位址。

舞步是這樣編排的。當你查找 images.example.com 時,這次查找被委派給了 CDN 自己的授權名稱伺服器。那台伺服器大致看得出請求是從哪來的——常常是從你的 DNS 解析器的 IP 位址推斷出來,有時則靠一個 EDNS 擴充,把你真實位址的一小段也帶過去而更精確。接著它挑出一台它判斷離你近、又快的邊緣伺服器,把那台邊緣的 IP 位址當作答案傳回。你的瀏覽器渾然不覺,只是單純地連上人家交給它的那個位址。同一個全球性的名稱,悄悄地把每位使用者,導向一台不同的在地機器。

Two users, same name, different edge IP returned by the CDN's DNS:

  User in Tokyo                          User in Frankfurt
     |  "images.example.com?"               |  "images.example.com?"
     v                                      v
  CDN authoritative DNS  --- picks --->  CDN authoritative DNS
     |  sees ~Japan                          |  sees ~Germany
     v  returns 203.0.113.10 (Tokyo edge)    v  returns 198.51.100.7 (Frankfurt edge)
     |                                       |
     v                                       v
  HTTP GET to Tokyo edge (a few ms)      HTTP GET to Frankfurt edge (a few ms)
     (origin in Virginia is never touched on a cache hit)
基於 DNS 的轉址:同一個主機名稱,會依發問者所在的位置,解析到不同的、就近的邊緣 IP。

把整個請求從頭走到尾

讓我們追蹤一次圖片請求,從點擊到畫面成像,看每一個零件如何協同運作。某地區的第一位使用者,會多付一點點代價;他之後的每一個人,都搭著他留下的溫熱快取一路順滑而行。

  1. 你的瀏覽器需要 images.example.com。它去問自己的 DNS 解析器,而後者(在走完慣常的「根 → 頂級網域 → 授權」那串之後)抵達了 CDN 的授權伺服器。
  2. CDN 的 DNS 記下請求大致來自哪裡,回傳一台就近的邊緣伺服器的 IP,並附上一個很短的 TTL,好讓這個選擇之後還能被修正。
  3. 你的瀏覽器對那台邊緣開一條連線——只隔著一趟短短的來回——並送出一個普通的 HTTP GET,就彷彿它就是源伺服器一樣。
  4. 快取命中:邊緣已經握有一份新鮮的副本(仍在它的 Cache-Control 有效期內),於是它立刻回覆。這是又快又常見的情形,源伺服器完全不會被聯絡到。
  5. 快取未命中(你是第一個,或副本過期了):邊緣橫渡大洋、向源伺服器抓那麼一次——可能會送出一個帶 ETag 的條件請求,若它那份過時副本仍然有效,就拿回一個便宜的 304——接著供應給你,並把副本存下來給下一位訪客。

退一步,欣賞一下這分工。DNS 解決了「找到附近的邊緣」;反向代理式的邊緣解決了「就近供應內容」;帶 Cache-Control 與 ETag 的 HTTP 快取,則解決了「在不重新下載一切的前提下,讓那些副本保持新鮮」。本篇裡,這些機制沒有一個是新的——CDN,就是當你把前幾級的零件,以全球規模巧妙地接在一起時,所得到的東西。這才是真正的教訓:大型系統,通常都是熟悉的零件,被巧妙地排列出來。