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