摘要
每次資料異動就得清空快取?那還不如別用了。這篇要聊的是怎麼讓快取技術既保住速度感,又能跟得上資料變動——說穿了就是場精心設計的妥協藝術 歸納要點:
- 快取就像在辦公桌放常用文件,但資料更新時可能得面對「舊檔案還在桌上」的尷尬——這篇會拆解如何用分層快取策略平衡即時性與效能,像我們去年幫電商客戶做的混合式Redis+CDN架構,愣是把延遲壓到200毫秒內
- 吞吐量和延遲根本是蹺蹺板兩端:當你學Netflix把影片預載到邊緣節點(他們全球大概有上萬台伺服器吧),就別妄想同時處理海量上傳數據。有次我們監測到某影音平台尖峰時段CDN命中率掉到70%,結果發現是後台分析程式在偷吃頻寬
- 最終一致性其實滿街都是啦!像你早上改FB頭貼,有時要過幾分鐘朋友才看到新照片。文章會用MySQL binlog和Kafka的案例說明,為何購物車結帳頁寧可顯示『庫存確認中』也不該讓用戶等強一致性檢查
在每一個打開就飛快載入的 App、購物網站結帳時沒什麼卡頓,或者即時推播訊息彈出來的瞬間,其實背後都藏著工程師反覆斟酌過的選擇。系統設計這玩意兒,說真的,不是只畫幾個框框加箭頭那麼簡單——更多時候,是在兩個看起來同樣重要的特質之間糾結、掙扎。有些狀況,你很想讓你的服務一直都能用,但就得接受資料偶爾不同步;要追求回應速度像閃電一樣,很可能某些東西處理得沒這麼徹底。這些抉擇並不是紙上談兵,每一步都會直接影響你用到的產品感覺、表現,甚至關鍵體驗。
像 Netflix、Twitter、Amazon、Uber 這類公司,他們內部團隊其實幾乎天天都在做這種取捨。有時候,一個架構上的小決定,後面居然會牽動整體規模可不可以撐大、生意穩不穩得住、甚至速度快不快都有差別。下面有幾種比較常見的系統設計權衡,大概十項左右(好像也有人說更多),每一項到底意義在哪?為什麼會有人選了 A 不選 B?有些真實世界裡頭發生過的方法案例,也許能讓人更懂「為什麼要這樣」,而不是只知道「怎麼做」。無論準備面試還是真的要蓋大型服務,理解背後邏輯,有時比死記流程管用。
先講第一組對立:把系統弄到很能撐大量流量跟資料,那就是所謂「可擴展性」;但如果你想所有人看到的資料隨時都是同一份,哪怕人在不同城市、不同步伺服器,那又是「一致性」。偏偏當你把服務分散到很多機器、遍布各地,要讓數據永遠完全同步其實相當難搞,只要更新動作密集,就更容易出現延遲或小矛盾。所以兩難就來了——想拼命擴展,很可能只能退一步,在一致性上鬆口氣,好讓整體維持暢通;但如果硬是堅持全程強一致,那遇到高壓力或突然湧入人潮的時候,不只慢下來,有時還直接掛掉也說不定。
CAP 理論講的是,你在分散式架構裡面,「一致性」、「可用性」、「分區容忍度」通常只能挑兩項兼顧,很難三者皆美。例如 Twitter 當年快速膨脹期就在頭痛這件事,他們最後妥協選了所謂「最終一致」,等於承認部分數據短時間內可能彼此不太一樣,只為保證效能還夠快。至於其它細節嘛,好像大家講法也都差不多......
像 Netflix、Twitter、Amazon、Uber 這類公司,他們內部團隊其實幾乎天天都在做這種取捨。有時候,一個架構上的小決定,後面居然會牽動整體規模可不可以撐大、生意穩不穩得住、甚至速度快不快都有差別。下面有幾種比較常見的系統設計權衡,大概十項左右(好像也有人說更多),每一項到底意義在哪?為什麼會有人選了 A 不選 B?有些真實世界裡頭發生過的方法案例,也許能讓人更懂「為什麼要這樣」,而不是只知道「怎麼做」。無論準備面試還是真的要蓋大型服務,理解背後邏輯,有時比死記流程管用。
先講第一組對立:把系統弄到很能撐大量流量跟資料,那就是所謂「可擴展性」;但如果你想所有人看到的資料隨時都是同一份,哪怕人在不同城市、不同步伺服器,那又是「一致性」。偏偏當你把服務分散到很多機器、遍布各地,要讓數據永遠完全同步其實相當難搞,只要更新動作密集,就更容易出現延遲或小矛盾。所以兩難就來了——想拼命擴展,很可能只能退一步,在一致性上鬆口氣,好讓整體維持暢通;但如果硬是堅持全程強一致,那遇到高壓力或突然湧入人潮的時候,不只慢下來,有時還直接掛掉也說不定。
CAP 理論講的是,你在分散式架構裡面,「一致性」、「可用性」、「分區容忍度」通常只能挑兩項兼顧,很難三者皆美。例如 Twitter 當年快速膨脹期就在頭痛這件事,他們最後妥協選了所謂「最終一致」,等於承認部分數據短時間內可能彼此不太一樣,只為保證效能還夠快。至於其它細節嘛,好像大家講法也都差不多......
吞吐量和延遲,這兩個在系統設計裡面其實經常扯在一起,不太可能都做到極致。優化一邊,另一邊多半會掉下來——就像天秤那樣子。延遲這玩意,就是用戶從發出請求到拿到結果的那段時間,網頁開起來快不快、資料抓回來要不要等,這些都跟延遲有關。越短越好,用戶心情才不會被磨光。
至於吞吐量,大概可以想成是一秒鐘內系統能夠處理多少個請求。假設同時湧進來的人變多,就很考驗吞吐力道了。如果你只想讓每一筆請求都飛快完成,有時候得犧牲掉同時並行的數量,也許資源分配比較浪費,所以整體能接待的人數反而少了一點。要是你想拉高同時服務的人數,那每個人的等待時間很可能就拉長了,好像排隊點餐那種感覺——櫃台專心幫一個人超快搞定,但外頭排著隊,人龍沒消;反過來一次收幾個單,每位顧客稍微等久些,可是總算大家都能吃上東西。
有趣的是,像Netflix這類公司,他們其實更傾向把重心放在低延遲上面,只因為用戶體感比什麼都重要吧?畢竟大部分人窩在沙發上準備追劇,不太可能接受按下「播放」還要乾等半天才跑影片出來。這家公司弄了將近遍佈全球的CDN,把影片內容塞在離觀眾最近的地方;然後又拆分功能,用微服務架構讓推薦、播映、付費等等各自獨立運作,效率自然提升不少;再加上一些預載或緩衝的小技巧,看起來就是即點即播,毫無拖泥帶水。但話說回來,他們也不是什麼事都一定馬上做,比如那些使用者行為分析資料,也許會等到比較閒再批次處理。說穿了,就是先顧好低延遲讓大家爽看戲,有些背後需要高吞吐量的運算晚點補也無妨。
對於一般消費型應用程式,速度慢一點往往就被嫌棄,好像壞掉似的,所以他們寧可讓表面表現搶眼,其它事情擱一旁也沒啥關係。
然後談到強一致性跟最終一致性的問題。只要資料是跨伺服器甚至跨地區儲存(現在幾乎所有大型系統都是),這條線就模糊起來啦。有說法是強一致性代表你剛改完某筆資料,全世界任何角落查詢都同步看到最新值,一絲不差;但所謂最終一致性嘛,就是允許短暫的不統一,大概過一下子才恢復同步……
至於吞吐量,大概可以想成是一秒鐘內系統能夠處理多少個請求。假設同時湧進來的人變多,就很考驗吞吐力道了。如果你只想讓每一筆請求都飛快完成,有時候得犧牲掉同時並行的數量,也許資源分配比較浪費,所以整體能接待的人數反而少了一點。要是你想拉高同時服務的人數,那每個人的等待時間很可能就拉長了,好像排隊點餐那種感覺——櫃台專心幫一個人超快搞定,但外頭排著隊,人龍沒消;反過來一次收幾個單,每位顧客稍微等久些,可是總算大家都能吃上東西。
有趣的是,像Netflix這類公司,他們其實更傾向把重心放在低延遲上面,只因為用戶體感比什麼都重要吧?畢竟大部分人窩在沙發上準備追劇,不太可能接受按下「播放」還要乾等半天才跑影片出來。這家公司弄了將近遍佈全球的CDN,把影片內容塞在離觀眾最近的地方;然後又拆分功能,用微服務架構讓推薦、播映、付費等等各自獨立運作,效率自然提升不少;再加上一些預載或緩衝的小技巧,看起來就是即點即播,毫無拖泥帶水。但話說回來,他們也不是什麼事都一定馬上做,比如那些使用者行為分析資料,也許會等到比較閒再批次處理。說穿了,就是先顧好低延遲讓大家爽看戲,有些背後需要高吞吐量的運算晚點補也無妨。
對於一般消費型應用程式,速度慢一點往往就被嫌棄,好像壞掉似的,所以他們寧可讓表面表現搶眼,其它事情擱一旁也沒啥關係。
然後談到強一致性跟最終一致性的問題。只要資料是跨伺服器甚至跨地區儲存(現在幾乎所有大型系統都是),這條線就模糊起來啦。有說法是強一致性代表你剛改完某筆資料,全世界任何角落查詢都同步看到最新值,一絲不差;但所謂最終一致性嘛,就是允許短暫的不統一,大概過一下子才恢復同步……
觀點延伸比較:
主題 | 細節 |
---|---|
NoSQL 儲存方式 | 適合未結構化資料,提供彈性和規模,但可能面臨一致性問題。 |
同步 vs 非同步處理 | 同步如排隊等候,非同步則像預訂,需考量通知失誤的風險。 |
狀態管理設計 | 無狀態設計易於擴展,但使用者需承擔更多責任;有狀態則便於個人化但不靈活。 |
批次處理與串流運算 | 批次適合大規模數據分析,串流則需即時反應,各有優缺點。 |
快取策略比較 | 讀取型快取可提高效率但潛在延遲,寫入型快取保證一致性但寫入速度慢。 |

