路漫漫其修遠兮,吾將上下而求索

0%

TGONext - 高併發高流量

前陣子參與到 TGONext 技術架構組的第一次聚會,這次聚會基於高併發而延伸出了許多討論,但因時間的關係,有許多主題並未深入地聊到細節,希望藉由這篇文章能引起大家來思考架構的本質。

為什麼盡量不要使用 Cache Cache 的缺點與選擇


備註:

感謝社群朋友們的討論,原標題的語境轉折表達過為偏頗,Cache 的優點為大家所知,但缺點卻過少討論。並非不使用 Cache,而是我們在決定使用 Cache 前也應當了解 Cache 如何維運。


在使用 Cache 前是否有考慮到以下兩點:

  1. Cache 的缺點
  2. Cache Service 的選擇

Cache 的缺點

Cache 在開發上需要注意兩件事:雪崩效應(Cache Avalanche) 以及 快取穿透(Cache Penetration)

雪崩效應(Cache Avalanche)

何謂雪崩效應:

雪崩效應是指當輸入發生最微小的改變時,也會導致輸出的不可區分性改變。

當 Cache 的服務重啟後,會導致暫存資料皆消失,若有其他服務相依該 Cache Service 也會一併失效。與此同時,相依於該失效 Service 的其他 Service 也會連帶的失效下去。

因此當研發或者維運團隊能力未達一定程度前盡量別使用 Cache

若非必要使用 Cache 不可,請務必設計好監控等機制,並確保 Cache Service 與其他 Service 的相依性。

快取穿透(Cache Penetration)

當用戶不停請求 Cache 內不存在的資料時,請求會轉往資料庫做查詢,但資料庫又不存在此資料時便會對系統造成很大的負擔。

解法也相當單純,請求了不存在的資料後,將異常資料存至 Cache Service ,並設定較短的 TTL 時間即可。

Cache Service 的選擇

常見的 Cache Service 不外乎:Memory Cache 與 Redis,但兩者有決定性的差異。

與 Memory Cache 相比,Redis 在服務重啟後會從 memory 載入資料,這點恰巧解決了雪崩效應的問題。

架構的演進

經典的架構演進順序如下:

  1. Web Server 大平台:httpd 及 database 全安裝至同一台主機
                graph LR
                subgraph Server
    HTTPd-->Database
    end
              
  2. 降低 DB 耦合:將 DB 抽離至單一主機上或者獨立成一個服務
                graph LR
                subgraph Server1
    httpd
    end
    subgraph Server2
    database
    end
    httpd --> database
              
  3. 降低 DB 存取:於 httpd 與 DB 之間建立 Cache 的服務
                graph LR
                subgraph Server1
    httpd
    end
    subgraph Server2
    cache
    end
    subgraph Server3
    database
    end
    httpd --> cache
    cache --> database
              

但是當服務架構發展至第三階段後,需特別注意服務的啟動順序。先啟動 httpd 或者先啟動 cache 或者先啟動 DB,對系統是否會有不同的影響。

舉例來說:若啟動順序為 httpd -> DB -> Cache
則當請求進來後可能因為 Cache Service 尚未準備好,導致 DB 被流量沖垮。

而啟動相依性最複雜的情況是發展到 Microservices 上

何謂相依性:

相依性意即為 retry 、circuit breaker,不會因持續重啟服務而耗盡資源

相比 Microservices 動輒上百個 component 或者 service 的情況下,Monolithic 的確是單純很多了。也因此在 Microservices 的時代上才會特別強調 去中心化 這個名詞。

何謂去中心化:

意即 Component 不管彼此相依性,隨便重啟即可

若使用 Microservices 而未思考到服務間相依關係的話,在重啟後往往是很大的災難,因此可以考慮使用 Service Mesh + Sidecar 來建置一個較完整的 Microservices 架構。

Microservices 的發展本質

有些人認為 Microservices 是架構發展到一定複雜程度後的最終之路,其實不全然。

Microservices 原先想解決的是跨組織間的隔閡與溝通,也因此伴隨著 Microservices 會特別提到 DDD(Domain Driven Design),在各別組織內關注想解決的 Domain 即可。

並非所有的架構問題皆可以使用 Microservices 做通解,High Concurrency 便是一個例子,原因是用戶從發出請求到收到回傳後,中間會在 Microservices 內通過多個 Component,而每經過一個 Component 便會增加 Latency,因此在決定架構前,請務必認清問題本質,是否想解決的問題為 High Concurrency 或者 High Throughput。另一個有趣的特質是,Microservices 僅確保 Eventual consistency,在處理資料的時候請務必小心。

分散式的 Log 管理

分散式上可考慮使用視覺化的 Log 管理工具,透過視覺化工具可以知道用戶請求分別經過哪些 component,且中間過程是否有問題。

綜合 架構的演進 中所提到的,Log 的設計也能基於 sidecar 的模式去設計。

The Bound Categories of Computer Science

在設計與規劃架構的時候,技術邊界對系統的擴充性是一個很大的閥值。
簡而言之技術邊界的意思便是:什麼是技術團隊無法掌握的,例如 SaaS,以及 Third Party API。

一昧擴充機器數量無法解決根本問題,在規劃架構時期,明確定義出技術邊界,在邊界範圍內的問題才是得以處理的問題。

MySQL 近代發展

普遍性的網站服務在資料庫讀取的需求遠大於寫入的需求,亦即為是否資料庫每次更版的時候可以讀的越快越好。

但 MySQL 卻反其道而行,從 5.6 開始,在讀取方面是:

5.6 > 5.7 > 8+

而在寫入的部分則相反:

5.6 < 5.7 < 8+

但這樣的設計發展極其合理,資料庫在做讀寫分離的時候,讀分離遠比寫分離更容易擴充,也因此 MySQL 會這樣設計了。

如何選擇資料庫

這是個有趣的問題,在開始討論之前導師請大家想想:

技術團隊怎麼決定使用這個資料庫的

RDBMS 與 NoSQL 各有千秋,一邊是 Strong Consistency,另一邊則是 Eventual Consistency,自然可以依據使用場僅挑出合適的資料庫,沒有最佳的資料庫,僅有最符合團隊使用場景的資料庫。

我額外提出一個問題,若依據 Consistency 的使用場景自然很容易決定資料庫,但同樣是 Strong Consistency 的情況下,該如何挑選資料庫,例如:MySQL、PostgreSQL、MariaDB 這些該如何挑選?

首先從 MySQL 看起,自從 5.6 以後,MySQL 與 MariaDB 便分家了,也就是說 5.7 以後 MySQL 所新增的功能是 MariaDB 不提供的,而 MariaDB 在 10 以後所新增的功能也是 MySQL 沒有的。更不用提 MySQL 在 8 之後的變化之大,有許多功能是非常不錯的,例如 JSON 的支援。

而 MySQL 則分成社群版(Community)與企業版(Enterprise), Enterprise 的功能也相當強大,例如 Thread Pool。若是想使用企業版但是有 Open Source 的 MySQL 資料庫,則可以考慮 Percona。

Percona 實踐了 MySQL 企業版的功能並開源之,其中包括了 Thread Pool 以及 Galera Cluster 的實踐,若想使用 MySQL 作為開發團隊資料庫使用時,Percona 是個不錯的選擇。

結論

這場活動雖然討論了許多問題,但導師一直強調須認清問題的本質到底為何。很多架構或者技術我們用的很順手,但卻常常忘記思考是哪些 Context 造就我們選擇這樣的架構或者技術,永遠不會有最佳的架構,僅有最合適的架構。