Fastly 全球規模邊緣雲計算實踐

作者 | Ethan Lu

譯者 | Sambodhi

策劃 | 辛曉亮

GIPHY 提供大量的 GIF 媒體內容。事實上,每天有超過 100 億條內容。除代表 GIF 實際下載的媒體請求外,我們還提供了公共 API 和 SDK 服務,讓開發者可以在他們的產品中使用,從而使他們的用戶能夠訪問我們龐大的庫。

和很多每天都有大量流量的科技公司一樣,我們面臨着可擴展性的挑戰。系統必須能夠處理大量的請求 (在每秒 10000 個請求之內),並且響應延遲很小。最糟糕的事莫過於等待加載,特別是 GIF!

這就是邊緣雲平臺(edge cloud platform)發揮作用的地方:邊緣雲平臺並不是讓我們的 AWS 服務器處理每個請求,而是儘可能多地緩存媒體內容和搜索結果 JSON 負載。這樣做是非常有效的,因爲媒體內容和 API 響應都不會頻繁改變。邊緣雲平臺服務器還將請求負載分配給不同的區域。我們使用 Fastly 驅動邊緣雲平臺,爲用戶提供了數十億條內容。

1 Fastly 解決方案

Fastly 提供多種功能,使我們能夠大規模地交付內容。這些特性可以大致歸類爲:

緩存分層

基本邊緣雲平臺的設置是將內容緩存在邊緣。這些服務器節點分佈在全球,向在其區域內發送請求的用戶提供緩存內容。如果邊緣節點沒有任何內容,則會向原始服務器(origin server)發送請求,以便檢索內容。

這樣的單層設置存在缺陷。每一個邊緣節點都根據其區域的請求維護自己的緩存。所以,一個新的內容片段可能不會在任何一個邊緣節點上被緩存,這可能會導致當每個邊緣節點都重複相同的內容請求時,到我們的原始服務器的流量會激增。由於病毒式內容的流行程度越來越高,這種行爲常會出現。

Fastly 提供名爲 Origin Shield 的第二層緩存服務。現在,可以從 Origin Shield 層檢索緩存中沒有請求內容的邊緣節點,請求只需要到達我們的原始服務器。

緩存管理

既然內容被緩存在邊緣和 Origin Shield,我們需要設法管理其緩存策略。並不是所有的內容都應該保持相同的緩存時間,或者說 TTL(Time to Live,生存時間)。例如,單個 GIF 的信息不會有太大的變化,所以它的 API 響應可以在一個相當長的一段時間內被緩存。而 Trending Endpoint 的 API 響應則返回當前趨勢 GIF 的持續更新列表,由於趨勢的性質,它需要在一個較短的 TTL 上。

Fastly 是由 Varnish 驅動的,所以所有的配置都採用 Varnish 配置語言(VCL)代碼的形式執行。邊緣和 Origin Shield 都運行 VCL 代碼,因此我們能夠通過一些簡單的 VCL 代碼,基於 API 端點路徑設置各種緩存 TTL:

# in vcl_fetch
if (req.url ~ "^/v1/gifs/trending") {
 # set 5 minute ttl for trending responses
 set beresp.ttl = 600s;
 return(deliver);
}

並不總是用 VCL 代碼來設置緩存 TTL。發送到 Origin 的 API 請求,可以在 Origin 的響應中對緩存控制指令進行編碼。僅需設置 VCL 代碼即可使其被重寫。在 Origin 中,我們可以通過在 API 響應中設置緩存控制頭,將這一決定傳遞給 Fastly 的 Origin Shield 和邊緣節點。尤其是 Surrogate-Control 頭,因爲這個頭將僅用於 Fastly 節點。所以我們可以更新上述 VCL,使 Surrogate-Control 優先於端點緩存策略,如下所示:

# in vcl_fetch
if (beresp.http.Surrogate-Control ~ "max-age" || beresp.http.Cache-Control ~ "(s-maxage|max-age)"
) {
  # upstream set some cache control headers, so Fastly will use its cache TTL
  return(deliver);
} else {
  # no cache headers, so use cache policies for endpoints
  if (req.url ~ "^/v1/gifs/trending") {
   # set 10 minute ttl for trending responses
   set beresp.ttl = 600s;
   return(deliver);
  }
}