資料啊,最終還是會在所有伺服器上變成一致的,只是這事不太可能立刻發生。說到這個,分散式系統想要做到那種「很強的」一致性,真的得靠大量溝通協調——像什麼確認訊息、鎖定資源或同步複製這些流程,結果就是延遲增加、可用性下降。有時候只因為某台伺服器癱掉了,就整個卡住。
其實,也有人就乾脆選擇「最終一致」這條路,不跟每台機器一一核對,反而讓整體速度快很多,也比較不容易出狀況。不過嘛,中間總會有那麼一小段時間,大家看到的資料內容稍微不一樣。好比說,有人在印度改Google文件,同時德國也有人在動筆。如果追求強一致,大概隨時都能看到同樣的內容;但如果只是要求最後結果能對上,那他們兩邊短暫內看到的頁面可能差了一點點,但之後又會自動補回來。
舉個業界例子好了。Amazon DynamoDB吧,一種NoSQL資料庫,好像蠻多AWS服務也拿它來用。DynamoDB其實就是標準那種偏向「最終一致」設計。假如說,有人把亞馬遜最後一支iPhone買走,「沒庫存了」這件事得通知到各地大約三五處機房。如果堅持要全數據中心同步確認才算完成,那萬一卡某台慢了、壞了?剩下的人都只能等著不能結帳下單。
Amazon設計DynamoDB預設就是先寫入本地再慢慢同步,所以寫入超級快,用戶收到已經成功的提示很快,但背後那些副本未必同時更新完畢。等於說,你有時候打開頁面會看到還有一支iPhone,但其實早被買光了。不過整個系統因此能保持相當高的可用率,又很難被網路故障拖垮。
當然,如果哪個應用真的非得讀到最新版本不可,也是可以特別要求強一致性啦,只是不是預設,每次讀寫都開啟反而容易拖慢大部分情境。而且對電子商務來講嘛,其實多數人更在意網站不要掛掉、速度夠快,而不是每秒鐘都保證資訊完全吻合。偶爾庫存顯示差那麼一下,大致也就接受了——尤其像黑色星期五這種流量暴增日子,誰還管那些細節。
忽然想到另一塊領域,就是應用架構到底要做成單體還是拆成微服務,好像也是每間公司都會糾結的一步……
其實,也有人就乾脆選擇「最終一致」這條路,不跟每台機器一一核對,反而讓整體速度快很多,也比較不容易出狀況。不過嘛,中間總會有那麼一小段時間,大家看到的資料內容稍微不一樣。好比說,有人在印度改Google文件,同時德國也有人在動筆。如果追求強一致,大概隨時都能看到同樣的內容;但如果只是要求最後結果能對上,那他們兩邊短暫內看到的頁面可能差了一點點,但之後又會自動補回來。
舉個業界例子好了。Amazon DynamoDB吧,一種NoSQL資料庫,好像蠻多AWS服務也拿它來用。DynamoDB其實就是標準那種偏向「最終一致」設計。假如說,有人把亞馬遜最後一支iPhone買走,「沒庫存了」這件事得通知到各地大約三五處機房。如果堅持要全數據中心同步確認才算完成,那萬一卡某台慢了、壞了?剩下的人都只能等著不能結帳下單。
Amazon設計DynamoDB預設就是先寫入本地再慢慢同步,所以寫入超級快,用戶收到已經成功的提示很快,但背後那些副本未必同時更新完畢。等於說,你有時候打開頁面會看到還有一支iPhone,但其實早被買光了。不過整個系統因此能保持相當高的可用率,又很難被網路故障拖垮。
當然,如果哪個應用真的非得讀到最新版本不可,也是可以特別要求強一致性啦,只是不是預設,每次讀寫都開啟反而容易拖慢大部分情境。而且對電子商務來講嘛,其實多數人更在意網站不要掛掉、速度夠快,而不是每秒鐘都保證資訊完全吻合。偶爾庫存顯示差那麼一下,大致也就接受了——尤其像黑色星期五這種流量暴增日子,誰還管那些細節。
忽然想到另一塊領域,就是應用架構到底要做成單體還是拆成微服務,好像也是每間公司都會糾結的一步……
有時候大家會把所有功能塞進一個大程式,那種做法叫「單體架構」。像是登入、付款、搜尋、通知,反正全部都黏在一起。剛開始的時候這樣子寫起來很方便,部署也沒啥麻煩事。不過等到東西越堆越多,要加新功能或修個小毛病,常常就跟在解一團亂線差不多。
後來流行起來的微服務,大概就是把原本那種大雜燴拆開,每個小服務只顧自己一塊業務。舉例來說,有人專門管地圖,有人弄付款系統,各做各的。服務之間靠API講話,好處是獨立發展、分開更新,也能根據需求彈性調整規模。但是這麼做帶來的複雜度也挺驚人:你得擔心每個服務怎麼溝通、資料要怎樣保持一致,還有監控、部署流程,每一小部分都得照顧到。
如果硬要比喻,大概可以說單體像是一人樂隊,什麼都自己包辦,簡單直接但想升級就卡關;微服務更像幾十甚至上百人的樂團,大家各司其職,不過如果沒有超厲害的指揮,那協調起來真的頭痛。
舉個產業故事好了——Uber早期也是用單體寫出平台。當時好像只有幾位工程師,而且只跑一座城市吧?那時候這樣搞沒什麼問題。但等他們快速擴展到全球,又開始東加西加新功能(譬如司機追蹤、車資計算、自動派車那些),那套老架構就變成負擔了。有些工程師甚至會同時改到同一份程式碼,一不小心誰出錯可能整個APP都掛掉。而且每次想針對某個區域比如說調整高峰價格,都不得不動到整體系統,很麻煩。
所以最後Uber還是下定決心,把原本那坨東西切碎成數千(大致上啦)種獨立小服務,比如地圖、支付、行程管理等等都有自己的團隊維護。這下子各組可以同步作業,各自上線更新,也能針對不同國家或區域彈性擴充。但這背後付出的代價非常可觀,他們得花大量資源搞工具和監控系統,例如設計出能自我修復的小機制、防止連鎖失敗等等。有段時間光是查bug就夠讓人暈頭轉向,但對於像Uber這種希望爆炸性成長的公司而言,再困難也只能咬牙撐下去。總結一下,就是換來更多自由,但要承受不少混亂與挑戰——看你需不需要罷了。
後來流行起來的微服務,大概就是把原本那種大雜燴拆開,每個小服務只顧自己一塊業務。舉例來說,有人專門管地圖,有人弄付款系統,各做各的。服務之間靠API講話,好處是獨立發展、分開更新,也能根據需求彈性調整規模。但是這麼做帶來的複雜度也挺驚人:你得擔心每個服務怎麼溝通、資料要怎樣保持一致,還有監控、部署流程,每一小部分都得照顧到。
如果硬要比喻,大概可以說單體像是一人樂隊,什麼都自己包辦,簡單直接但想升級就卡關;微服務更像幾十甚至上百人的樂團,大家各司其職,不過如果沒有超厲害的指揮,那協調起來真的頭痛。
舉個產業故事好了——Uber早期也是用單體寫出平台。當時好像只有幾位工程師,而且只跑一座城市吧?那時候這樣搞沒什麼問題。但等他們快速擴展到全球,又開始東加西加新功能(譬如司機追蹤、車資計算、自動派車那些),那套老架構就變成負擔了。有些工程師甚至會同時改到同一份程式碼,一不小心誰出錯可能整個APP都掛掉。而且每次想針對某個區域比如說調整高峰價格,都不得不動到整體系統,很麻煩。
所以最後Uber還是下定決心,把原本那坨東西切碎成數千(大致上啦)種獨立小服務,比如地圖、支付、行程管理等等都有自己的團隊維護。這下子各組可以同步作業,各自上線更新,也能針對不同國家或區域彈性擴充。但這背後付出的代價非常可觀,他們得花大量資源搞工具和監控系統,例如設計出能自我修復的小機制、防止連鎖失敗等等。有段時間光是查bug就夠讓人暈頭轉向,但對於像Uber這種希望爆炸性成長的公司而言,再困難也只能咬牙撐下去。總結一下,就是換來更多自由,但要承受不少混亂與挑戰——看你需不需要罷了。

