這是一個 Cloudflare Worker,全部不到十行:
export default {
async fetch(request) {
return new Response("Hello from the edge");
}
};
wrangler deploy 推上去,幾秒後它就在全球三百多個城市同時上線了。台北的人連到台北的節點,倫敦的人連到倫敦的節點,沒有一台伺服器是你要管的。
一般的 serverless——AWS Lambda、Google Cloud Run——第一次被呼叫時會有冷啟動(cold start),從幾百毫秒到幾秒不等。這兩家的底層其實不一樣:Google Cloud Run 跑的是真正的 Container;AWS Lambda 用的是更接近輕量虛擬機器的 Firecracker microVM。共通點是冷啟動的成因——不管是 Container 還是 microVM,平台都得先把那層環境拉起來、載入你的執行環境,才能開始跑你的程式碼。
但 Worker 不一樣,它的冷啟動幾乎量不出來,官方數字是 5 毫秒以下。
為什麼?同樣是「把一段程式碼放上雲端、有人呼叫才執行」,差距怎麼會這麼大?
答案是:Worker 根本沒在用 Container。
V8 Isolate:沙箱,不是 Container
要理解 Worker,得先丟掉「每個 serverless 函式 = 一個輕量 Container」這個直覺。
傳統的 serverless 隔離單位是 Container(容器)。每個函式實體都有自己的作業系統使用者空間、自己的一份檔案系統、自己的一份 Node.js runtime。要跑你的程式碼,平台得先把這整套環境拉起來——這就是冷啟動的成本來源。
Worker 的隔離單位是 V8 Isolate(V8 隔離區)。
V8 是 Chrome 和 Node.js 底下那顆 JavaScript 引擎。它有個能力:在同一個 process 裡開出多個互相隔離的執行環境,每個環境有自己的記憶體堆積(heap)、自己的全域物件,彼此看不到對方的變數。瀏覽器就是靠這個機制,讓同一個 renderer process 裡不同的 JS context 不會互相污染——至於跨分頁那層更強的隔離,是 process 級的 Site Isolation 在做的。
Cloudflare 把這個機制搬到伺服器端。一個 Worker process 裡可以塞數千個 isolate,每個 isolate 跑一個客戶的程式碼:
差別在這裡:啟動一個 Container,作業系統要配新的 process、命名空間、cgroup,動輒上百毫秒。啟動一個 isolate,只是在既有 process 裡 new 一個 V8 context、把你的 script 編譯進去——幾毫秒的事。
更關鍵的是,isolate 可以預先暖好。當請求進來、TLS 還在 Handshake 的那幾毫秒,Cloudflare 已經可以把對應的 isolate 準備好。等 Handshake 完成,程式碼立刻能跑。冷啟動就這樣被藏進了你本來就要等的網路延遲裡。
程式碼跑在哪裡:Edge 網路與 Anycast
冷啟動只是一半。另一半是「離你最近」這件事怎麼成立。
傳統雲端你要選 region——us-east-1、ap-northeast-1。你的程式碼跑在那個資料中心,台灣的使用者連 us-east-1,封包要繞半個地球,光速就決定了你逃不掉的延遲。
Cloudflare 沒有 region 的概念,它有的是遍布全球三百多個城市的邊緣節點(PoP,Point of Presence)。你的 Worker 不是部署到某個機房,而是同時部署到每一個節點。
那使用者怎麼連到最近的節點?靠 Anycast。
一般的 IP 位址(Unicast)對應一台機器。Anycast 讓同一個 IP 同時存在於全球幾百個節點,網路的 BGP 路由協定會自動把封包導向「網路上最近」的那個。台北的使用者和倫敦的使用者打同一個 IP,卻會落在不同城市的機房裡——這是網路層幫你做的,你的程式碼完全不用管。
把兩半拼起來,Worker 的完整圖像就出來了:你的程式碼跑在離使用者最近的城市,而且在請求抵達前就已經暖好,沒有 region、沒有冷啟動。 這就是 Edge Computing 的核心——把運算從集中的資料中心,推到網路的邊緣。
一個請求的生命週期
把抽象講完,看一個請求實際怎麼走:
- 使用者瀏覽器送出請求,目標是你的 Anycast IP
- BGP 把封包導到最近的 Cloudflare PoP(假設是台北)
- TLS 交握進行的同時,該節點根據網域找到對應的 Worker,把它的 isolate 暖好
- 交握完成,你的
fetchhandler 在 isolate 裡執行 - handler 裡如果要讀資料(KV、D1、R2),從邊緣的儲存層拿
- Response 從同一個節點直接回給使用者
整條路徑裡,沒有任何一步要「等 Container 起來」。運算發生在台北,使用者也在台北,中間的物理距離被壓到最短。
這也解釋了 Worker 的一個限制從哪來:單一請求的 CPU 時間有上限(免費方案 10ms,付費 30s)。因為一個 isolate 跟數千個鄰居共用同一個 process,沒有 Container 那種硬性的資源隔離牆。一個 isolate 如果無限佔用 CPU,會拖垮整個 process 裡的其他人。所以平台用時間配額來當守門員——這是 isolate 模型省下隔離成本後,必須付的另一筆帳。
與 Container 的本質差異
到這裡,Worker 跟 Container 的分界已經很清楚了。它們不是「同類技術的不同實作」,而是兩種隔離哲學:
| Container | V8 Isolate(Worker) | |
|---|---|---|
| 隔離邊界 | 作業系統(命名空間、cgroup) | V8 引擎(各自的 heap 與 context) |
| 啟動成本 | 數十到數百毫秒 | 5ms 以下 |
| 記憶體 overhead | 每個實體數十 MB 起 | 每個 isolate 數 MB 以下 |
| 能跑的語言 | 任何能裝進 Container 的東西 | JS / TS / WASM(能編成 WASM 的語言) |
| 系統存取 | 完整的檔案系統、Child Process、原生函式庫 | 受限,沒有檔案系統、不能開 Child Process |
| 適合的工作 | 長時間、重運算、需完整 OS 能力 | 短、高頻、延遲敏感的請求處理 |
關鍵的取捨在最後兩列。
Container 給你一整個作業系統使用者空間——你可以裝任何函式庫、讀寫檔案、開 Child Process、跑一個完整的 PostgreSQL。代價是每個實體都背著這套環境的重量,啟動慢、佔記憶體多。
Isolate 把這些全拿掉了。沒有檔案系統、不能 fork、不能載入原生 .so。你能用的就是 JavaScript/WASM 加上 Web 標準 API(fetch、crypto、streams)。換來的是近乎免費的啟動和極小的記憶體佔用,讓「同一個 process 塞數千個租戶」這件事在經濟上成立。
所以這不是誰取代誰。Container 是給工作站的隔離,isolate 是給請求的隔離。 Container 處理「跑一個服務」,isolate 處理「回一個請求」。
一個常見的誤解:Container 不是「零開銷」
很多文章會告訴你:Container 共用主機核心、沒有 hypervisor,所以「幾乎沒有效能損耗」。我不太同意這個講法。
這句話只在跟虛擬機器(VM)比的時候成立。Container 確實比 VM 輕——它省掉了一整層 guest OS。但「比 VM 輕」跟「沒有開銷」是兩回事,這兩件事很常被混在一起講。
把一個 Container 跑起來,底下這些成本一個都沒少:
- 網路。封包要穿過 veth pair、bridge,在 Kubernetes 裡還多一層 overlay 網路(VXLAN 封裝)。每個進出的封包都要過 iptables / conntrack——低流量時你感覺不到,但一到高並行,這些逐封包的處理就會實實在在地吃掉 CPU、墊高延遲。
- 檔案系統。overlayfs 的 copy-on-write 分層,讓寫入和大量小檔的 metadata 操作比直接讀寫慢一截。
- syscall 過濾。seccomp 對每一次系統呼叫都套一層 BPF 過濾,單次成本很小,呼叫量大就量得出來。
- CPU 配額。cgroup 的 CFS quota 會在你還沒用滿平均額度時就把 process throttle 掉,讓尾延遲(tail latency)忽然跳高——這是 Container 化服務很經典的雷。
這些開銷平常被「Container 很輕」這句話蓋過去,因為對多數應用它確實夠輕。但「夠輕」不等於「沒有」。當你的服務跑在幾千個 Container、又在追求個位數毫秒的尾延遲時,這些藏起來的成本會一個個浮上檯面。
Container 不是沒有開銷,是它的開銷被攤平、被藏進了「跟 VM 比起來很輕」這個對照組裡。
而這正好展現出 isolate 的優勢——它連 Container 都甩不掉的那層 OS 隔離成本,也一起拿掉了。
限制與取捨,講白一點
Worker 快歸快,但它的模型逼你接受幾件事:
- 沒有完整的 Node.js。雖然 Cloudflare 補了一層
nodejs_compat,但很多依賴原生模組或檔案系統的 npm 套件直接不能用。你得用 Web 標準的方式重寫。 - CPU 時間有硬上限。重運算(影片轉檔、大型 ML 推論)不適合塞進 Worker。那是 Container 的場子。
- 沒有本地狀態。isolate 隨時可能被回收,你不能把資料存在記憶體變數裡指望它還在。狀態要外放到 KV、D1、Durable Objects。
- 冷門語言要先過 WASM。Rust、Go 可以編成 WASM 跑,但體驗和工具鏈不如原生 JS 順。
這些不是 bug,是 isolate 模型的必然結果。你享受了零冷啟動,就得接受一個「不是完整作業系統」的執行環境。
未來趨勢:邊緣會長出更多東西
Edge Computing 早期被當成「CDN 加一點邏輯」——在邊緣改改 header、做做轉址。現在的方向是把整個應用後端搬到邊緣,而瓶頸從來不是運算,是狀態。運算可以無狀態地散到幾百個節點,但資料庫不行。所以這幾年的演進,幾乎都在解這個問題:
- Durable Objects 給了邊緣「有狀態的單例」——每個物件是全球唯一的一個實體,把強一致性的狀態(線上協作的游標、聊天室、計數器)釘在一個確定的位置,同時又在邊緣網路裡。
- KV(最終一致、邊緣快取)、R2(物件儲存)、D1(SQLite 資料庫)把資料層也往邊緣推。KV 和 R2 比較接近真的鋪在邊緣;D1 目前還是單一主要區域加讀取複本,沒有完全分散,但方向一致——讓 Worker 不必每次都回源站拿資料。
- Cloudflare Containers 是個有意思的轉折:Cloudflare 自己也開始提供 Container 了。意思很明白——isolate 跟 Container 不是二選一,而是會在同一個平台裡分工。延遲敏感的入口用 Worker 擋,重運算的後段丟給 Container,Worker 當那個調度的大腦。
- WASM 的成熟會慢慢抹平語言落差。Rust、Go 其實早就能編成 WASM 跑在 isolate 裡,現在卡的是工具鏈,還有那層把 WASM 接上 runtime 的 JS 膠水;等 WASI、component model 這些標準長齊,邊緣就不會再是 JS 獨大。
把這些連起來看,趨勢不是「Worker 取代 Container」,而是兩種模型在邊緣合流:用 isolate 的便宜啟動接住流量,用 Container 的完整能力扛重活,中間用一層邊緣儲存把狀態黏起來。
結語
Cloudflare Workers 之所以沒有冷啟動,不是因為它把 Container 最佳化到極致,而是它從一開始就沒走 Container 這條路。它把隔離邊界從作業系統下移到 V8 引擎,用 isolate 換掉 Container,再靠 Anycast 把這些 isolate 攤平到全球的邊緣節點上。
理解了這層,你看 Edge Computing 就不會只看到「比較快的 CDN」。你會看到一個更根本的選擇:當你願意放棄完整作業系統,換來的是運算變得又快又便宜,還能部署到離全球使用者最近的地方。 Container 和 isolate 不是新舊之爭,是兩種隔離尺度——一個給服務,一個給請求。
下次你按下 wrangler deploy,程式碼在三百個城市同時運行起來、第一個請求就近乎零延遲地被接住時,你不會再覺得那是魔法。你會直接想到那顆 V8 引擎,在某個離使用者最近的機房裡,安靜地 new 出一個 isolate。