天天看點

怎麼提高自己的系統架構水準

原創 勇劍 淘系技術  7月15日

怎麼提高自己的系統架構水準
系統設計與架構,與系統的業務類型關聯還是很大的,比如傳統的業務系統主要關注的是領域模組化設計,高并發、高可用、資料一緻性等系統,在設計的時候會與業務系統有較大的差别,是以這裡針對不同類型的系統,來簡單介紹一下設計的時候面臨的一些難點與解決方案。

背景正常業務系統設計關鍵——領域模型

業務系統設計的關鍵是在于如何定義系統的模型以及模型之間的關系,其中主要是領域模型的定義,當我們在模型确定之後,模型之間的關系也會随之明确。

模型設計可以參考領域模型的經典書籍《Domain-Driven Design》一書,通過這個基本可以對領域定義、防腐層、貧血模型等概念有一個較為清晰的認識了。

單個應用内的領域模型系統也需要注意領域分層,作為開發大家是不是見過、重構過很多Controller-Service-DAO 樣式的代碼分層設計?往往在在做重構的時候會令人吐血。

設計較好的領域設計這裡給一個分層建議:

▐  接口層 Interface

主要負責與外部系統進行互動&通信,比如一些 dubbo服務、Restful API、RMI等,這一層主要包括 Facade、DTO還有一些Assembler。

▐  應用層 Application

這一層包含的主要元件就是 Service 服務,但是要特别注意,這一層的Service不是簡單的DAO層的包裝,在領域驅動設計的架構裡面,Service層隻是一層很“薄”的一層,它内部并不實作任何邏輯,隻是負責協調和轉發、委派業務動作給更下層的領域層。

▐  領域層 Domain

Domain 層是領域模型系統的核心,負責維護面向對象的領域模型,幾乎全部的業務邏輯都會在這一層實作。内部主要包含Entity(實體)、ValueObject(值對象)、Domain Event(領域事件)和 Repository(倉儲)等多種重要的領域元件。

▐  基礎設施層 Infrastructure

它主要為 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問題等。

▐  負載均衡問題

軟體部署多份之後,如何保證系統負載?如何選擇調用機器?也就是負載均衡問題

  • 狹義上的負載均衡按照類型可以分為這幾種:
    1. 硬體負載:比如F5等
    2. 軟體負載:比如 LVS、Ngnix、HaProxy、DNS等。
    3. 當然,還有代碼算法上的負載均衡,比如Random、RoundRobin、ConsistentHash、權重輪訓等等算法
  • 廣義上的負載均衡可以了解為負載均衡的能力,比如一個負載均衡系統需要如下4個能力:
    1. 故障機器自動發現
    2. 故障服務自動摘除(服務熔斷)
    3. 請求自動重試
    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(業務方定制熔斷方法),需要有服務治理平台,可以看到服務的狀态、是否降級、是否熔斷、可以實時下發閥值配置等。

▐  服務降級

服務整體負載超出預設的上限,或者即将到來的流量預計将會超過閥值,為了保證重要或者基本的服務能夠正常運作,拒絕部分請求或者将一些不重要的不緊急的服務或任務進行服務的延遲使用或暫停使用。

主要的手段如下:

  • 服務層降級,主要手段
    1. 拒絕部分請求(限流),比如緩存請求隊列,拒絕部分等待時間長的請求;根據Head,來拒絕非核心請求;還有其他通用算法上的限流比如令牌桶、漏桶算法等等。
    2. 關閉部分服務:比如雙11大促0點會關閉逆向退款服務等等。
    3. 分級降級:比如自治式服務降級,從網關到業務到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),也都是分布式系統設計裡面需要了解到的一些知識要點。

最後

這裡簡單介紹了不同系統設計的時候會面臨的一些難點,基本裡面每一個點,都是前人在解決各種疑難問題的道路上不斷探索,最終才得出的這些業界解決方案,呈現在大家眼前,作為一個技術人員,學會這些技術點隻是時間問題,但這種發現問題、直面問題、再到解決問題的能力和精神才是我們最值得學習的地方,也是做為一個系統設計人員或者說是架構師的必要能力。

上一篇: ECS使用體驗
下一篇: ESC使用感受