本文內容源自「聲網開發者創業講堂 Vol.02」的演講分享,分享講師為 Thoughtworks 專家級諮詢師劉勇智。大家可以點選此連結,觀看視訊回放以及下載講師 PPT。
從去年年底到現在,隨著疫情的反覆,很多城市的一碼通系統出現了故障,這證明一碼通系統在技術上還存在一些不足,所以本次分享將介紹如何利用 PAST 問題解決框架,從架構和設計方面研究和解決這些問題。
01 PAST 問題解決框架
PAST 的第一個單詞 P 是 Problem,代表的是問題。當遇到問題的時候,不要急於進入方案階段,應該先進行調研和分析,確認問題到底是什麼。這也是 Eric Evans 的《領域驅動設計》中提到的,理解目標領域並將所學到的知識融入到軟體中是領域驅動設計(DDD)的首要任務,這強調了問題的重要性。
第二個單詞 A 是 Analysis,代表的是分析矛盾。導致問題產生的原因可能有多種,在這個過程中要先找出主要矛盾和次要矛盾,然後針對這些原因或者矛盾找到對應的 Solution,也就是 S。此時,可以先列舉方案,然後在 Tradeoff 階段進行權衡和取捨。在進行軟體或者架構設計的實踐中,大多數時候都在做權衡,而不是決策,所以需要對設計進行取捨,最終制定出方案。根據不同的方案,可能要順勢而為,在最後階段進行成效的 review。
02 Problem 問題
下圖為某一城市的一碼通系統。假設產生地為西虹市,圖 1(左)是正常情況下的一碼通系統,包含姓名、證件號和二維碼,其中二維碼分為綠碼、紅碼或者黃碼。另外,圖 1(左)的下方還包含疫苗接種資訊以及 15 天內的核酸檢測結果。由圖可知,一碼通的主入口整合了眾多的功能。圖 1(右) 為週一早晨一碼通系統的故障顯示頁面,顯示一碼通系統空白,此外,還出現了核酸檢測結果不顯示任何資訊等問題。
■圖 1
03 Analysis 分析
3.1 主要矛盾
在分析階段需要針對這些問題嘗試進行調查和分析,找到問題的原因和矛盾。此時主要是大量市民需要在週一早上從不同場合開啟一碼通系統的核酸證明頁面,與一碼通系統不能同時滿足大併發量之間的矛盾。一碼通系統無法開啟,導致使用者反覆重新整理,系統使用者請求飆增。此時,請求可能直接到達後臺甚至服務層,進而進入分散式 cache 或者資料庫,這造成後臺伺服器的流量突增,對應的網路頻寬增加。
3.2 架構分析
接下來進一步分析架構和設計,從資料層面來說,可能沒有形成很好的快取機制,很多查詢請求都直接進入伺服器甚⾄資料庫,造成了快取的擊穿,很多流量被“懟”到了資料庫。從變化頻率彈性來說,一碼通系統的問題在於,個⼈碼頁⾯聚合了太多的內容,沒有基於容器的叢集搭建和熔斷機制。從 CFR 跨功能需求來說,問題在於,開發人員在進行服務設計時沒有考慮伺服器的峰值限制,在系統測試設計階段沒有做好效能和壓⼒測試,導致系統最終超過了負荷。從⽹絡瓶頸來說,理論上來說 1000M ⽹卡的傳輸速度是 125MB/s,100M ⽹卡的傳輸速度是 12.5MB/s,在當天出現故障的願意可能是網路頻寬不夠支撐,導致網路上的瓶頸。
■圖 2
04 Solution 方案列舉
完成分析之後需要制定問題解決方案,在方案列舉過程中,不要著急表達自己的傾向,應該先確定備選方案,並對其進行排列和組合。
4.1 基於資料分層架構
針對資料來說,可以分為 UI 層個性資料、快取資料和 DB 全量資料。快取設計遵循就近原則,資料離使用者越近越好,這樣效能可能就越好。按照這種原則,基於資料的分層架構實際上是一種漏斗型架構,它是以物件和集合為單位的一種快取策略,能夠減少對下層系統的訪問。針對每一層資料,可以採用不同的方法進行處理。
對於 UI 層個性資料,就一碼通系統出現的問題而言,當使用者點選查詢核酸結果時,可以令按鈕不可用,從功能上禁止重複提交,比如設定 15 秒以後才能開啟,這樣就會阻斷流量。對於快取資料,可以在瀏覽器進行快取,比如把常見的圖片、靜態檔案、指令碼快取起來,這可能只需要有限的資源就可以讓請求進入對應的後續階段。此外,還可以利用 CDN 快取分發網路。資料從 UI 層到達應用層或者服務端,可以採用 NGINX 或者中間伺服器進行代理,比如在伺服器端為頻繁查詢,但修改較少的資料建立快取。
對於 DB 全量資料,主要應專注於儲存,而不是進行復雜的運算。資料庫廠商可以支援資料庫限流,當達到一定的流量時。返回對應的異常碼會告訴使用者不可用,對應的服務端根據情況可以進行熔斷處理,而不至於讓資料庫一直處理資訊而不能響應,進而導致整個應用崩潰。另外,在當天一碼通系統出現問題的時候,建立不同的微服務對核酸報告進行動態的彈性擴容是一個不錯的選擇,劃分手段就是 DDD。
■圖 3
4.2 業務變化頻率和彈性
在創業過程中或在一些比較複雜的系統中,可以做一些體驗設計,對系統業務能力進行劃分。要基於上下文,根據系統業務能力判斷是否從單體轉為微服務。另外,還要考慮業務變化頻率和彈性,比如核酸檢測是近期使用非常頻繁的功能,將其放至主頁,進入系統後可以直接查詢。事實也證明,當時西虹市在疫情出現幾天後就把核酸檢測這個功能直接放到頁面上,這間接說明了業務變化頻率對系統的設計是很重要的。
■圖 4
4.3 CFR – 測試設計與效能
針對 CFR 來說,圖 5 展示了有指導意義的測試四象限。Q1 象限從技術出發支撐團隊的整個測試,包括單元測試和元件測試,可以幫助團隊儘快發現問題。Q2 象限從業務角度支撐團隊測試,更側重於發現功能和業務上的問題。Q3 象限從業務角度來評價產品,主要包括一些探索性測試。Q4 象限從技術角度來評價產品,包括效能測試、壓力測試以及安全測試。可以將 4 個象限分為質量交付(Q1、Q2、Q3)和運維(Q1、Q2、Q3、Q4)兩條指引。隨著 DevOps 的盛行,往往把這四個象限是結合起來,制定有效的測試策略,使測試和開發在專案中能夠落地。
■圖 5
從效能設計方案來說,在併發量很高的情況下,比如一秒鐘有 100 個請求,那麼是否要把 100 個請求直接放到服務端和資料庫,進行 100 次查詢?顯然不是,解決方案應該是把這些請求合併到一起。可以通過限時器或者定時器的形式把請求合到一起,在查詢之後找到對應的 API 對應進行返回。這實際上是批量查詢的變種。但如果請求較少,就沒有必要進行請求合併了,應根據情況配置。還有一種方法叫限流,這時可以採用令牌桶演算法,令牌桶的容量是⼀定的,令牌是以⼀定的速率加進去的,如果桶已經滿了,就不再繼續新增。也可以採用漏桶演算法,不管當前有多少併發數,通過出⽔速率保證後臺程式接到的請求數是⼀定的,可以達到限流的⽬的。這種方法不適用於一碼通系統事件的情況。中介軟體限流方法是 Tomcat 使⽤ maxThreads 來實現限流,也可以通過 NGINX 的 limit_req_zone 和 burst 來實現速率限流,NGINX 的 limit_conn_zone 和 limit_conn 兩個指令可以控制併發連線的總數。
4.4 網路瓶頸
從網路瓶頸來說,為了防止網路堵塞情況發生,可以嘗試把訪問方式由 HTTP 變成 TCP,例如訪問 Redis 快取,這種情況採⽤ RESP ⽅式。還可以使⽤更⾼檔次的⽹卡,例如採⽤ DNS 負載均衡,使多個 IP 對應同⼀個域名。
05 Tradeoff 權衡和取捨
做軟體就是做權衡。具體來說,前端落地後,客戶端快取、瀏覽器快取、CDN 快取等都可以開始執行,首先訪問伺服器,這裡的伺服器包含對應的 NGINX 或者負載均衡器,流量接下來到達應用層和服務層,如果此階段流量較大,可以多線進行效能優化或者高效能的 RPC,也可以新增快取。然後可以進入微服務框架。
回到快取部分,資料訪問層可能包含 Redis 等,一些頻繁訪問但不經常變的資料就可以快取到這裡,通過請求合併或者查詢減少 I/O。在儲存層,資料庫比較注重全量資料,如果資料庫壓力比較大,可以考慮分庫和分表。根據不同的資料情況,甚至不同的人、不同的區,都可以建立自己的資料庫來進行訪問。從基礎設施來說,系統要能夠支援快速的擴容,如果把業務變化頻率彈性考慮進去,那麼雲原生是不可缺少的。最後列出一個方案僅供參考。
答疑環節
1、如何帶領和管理初創企業的技術團隊?
要想帶領團隊,首先應該確定團隊的方向,也就是專案願景。確定願景之後才有了目標,然後根據實際情況,確認支撐這些目標完成所需要的人員技能要求。其次,團隊要進行能力提升,因為要完成業務目標,需要對應的能力輸出。另外,如果團隊人員比較多,還要有團隊規範,使公司或者專案的戰略流程化,流程工具化。對於組織來說,我認為還要進行不停的學習嘗試,應該從客戶的角度來解決其痛點。
2、一碼通系統的 CDN 設計有什麼原則?
一般情況下,在配置的時候,要明確有哪些是能夠快取起來的。比如,可以把不經常訪問也不經常變化的資料放到 CDN 快取中,具體要根據業務資料的情況來決定。
3、請求如何合併?
在 Java 中有 feature 功能可以引入請求。比如,1 秒鐘有 100 個請求,引入請求之後可以將其分為 10 份,通過執行緒池一秒內遍歷 10 次。具體可以把請求分別新增至執行緒池中,然後執行緒池定時觸發呼叫請求。feature 功能使請求從資料庫返回之後,能夠找到對應的 request。對於這些問題,JavaSpring 中已經有比較成熟的方案。