他們當時拋棄了簡單,換來的是靈活跟擴展——這好像也挺適合那種增長速度快到讓人反應不及的情境。資料庫嘛,常常會被拿來比較。像是SQL(有人說關聯式資料庫啦),比如PostgreSQL、MySQL這幾個名字很常聽見。它們其實就是把資料塞進一格格表格裡,有明確規則那種,什麼欄位都規定得清楚,而且ACID特性——對交易安全、穩定性、那些嚴謹的東西非常講究,如果你做金融或是倉儲管理,大概少不了這類。
然後NoSQL又是不太一樣的風景,比如MongoDB或者Cassandra,也有DynamoDB,好像還有更多沒數過。它們根本就不在意什麼固定格式,有點像是把所有限制都拆掉,能水平分散出去加速讀寫,還可以處理那些亂七八糟的資料格式——JSON、鍵值對還有圖形結構什麼的。有時候你會懷疑,到底哪種比較實用?大致上啦,如果想要數據一致性和複雜查詢(像多表連接或設限),選SQL準沒錯。不過如果規模一直放大,那些舊式架構可能就卡住了,不太容易分攤到很多台機器上。而NoSQL則好像更隨心所欲,一下子要改結構也不用怎麼擔心,但換來的,就是一致性跟跨多筆交易同步容易出狀況,在分散式系統裡尤其明顯。
可以打個比方。如果說SQL像圖書館,每本書都排好,標籤齊全,要找很方便,可是突然要整間搬動位置就麻煩死了;NoSQL呢,就像白板,寫啥擦啥隨便你,但等到真的要分類找東西,就不是一件輕鬆事。
臉書Messenger,大概不少人用過吧?說起這例子最妙。他們早年其實也是用傳統關聯式資料庫存訊息,用的人越來越多,問題也冒出頭:訊息得存好多好多份,要即時送出去,又不能因為設備不同而鎖住某個帳號。後來,他們乾脆跑去採用Apache HBase(一種參考Google Bigtable設計出來的NoSQL系統)。這玩意兒是屬於欄位型、分布式那掛……嗯,其實細節我記不太清楚,大抵如此吧。
然後NoSQL又是不太一樣的風景,比如MongoDB或者Cassandra,也有DynamoDB,好像還有更多沒數過。它們根本就不在意什麼固定格式,有點像是把所有限制都拆掉,能水平分散出去加速讀寫,還可以處理那些亂七八糟的資料格式——JSON、鍵值對還有圖形結構什麼的。有時候你會懷疑,到底哪種比較實用?大致上啦,如果想要數據一致性和複雜查詢(像多表連接或設限),選SQL準沒錯。不過如果規模一直放大,那些舊式架構可能就卡住了,不太容易分攤到很多台機器上。而NoSQL則好像更隨心所欲,一下子要改結構也不用怎麼擔心,但換來的,就是一致性跟跨多筆交易同步容易出狀況,在分散式系統裡尤其明顯。
可以打個比方。如果說SQL像圖書館,每本書都排好,標籤齊全,要找很方便,可是突然要整間搬動位置就麻煩死了;NoSQL呢,就像白板,寫啥擦啥隨便你,但等到真的要分類找東西,就不是一件輕鬆事。
臉書Messenger,大概不少人用過吧?說起這例子最妙。他們早年其實也是用傳統關聯式資料庫存訊息,用的人越來越多,問題也冒出頭:訊息得存好多好多份,要即時送出去,又不能因為設備不同而鎖住某個帳號。後來,他們乾脆跑去採用Apache HBase(一種參考Google Bigtable設計出來的NoSQL系統)。這玩意兒是屬於欄位型、分布式那掛……嗯,其實細節我記不太清楚,大抵如此吧。
橫向擴展到成千上萬台伺服器這種事,現在不少大型平台都在做。像那種比較新型的儲存方式——NoSQL,聽說它很適合放未結構化資料,例如聊天紀錄、一些奇怪的標籤或是媒體檔案這些。也有種講法,大公司為什麼願意犧牲傳統SQL那種穩定安全性?差不多就是為了追求彈性和規模。不然哪可能做到讓你人在世界各地,訊息依舊可以瞬間讀取寫入?
可是嘛,這樣做難免有些麻煩。例如一致性問題就得自己處理,有時候訊息同步會延遲一兩秒才出現在另一台裝置上,不過對聊天來說,好像還算能接受。畢竟誰會計較那點小延遲?總比每次都要搬動整個資料表好太多了。
換個話題。有時候設計系統時會遇到「同步」跟「非同步」到底選哪個的問題。有些人形容,同步流程就像去咖啡店排隊,一步一步等著拿杯子才走人;非同步呢,比較像用手機下單,你隨便晃晃等通知響起再回去拿。不過講真的,也不是所有情境都適合非同步——如果漏掉通知、或者咖啡被別人拿走怎辦?
LinkedIn以前處理動態消息,好像最早都是採用同步更新。每當有人發文或按讚,他們就直接幫你全網好友即時刷新一次訊息流。在使用者還沒破百萬的年代,也許勉強撐得住。可後來成長到數以億計的人同時在線,每多一點互動後端壓力就大得不像話,有段時間應該常常卡頓甚至推播失效吧。
於是他們改變策略,開始把這些操作丟進Kafka之類的大型事件佇列裡頭。也就是說,用戶發文或按個愛心,其實背後只是先被記錄下來,而不是立刻通知大家。這方法雖然加快了整體效率,但開發團隊得花更多心思處理佇列管理、錯誤重送以及那些偶爾失敗的小狀況。有沒有完全解決所有問題?恐怕很難說,只能不斷調整吧。
可是嘛,這樣做難免有些麻煩。例如一致性問題就得自己處理,有時候訊息同步會延遲一兩秒才出現在另一台裝置上,不過對聊天來說,好像還算能接受。畢竟誰會計較那點小延遲?總比每次都要搬動整個資料表好太多了。
換個話題。有時候設計系統時會遇到「同步」跟「非同步」到底選哪個的問題。有些人形容,同步流程就像去咖啡店排隊,一步一步等著拿杯子才走人;非同步呢,比較像用手機下單,你隨便晃晃等通知響起再回去拿。不過講真的,也不是所有情境都適合非同步——如果漏掉通知、或者咖啡被別人拿走怎辦?
LinkedIn以前處理動態消息,好像最早都是採用同步更新。每當有人發文或按讚,他們就直接幫你全網好友即時刷新一次訊息流。在使用者還沒破百萬的年代,也許勉強撐得住。可後來成長到數以億計的人同時在線,每多一點互動後端壓力就大得不像話,有段時間應該常常卡頓甚至推播失效吧。
於是他們改變策略,開始把這些操作丟進Kafka之類的大型事件佇列裡頭。也就是說,用戶發文或按個愛心,其實背後只是先被記錄下來,而不是立刻通知大家。這方法雖然加快了整體效率,但開發團隊得花更多心思處理佇列管理、錯誤重送以及那些偶爾失敗的小狀況。有沒有完全解決所有問題?恐怕很難說,只能不斷調整吧。

