背景常規業務系統設計關鍵——領域模型
業務系統設計的關鍵是在于如何定義系統的模型以及模型之間的關系,其中主要是領域模型的定義,當我們在模型確定之后,模型之間的關系也會隨之明確。
模型設計可以參考領域模型的經典書籍《Domain-Driven Design》一書,通過這個基本可以對領域定義、防腐層、貧血模型等概念有一個較為清晰的認識了。
單個應用內的領域模型系統也需要注意領域分層,作為開發大家是不是見過、重構過很多Controller-Service-DAO 樣式的代碼分層設計?往往在在做重構的時候會令人吐血。
設計較好的領域設計這里給一個分層建議:
主要負責與外部系統進行交互&通信,比如一些 dubbo服務、Restful API、RMI等,這一層主要包括 Facade、DTO還有一些Assembler。
這一層包含的主要組件就是 Service 服務,但是要特別注意,這一層的Service不是簡單的DAO層的包裝,在領域驅動設計的架構里面,Service層只是一層很“薄”的一層,它內部并不實現任何邏輯,只是負責協調和轉發、委派業務動作給更下層的領域層。
Domain 層是領域模型系統的核心,負責維護面向對象的領域模型,幾乎全部的業務邏輯都會在這一層實現。內部主要包含Entity(實體)、ValueObject(值對象)、Domain Event(領域事件)和 Repository(倉儲)等多種重要的領域組件。
它主要為 Interfaces、Application 和 Domain 三層提供支撐。所有與具體平臺、框架相關的實現會在 Infrastructure 中提供,避免三層特別是 Domain 層摻雜進這些實現,從而“污染”領域模型。Infrastructure 中最常見的一類設施是對象持久化的具體實現。
高并發系統設計
在面試中是不是經常被問到一個問題:如果你系統的流量增加 N 倍你要怎么重新設計你的系統?這個高并發的問題可以從各個層面去解,比如
鎖優化(采用無鎖數據結構),主要是 concurrent 包下面的關于 AQS 鎖的一些內容
數據庫緩存設計(降低數據庫并發爭搶壓力),這里又會有緩存、DB 數據不一致的問題,在實際使用中,高并發系統和數據一致性系統采用的策略會截然相反。
數據更新時采用合并更新,可以在應用層去做更新合并,同一個 Container 在同一時間只會有一個 DB 更新請求。
其他的比如基于 BloomFilter 的空間換時間、通過異步化降低處理時間、通過多線程并發執行等等。
根據不同的存儲訴求來進行不同的存儲選型,從早期的 RDBMS,再到 NoSql(KV存儲、文檔數據庫、全文索引引擎等等),再到最新的NewSql(TiDB、Google spanner/F1 DB)等等。
表數據結構的設計,字段類型選擇與區別。
索引設計,需要關注聚簇索引原理與覆蓋索引消除排序等,至于最左匹配原則都是爛大街的常識了,高級一點索引消除排序的一些機制等等,B+樹與B樹的區別。
最后的常規手段:分庫分表、讀寫分離、數據分片、熱點數據拆分等等,高并發往往會做數據分桶,這里面往深了去說又有很多,比如分桶如何初始化、路由規則、最后階段怎么把數據合并等等,比較經典的方式就是把桶分成一個主桶+N個分桶。
分布式系統為服務化
無狀態化支持水平彈性擴縮容
業務邏輯層面 failfast 快速失敗
調用鏈路熱點數據前置
多級緩存設計
提前容量規劃等等
高可用系統設計
對于可用性要求非常高的系統,一般我們都說幾個9的可用率,比如 99.999% 等。
面對高可用系統設計也可以從各個方面來進行分析
代碼層面:需要關注分布式事務問題,CAP理論是面試的常規套路
軟件層面:應用支持無狀態化,部署的多個模塊完全對等,請求在任意模塊處理結果完全一致 => 模塊不存儲上下文信息,只根據請求攜帶的參數進行處理。目的是為了快速伸縮,服務冗余。常見的比如session問題等。
軟件部署多份之后,如何保證系統負載?如何選擇調用機器?也就是負載均衡問題
狹義上的負載均衡按照類型可以分為這幾種:
硬件負載:比如F5等
軟件負載:比如 LVS、Ngnix、HaProxy、DNS等。
當然,還有代碼算法上的負載均衡,比如Random、RoundRobin、ConsistentHash、加權輪訓等等算法
廣義上的負載均衡可以理解為負載均衡的能力,比如一個負載均衡系統需要如下4個能力:
故障機器自動發現
故障服務自動摘除(服務熔斷)
請求自動重試
服務恢復自動發現
上面提負載均衡的時候,廣義負載均衡需要完成自動重試機制,那么在業務上,我們就必須保證冪等設計。
這里可以從2個層面來進行考慮:
請求層面
由于請求會重試所以必須做冪等,需要保證請求重復執行和執行一次的結果完全相同。請求層面的冪等設計需要在數據修改的層做冪等,也就是數據訪問層讀請求天然冪等,寫請求需要做冪等。讀請求一般是天然冪等的,無論查詢多少次返回的結果都是一致。這其中的本質實際上是分布式事務問題,這里下面再詳細介紹。
業務層面
不冪等會造成諸如獎勵多發、重復下單等非常嚴重的問題。業務層面的冪等本質上是分布式鎖的問題,后面會介紹。如何保證不重復下單?這里比如token機制等等。如何保證商品不超賣?比如樂觀鎖等。MQ消費方如何保證冪等等都是面試的常見題。
業務層面的冪等設計本質上是分布式鎖問題,什么是分布式鎖?分布式環境下鎖的全局唯一資源,使請求串行化,實際表現互斥鎖,解決業務層冪等問題。
常見的解決方式是基于 Redis 緩存的 setnx 方法,但作為技術人員應該清楚這其中還存在單點問題、基于超時時間無法續租問題、異步主從同步問題等等,更深一點,CAP理論,一個AP系統本質上無法實現一個AP需求,即使是 RedLock 也不行。
那我們如何去設計一個分布式鎖呢?強一致性、服務本身要高可用是最基本的需求,其他的比如支持自動續期,自動釋放機制,高度抽象接入簡單,可視化、可管理等。
基于存儲層的可靠的解決方案比如:
zookeeper
CP/ZAB/N+1可用:基于臨時節點實現和Watch機制。
ETCD
CP or AP/Raft/N+1可用:基于 restful API;KV存儲,強一致性,高可用,數據可靠:持久化;Client TTL 模式,需要心跳CAS 唯一憑證 uuid。
微服務化之后,系統分布式部署,系統之間通過 RPC 通訊,整個系統發生故障的概率隨著系統規模的增長而增長,一個小的故障經過鏈路傳導放大,有可能造成更大的故障。希望在調用服務的時,在一些非關鍵路徑服務發生服務質量下降的情況下,選擇盡可能地屏蔽所造成的影響。
大部分熔斷返回默認值 null,也可以定制,RPCClient 原生支持最好,業務方少改代碼(熔斷放的地方),進入熔斷時,打印熔斷日志,同時返回 Exception(業務方定制熔斷方法),需要有服務治理平臺,可以看到服務的狀態、是否降級、是否熔斷、可以實時下發閥值配置等。
服務整體負載超出預設的上限,或者即將到來的流量預計將會超過閥值,為了保證重要或者基本的服務能夠正常運行,拒絕部分請求或者將一些不重要的不緊急的服務或任務進行服務的延遲使用或暫停使用。
主要的手段如下:
服務層降級,主要手段
拒絕部分請求(限流),比如緩存請求隊列,拒絕部分等待時間長的請求;根據Head,來拒絕非核心請求;還有其他通用算法上的限流比如令牌桶、漏桶算法等等。
關閉部分服務:比如雙11大促0點會關閉逆向退款服務等等。
分級降級:比如自治式服務降級,從網關到業務到DB根據攔截、業務規則逐漸降低下游請求量,體現上是從上到下的處理能力逐漸下降。
數據層降級
比如流量大的時候,更新請求只緩存到MQ,讀請求讀緩存,等流量小的時候,進行補齊操作(一般數據訪問層如果做了降級,就沒必要在數據層再做了)
柔性可用策略
比如一些指定最大流量的限流工具,又或是根據CPU負載的限流工具等,需要保證自動打開,不依賴于人工。
發布方式也是影響高可用的一個點,哈哈,以前還經歷過一些線上直接停機發布的案例(銀行內部系統),不過作為高大上的互聯網,主要會采用這幾種發布方式:灰度發布、藍綠發布、金絲雀發布等等。
數據一致性系統設計
一般一些金融、賬務系統對這一塊要求會非常嚴格,下面主要介紹下這里面涉及到的事務一致性、一致性算法等內容。
在 DB 層面,一般通過 剛性事務 來實現數據一致性,主要通過 預寫日志(WAL) 的方式來實現,WAL(write ahead logging)預寫日志的方式。就是所有對數據文件的修改,必須要先寫日志,這樣,即使在寫數據的時候崩潰了,也能通過日志文件恢復,傳統的數據庫事務就是基于這一個機制(REDO 已提交事務的數據也求改 UNDO 未提交事務的回滾)。
除了這個方式之外,還有一個就是通過 影子數據塊 來進行數據備份,提前記錄被修改的數據塊的修改前的狀態,備份起來,如果需要回滾,直接用這個備份的數據塊進行覆蓋就好了。
其他的就是基于二階段提交的 XA模型 了。
但是目前互聯網系統,已經廣泛采用分布式部署模式了,傳統的剛性事務無法實現,所以 柔性事務成了目前主流的分布式事務解決防范,主要的模式有下面幾種:
TCC 模式/或者叫2階段模式
在 try 階段預扣除資源(但是不鎖定資源,提升可用性),在Confirm 或者 Cancel 階段進行數據提交或者回滾。一般需要引入協調者,或者叫事務管理器。
SAGA模式
業務流程中每個參與者都提交本地事務,當出現某一個參與者失敗則補償前面已經成功的參與者,支持向前或者向后補償。
MQ的事務消息
就是先發 halfMsg,在處理完之后,再發送 commit 或者 rollback Msg,然后 MQ 會定期詢問 producer ,halfMsg? 能不能 commit 或者 rollback,最終實現事務的最終一致性。實際上是把補償的動作委托給了 RocketMQ。
分段事物(異步確保)
基于可靠消息+本地事務消息表 + 消息隊列重試機制。目前這也是一些大廠的主流方案,內部一般稱為分段事物。
柔性事務基本都是基于最終一致性去實現,所以肯定會有 補償 動作在里面,在達到最終一致性之前,對用戶一般展示 軟狀態。
需要注意的一點是,并不是所有的系統都適合引入數據一致性框架,比如用戶可以隨時修改自己發起的請求的情況,例如,商家設置后臺系統,商戶會隨時修改數據,這里如果涉及到一致性的話,引入一致性框架會導致補償動作達到最終一致性之前,資源鎖會阻塞用戶后續的請求。導致體驗較差。這種情況下就需要通過其他手段來保障數據一致性了,比如數據對賬等操作。
從早期的 Paxos 算法,再到后面衍生的 zab 協議(參考:A simple totally ordered broadcast protocol),提供了當下可靠的分布式鎖的解決方案。再到后來的 Raft 算法(In Search of an Understandable Consensus Algorithm),也都是分布式系統設計里面需要了解到的一些知識要點。
最后
這里簡單介紹了不同系統設計的時候會面臨的一些難點,基本里面每一個點,都是前人在解決各種疑難問題的道路上不斷探索,最終才得出的這些業界解決方案,呈現在大家眼前,作為一個技術人員,學會這些技術點只是時間問題,但這種發現問題、直面問題、再到解決問題的能力和精神才是我們最值得學習的地方,也是做為一個系統設計人員或者說是架構師的必要能力。
|