通過這樣的設置,我們可以讓緩存內容通過動態 TTL 策略自動失效,從而滿足我們的需求,但是如果我們不希望等待緩存自然過期,也需要顯式地讓緩存失效。只需通過緩存鍵(URL)就可以讓緩存失效。對於媒體來說,這很有效,但是對 API 的響應有點複雜。

例如,我們的 API 搜索端點可以爲不同的查詢返回相同的 GIF,但是如果我們希望使其失效,則無法知道每個可能生成 GIF 的 URL:

# same GIF can appear in the response of all of these API calls
https://api.giphy.com/v1/gifs/search?api_key=__KEY1__&q=haha
https://api.giphy.com/v1/gifs/search?api_key=__KEY1__&q=hehe
https://api.giphy.com/v1/gifs/search?api_key=__KEY2__&q=lol
https://api.giphy.com/v1/gifs/search?api_key=__KEY3__&q=laugh

對於這種情況,我們利用了 Fastly 的代理鍵(Surrogate Key)!顧名思義,代理鍵能夠唯一地識別緩存的內容,與緩存鍵的方式基本相同。與緩存鍵不同,每個存儲結果可以有多個代理鍵,我們可以設置代理鍵。通過在每個 API 響應中顯示的 GIF ID,使我們可以確定包含特定 GIF 的多個緩存內容:

# same GIF (gif_id_abc) can appear in the response of all of these API calls
https://api.giphy.com/v1/gifs/search?api_key=__KEY1__&q=haha
    Assign Surrogate Key: gif_id_abc
https://api.giphy.com/v1/gifs/search?api_key=__KEY1__&q=hehe
    Assign Surrogate Key: gif_id_abc
https://api.giphy.com/v1/gifs/search?api_key=__KEY2__&q=lol
    Assign Surrogate Key: gif_id_abc
https://api.giphy.com/v1/gifs/search?api_key=__KEY3__&q=laugh
    Assign Surrogate Key: gif_id_abc

還可以爲同一內容添加多個代理鍵:

# same GIF (gif_id_abc) can appear in the response of all of these API calls
https://api.giphy.com/v1/gifs/search?api_key=__KEY1__&q=haha
    Assign Surrogate Key: gif_id_abc
https://api.giphy.com/v1/gifs/search?api_key=__KEY1__&q=hehe
    Assign Surrogate Key: gif_id_abc
https://api.giphy.com/v1/gifs/search?api_key=__KEY2__&q=lol
    Assign Surrogate Key: gif_id_abc
https://api.giphy.com/v1/gifs/search?api_key=__KEY3__&q=laugh
    Assign Surrogate Key: gif_id_abc

代理鍵是一個強大的特性,可以讓我們選擇合適的緩存,非常精確而簡單地使其失效。通過這種設置,我們可以在下列情況使緩存失效:

在邊緣運行代碼

VCL 爲我們在邊緣雲平臺的配置方面提供了大量功能。我們之前展示了配置如何爲邊緣和 Origin Shield 節點設置各種緩存 TTL 策略,但是我們還可以使用 VCL 設置請求信息。

我們可以用代碼來重寫傳入的請求 URL。如果我們需要修改我們的 API 端點,那麼這樣做會更方便,而不用麻煩我們的消費者來更新他們的調用。

# in vcl_recv
if (req.url ~ “^/some-old-endpoint”) {
    # rewrite to the new endpoint
set req.url = regsub(req.url, “/some-old-endpoint”, “/new-and-improved-endpoint”);
}

還可以選擇一定比例的傳入請求來測試實驗特性。利用 Fastly 的隨機性庫,我們可以爲某些請求中添加一個特殊的頭,以實現原始服務器上的新行爲。

# in vcl_recv
set req.http.new_feature = 0
if (randombool(1,10000)) {
    # .01% of the traffic gets to see the new feature
set req.http.new_feature = 1;
}

它結合了 Fastly 的邊緣字典,使得我們可以用最少的代碼建立不同的行爲。

# API keys that will have a percentage of their request use the new feature
table new_feature_access {
    “__API_KEY1__”: “1”,
    “__API_KEY2__”: “5”,
    “__API_KEY3__”: “1000”,
}
sub vcl_recv {
set req.http.new_feature = 0
# check if request has an api key that is setup to have a percentage of its requests use the new feature
if (randombool(std.atoi(table.lookup(new_feature_access, subfield(req.url.qs, "api_key", "&"), "0"))
,10000)) {
set req.http.new_feature = 1;
}
return(lookup);
}