有時候,背景處理流程會在使用者動作剛被確認後才慢慢啟動。像 LinkedIn 這種平台,他們讓後台的工作人員(其實就是一堆程式)去接手同步訊息、通知朋友、還有分析數據什麼的——這些繁瑣事通通丟到後面跑。前端就能立刻跳出「貼文已建立!」之類的提示,看起來整個很俐落,速度感滿分。大工程都丟給機器在背後忙,所以用戶流暢度高,就算人潮突然暴增也不至於卡住。不過嘛,這樣設計反而得多照顧一些麻煩事,例如重試機制、死信佇列還有事件順序亂掉的問題。不過聽說他們認為,這種複雜性是值得的——畢竟換來超高吞吐量跟可用性,大塞車時體驗也沒崩壞。
說到系統架構,狀態該不該保留?其實兩邊各有利弊。有些系統會把使用者狀態記在伺服器那頭,比如登入資料、購物車內容或遊戲進度等等。大家都覺得這樣比較貼心——服務端好像真的「認識」你,每次回來都還記得上回聊了什麼。不過,一旦想要擴展規模,就會發現事情變複雜了:如果每個人都黏著某台伺服器,那負載分配就不太靈活;萬一想把狀態共享給所有伺服器,又要另找地方存放資料,工程量忽然膨脹許多。
另一派則主張無狀態設計。他們覺得每次請求都當作全新事件,不去記前因後果。一點像販賣機,每按一次鈕投一次錢,它完全不管你之前幹過啥。這種做法帶來一個明顯好處,就是橫向擴充簡單極了——哪台伺服器閒著,就拿去處理請求,不必考慮誰跟誰綁定。但相對地,用戶端(比方說瀏覽器或手機 App)肩上的責任變重,要自己攜帶憑證或紀錄狀態,多了一份麻煩。
AWS 就玩出了名堂。他們 Lambda 函數本來就是無狀態設計,每執行一次都是全新的一輪,也沒有任何歷史資料殘留。所以 AWS 能隨時叫出數以千計甚至更多的 Lambda 實例,只要需求飆升就馬上加人力,不用擔心使用者會連不上原本那台主機。基礎建設維護壓力自然小很多,也不用特地同步那些細碎的 session 資料。不過話又說回來,要是開發者真需要保存什麼登入資訊或其他東西,那還是得靠外部儲存方案,例如 DynamoDB 或 ElastiCache 這類服務。
總結下來,有些公司寧願讓開發團隊多費點神,把資料搬到外頭管理,但換到的是應付突發流量、高速伸縮能力——大約就是這種取捨吧,其實也不是只有一家大企業玩這套。
說到系統架構,狀態該不該保留?其實兩邊各有利弊。有些系統會把使用者狀態記在伺服器那頭,比如登入資料、購物車內容或遊戲進度等等。大家都覺得這樣比較貼心——服務端好像真的「認識」你,每次回來都還記得上回聊了什麼。不過,一旦想要擴展規模,就會發現事情變複雜了:如果每個人都黏著某台伺服器,那負載分配就不太靈活;萬一想把狀態共享給所有伺服器,又要另找地方存放資料,工程量忽然膨脹許多。
另一派則主張無狀態設計。他們覺得每次請求都當作全新事件,不去記前因後果。一點像販賣機,每按一次鈕投一次錢,它完全不管你之前幹過啥。這種做法帶來一個明顯好處,就是橫向擴充簡單極了——哪台伺服器閒著,就拿去處理請求,不必考慮誰跟誰綁定。但相對地,用戶端(比方說瀏覽器或手機 App)肩上的責任變重,要自己攜帶憑證或紀錄狀態,多了一份麻煩。
AWS 就玩出了名堂。他們 Lambda 函數本來就是無狀態設計,每執行一次都是全新的一輪,也沒有任何歷史資料殘留。所以 AWS 能隨時叫出數以千計甚至更多的 Lambda 實例,只要需求飆升就馬上加人力,不用擔心使用者會連不上原本那台主機。基礎建設維護壓力自然小很多,也不用特地同步那些細碎的 session 資料。不過話又說回來,要是開發者真需要保存什麼登入資訊或其他東西,那還是得靠外部儲存方案,例如 DynamoDB 或 ElastiCache 這類服務。
總結下來,有些公司寧願讓開發團隊多費點神,把資料搬到外頭管理,但換到的是應付突發流量、高速伸縮能力——大約就是這種取捨吧,其實也不是只有一家大企業玩這套。
有時候像節慶或大促一到,網站流量突然暴漲,這種情況其實不算罕見。亞馬遜他們在用戶會話管理上,好像就選擇犧牲了一點單純性,但換來的好處是彈性、可伸縮,還有那種幾乎讓人不用太擔心伺服器綁定的高可用系統架構,結果就是每秒數以百萬計的請求也能平順撐住,不會一下就癱掉。
說起來,批次處理和串流運算這兩個詞常被搞混,其實差異蠻明顯。批次嘛,就是你累積一堆資料等一段時間再一起整理,有點像麵包店一天只烤一次,一大早賣新鮮麵包。它效率挺高,大規模數據比較適合這樣慢慢來,而且資源分配上可以抓得剛好,但即時性肯定沒辦法。串流就完全不同了,那種邊進資料邊分析,例如監控現場感測器訊號或即時偵測詐欺,就像三餐熱騰騰現做三明治,一整天隨時都有人下單馬上出菜。不過串流對硬體要求嚴苛多了,設計也複雜不少,你要確保反應夠快、錯誤又不能太多。
Netflix這家公司,他們應該兩種都有用吧?畢竟會員行為、裝置資訊、內容標籤……那些資料量說不清到底有多大。有些事,比方推薦影片或做內部商業報告,他們偏向批次方式,用類似Spark那類工具在很大的伺服器叢集上輪班跑分析。有些需求,比如偵測播放問題或者首頁想針對每位觀眾即時個人化調整,就得靠Kafka、Flink這類串流技術才能及時反應。
簡單講:Netflix那邊既享受了批次作業帶來的成本效益與規模優勢,也同時抓住了串流服務低延遲、高即時性的好處。兩者交互運作,看似平衡卻其實很難拿捏。
然後緩存策略又有另一套思考。讀取型緩存,大致意思是程式想查資料時先問快取,有東西就直接給,沒有才往後端找,再把找到的塞回快取留給下一次用。細節…其實還有其他變化,但原則大概如此。
說起來,批次處理和串流運算這兩個詞常被搞混,其實差異蠻明顯。批次嘛,就是你累積一堆資料等一段時間再一起整理,有點像麵包店一天只烤一次,一大早賣新鮮麵包。它效率挺高,大規模數據比較適合這樣慢慢來,而且資源分配上可以抓得剛好,但即時性肯定沒辦法。串流就完全不同了,那種邊進資料邊分析,例如監控現場感測器訊號或即時偵測詐欺,就像三餐熱騰騰現做三明治,一整天隨時都有人下單馬上出菜。不過串流對硬體要求嚴苛多了,設計也複雜不少,你要確保反應夠快、錯誤又不能太多。
Netflix這家公司,他們應該兩種都有用吧?畢竟會員行為、裝置資訊、內容標籤……那些資料量說不清到底有多大。有些事,比方推薦影片或做內部商業報告,他們偏向批次方式,用類似Spark那類工具在很大的伺服器叢集上輪班跑分析。有些需求,比如偵測播放問題或者首頁想針對每位觀眾即時個人化調整,就得靠Kafka、Flink這類串流技術才能及時反應。
簡單講:Netflix那邊既享受了批次作業帶來的成本效益與規模優勢,也同時抓住了串流服務低延遲、高即時性的好處。兩者交互運作,看似平衡卻其實很難拿捏。
然後緩存策略又有另一套思考。讀取型緩存,大致意思是程式想查資料時先問快取,有東西就直接給,沒有才往後端找,再把找到的塞回快取留給下一次用。細節…其實還有其他變化,但原則大概如此。