這只是觸及了 VCL 實現的功能的皮毛。如果你想知道還有什麼可以做的,可以在這裏找到 Fastly 的文檔!

https://developer.fastly.com/

2 技巧提示

我們使用 Fastly 的很多特性來爲世界提供 GIF 動畫內容。但是,當你可以使用如此多的特性時,配置邊緣雲平臺可能會變得非常複雜,因此,下面是一些我們推薦的技巧提示可以幫助你完成這個任務。

在邊緣和 Origin Shield 中執行 VCL

對於兩層緩存設置,有一個需要記住的關鍵問題是,將在邊緣和 Origin Shield 執行相同的 VCL 代碼。這可能導致 VCL 代碼在請求 / 響應的狀態信息更改時出現意外的結果。

舉例來說,我們之前的 VCL 代碼將根據由上游緩存控制頭或 VCL 代碼本身指定的緩存 TTL,爲 Origin Shield 和邊緣節點設置緩存 TTL:

# in vcl_fetch
if (beresp.http.Surrogate-Control ~ "max-age" || beresp.http.Cache-Control ~ "(s-maxage|max-age)"
) {
  # upstream set some cache control headers, so Fastly will use its cache TTL
  return(deliver);
} else {
  # no cache headers, so use cache policies for endpoints
  if (req.url ~ "^/v1/gifs/trending") {
   # set 10 minute ttl for trending responses
   set beresp.ttl = 600s;
   return(deliver);
  }
}

假設對於 Trending Endpoint,我們也設置了響應的 Cache-Control 頭,這樣我們就可以指示調用方將內容緩存到另一段的時間。這樣做只需按以下步驟:

# in vcl_fetch
if (beresp.http.Surrogate-Control ~ "max-age" || beresp.http.Cache-Control ~ "(s-maxage|max-age)"
) {
  # upstream set some cache control headers, so Fastly will use its cache TTL
  return(deliver);
} else {
  # no cache headers, so use cache policies for endpoints
  if (req.url ~ "^/v1/gifs/trending") {
   # set 10 minute ttl for trending responses
   set beresp.ttl = 600s;
   # set 30 second ttl for callers
   set beresp.http.cache-control = "max-age=30";
   return(deliver);
  }
}

Origin Shield 會執行這段 VCL 代碼,向響應的頭添加 Cache-Control 頭,並將其返回到邊緣。但是,在邊緣處,它將看到響應中設置了 Cache-Control,並會執行 if 語句。這將導致邊緣節點使用 30 秒的緩存 TTL,而不是預期的 10 分鐘!

幸運的是,Fastly 提供了一種區分邊緣和 Origin Shield 的方法,它在請求中設置了頭(Fastly-FF):

# in vcl_fetch
if (req.url ~ "^/v1/gifs/trending") {
   # set 10 minute ttl for trending responses
   set beresp.ttl = 600s;
   return(deliver);
}
# in vcl_deliver
if (!req.http.Fastly-FF) {
   # set 30 second ttl for callers
   set resp.http.cache-control = "max-age=30";
}

通過這個添加,Cache-Control 頭將僅在邊緣節點上設置,我們的緩存策略再次按預期運行!

調試和測試

我們剛纔提到的陷阱可能難以發現和調試。VCL 代碼只是運行在服務器上,並向你顯示響應和響應頭信息。只需將調試信息添加到自定義頭信息中,並在響應中查看它們,但是這很快就會變得不方便了。

所幸的是,Fastly Fiddle 工具在執行 VCL 代碼時能得到更好的信息。在這個工具中,我們可以模擬各種 VCL 代碼部分,並瞭解 Fastly 的邊緣以及 Origin Shield 服務器將如何處理 VCL 代碼的信息。

以下是上述示例的 fiddle,顯示雙重執行 VCL 將影響緩存 TTL。

我們在左邊的適當部分設置了 VCL,然後執行它,查看 Fastly 將如何處理右邊的請求:

上圖展示了在請求通過 edge 和 Origin Shield 節點時,關於它的生命週期的許多有用信息。實際環境中,VCL 代碼可能會非常複雜,而這個工具在這種情況下非常出色。

作者簡介

Ethan Lu,API 團隊技術領導。

原文鏈接

https://engineering.giphy.com/how-giphy-uses-fastly-to-achieve-global-scale/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/jndjc057iAhJAIBqUo1VDA