通常說到 cache,有時候系統會選擇那種隨叫隨更新的做法,不過這樣一來,第一次遇到沒資料的狀況,讀取就會多個步驟、慢上一點點。反觀 write-through 這種寫入時同時處理資料庫和快取的做法,每當應用程式有新資料產生,像是你發文、按讚或留言,後端其實是兩邊一起動作——cache 跟資料庫同時寫進去。結果怎樣?雖然花在寫入那邊的時間稍微拉長,但兩者內容保持一致,比較不會出現舊資料亂跳。反倒是讀取那一刻不用等太久,因為該有的都已經備好。
其實每種方法都有自己的麻煩。read-through 有點像打電話訂披薩,但你要的配料老闆可能臨時要衝出去買,所以等比較久;而 write-through 就像預先把不同口味都準備好放著,客人來了就直接拿,不過廚房會很忙一直補貨。一個快讀、一個保證一致性但寫入慢些,各有優缺。
說到行業範例,有聽過 Twitter 處理時間軸超講究即時跟正確,他們想讓幾千萬使用者看到永遠都是最新貼文(或者至少很接近),於是就在 timeline 這部分用了 write-through cache,大致意思就是每次有人推文、互動,那些資訊同步丟給 Redis 或 Memcached 之類的快取系統還有主資料庫。換句話說,下次別人滑 timeline,就直接從 cache 抓馬上最新的東西,不太需要等背景程序刷新才看得到。代價嘛——寫入速度變慢了一點,可對社群平台來說,新鮮度比什麼都重要,用戶只要覺得 timeline 沒 delay 就會繼續黏著看下去。
至於 REST 跟 GraphQL 的討論...唔,REST 類型 API 通常分很多 endpoint,比如 /users、/posts 等,每個路徑回傳固定內容。不難懂,也算穩定,好存快取。但問題也明顯啦:常常撈到一大堆不需要的東西(像只想抓用戶名稱卻拉了一整包個人檔案),或是東缺西缺得跑好多趟 request 才湊齊所有細節。有的人覺得這算小麻煩,但碰多了自然感受特別深刻...
其實每種方法都有自己的麻煩。read-through 有點像打電話訂披薩,但你要的配料老闆可能臨時要衝出去買,所以等比較久;而 write-through 就像預先把不同口味都準備好放著,客人來了就直接拿,不過廚房會很忙一直補貨。一個快讀、一個保證一致性但寫入慢些,各有優缺。
說到行業範例,有聽過 Twitter 處理時間軸超講究即時跟正確,他們想讓幾千萬使用者看到永遠都是最新貼文(或者至少很接近),於是就在 timeline 這部分用了 write-through cache,大致意思就是每次有人推文、互動,那些資訊同步丟給 Redis 或 Memcached 之類的快取系統還有主資料庫。換句話說,下次別人滑 timeline,就直接從 cache 抓馬上最新的東西,不太需要等背景程序刷新才看得到。代價嘛——寫入速度變慢了一點,可對社群平台來說,新鮮度比什麼都重要,用戶只要覺得 timeline 沒 delay 就會繼續黏著看下去。
至於 REST 跟 GraphQL 的討論...唔,REST 類型 API 通常分很多 endpoint,比如 /users、/posts 等,每個路徑回傳固定內容。不難懂,也算穩定,好存快取。但問題也明顯啦:常常撈到一大堆不需要的東西(像只想抓用戶名稱卻拉了一整包個人檔案),或是東缺西缺得跑好多趟 request 才湊齊所有細節。有的人覺得這算小麻煩,但碰多了自然感受特別深刻...
GraphQL 這種東西,其實有點像你去某些餐廳,點菜不是只靠套餐,而是可以自己挑配料那種。它讓使用的人能一次就拿到想要的那些資訊──不多也不少,不會像傳統 REST 那樣,常常一份資料夾帶了很多沒用的東西,或者還要拆成好幾次請求才能湊齊。但這樣的彈性,也把後端搞得更頭大:解決資料查詢背後那層邏輯變複雜很多,有時甚至會卡在怎麼做快取,或者遇到有人發出超級重或特別深的查詢,整個服務瞬間慢下來。怎麼說呢,你可以想像 REST 就像老式便當店——選套餐雖然方便,但裡面幾乎一定有你吃不完的小菜;而 GraphQL 比較偏向那種你隨口喊要換什麼配料、廚師還得隨機應變的地方,只是這樣廚房壓力明顯大上不少。
其實業界有家公司——GitHub,大概也是將近十年前吧,他們最早本來給開發者用的是比較固定套路的 REST API。可是慢慢地,好像越來越多程式人覺得每次要抓某些關聯資訊都得發好多請求、資料包又肥到讓人頭痛(其實我印象中有過很誇張的一兩筆),結果網路延遲跟頻寬都被拖累。後來 GitHub 索性公開了一套 GraphQL API,開發者終於能直接針對需要的 repo、issue 或 pull request 自由組合,一次搞定。不過這背後故事就沒那麼輕鬆了,他們自己好像花了很長時間優化執行效能和安全管控(授權之類的),為了防止有人亂玩高成本查詢也下了很多功夫。換句話說,外部的人獲得更多主動權與效率,但 GitHub 內部反而必須投入相當可觀資源把系統撐起來,也不能排除偶爾還是會踩雷。
如果硬要總結,我倒覺得設計一個系統,其實很少有全贏全輸。有時候你看其他公司走過哪些奇怪彎路,大致猜得到:每個決策背後都是一堆妥協和拉鋸戰。沒有什麼萬無一失的方法,只能試著從前人的選擇學點經驗,下次輪到自己設計時,也許心裡比較踏實……但搞不好還是會遇到一些意料之外的小插曲吧。
其實業界有家公司——GitHub,大概也是將近十年前吧,他們最早本來給開發者用的是比較固定套路的 REST API。可是慢慢地,好像越來越多程式人覺得每次要抓某些關聯資訊都得發好多請求、資料包又肥到讓人頭痛(其實我印象中有過很誇張的一兩筆),結果網路延遲跟頻寬都被拖累。後來 GitHub 索性公開了一套 GraphQL API,開發者終於能直接針對需要的 repo、issue 或 pull request 自由組合,一次搞定。不過這背後故事就沒那麼輕鬆了,他們自己好像花了很長時間優化執行效能和安全管控(授權之類的),為了防止有人亂玩高成本查詢也下了很多功夫。換句話說,外部的人獲得更多主動權與效率,但 GitHub 內部反而必須投入相當可觀資源把系統撐起來,也不能排除偶爾還是會踩雷。
如果硬要總結,我倒覺得設計一個系統,其實很少有全贏全輸。有時候你看其他公司走過哪些奇怪彎路,大致猜得到:每個決策背後都是一堆妥協和拉鋸戰。沒有什麼萬無一失的方法,只能試著從前人的選擇學點經驗,下次輪到自己設計時,也許心裡比較踏實……但搞不好還是會遇到一些意料之外的小插曲吧。
參考來源
將資料填入快取的三種模式
這個作法當然可以解決即時性的問題,不過在快取資料的更新動作上,就會變得複雜許多。 因為,客戶端程式在設計資料的更新操作時,必須考慮到每個更新動入,究竟 ...
來源: iThomeCache Policy — 效能與一致性之間的抉擇. 快取…
為了最大化快取的效益值,我們必須依照不同情況採取不同的快取策略,這是非常重要的關鍵,如果選錯了快取策略,連帶的可能造成應用效能比未使用快取時還要糟糕 ...
相關討論