[億級流量架構讀後記錄一] 交易性系統設計原則

交易性系統設計原則

高併發原則

1. 無狀態

  • 應用無狀態, 配置文件有狀態, 方便水平擴展

2. 拆分

  • 系統維度— 商品, 購物車, 結算, 訂單);
  • 功能維度— 優惠券系統可以拆分爲後臺券創建系統, 領券系統, 用券系統等;
  • 讀寫維度— 讀服務架構緩存; 寫服務分庫分表; 聚合讀取的數據,如商品詳情頁, 考慮將分散的數據聚合一處存儲;
  • AOP維度— 根據訪問特徵, 比如, 商品詳情頁可以分爲CDN, 頁面渲染系統; CDN就是一個AOP系統;
  • 模塊維度— 比如按照基礎或者代碼維護特徵進行拆分, 如基礎模塊分庫分表, 數據庫連接池等基礎服務; 代碼結構一般按照三層架構 web, service, dao進行拆分;

3. 服務化

​ 除了服務的自動註冊和發現, 還要考慮服務的分組/隔離,比如,有的系統訪問過大,導致把整個服務打掛,因此,需要爲不同的調用方提供不同的服務分組,隔離訪問(其實就是進行隔離). 後期隨着調用量增加還要考慮服務的限流/黑白名單等. 其中摻雜細節: 超時時間/重試機制/服務路由(能動態切換不同的分組)/故障補償等.

4. 消息隊列

​ 消息隊列的重要性不言而喻, 功能: 服務解耦(一對多消費)、異步消費、流量削峯/緩衝等。比如,訂單數據, 有非常多的系統關心並訂閱,比如,訂單生產系統、定期送系統、訂單風控系統等。如果訂閱者太多,那麼訂閱單個消息隊列就會成爲瓶頸,此時需要考慮對消息隊列進行多個鏡像複製。

​ 當然還有生產消息失敗,消息重複接受時的場景。指定重試次數內進行生產重試(或者記錄,報警)。對於消息重複問題,需要做防重處理。

案例:

  • 扣減庫存,redis扣減庫存 > 記錄扣減日誌 (後臺同步 worker 將其 同步到 DB);

  • 交易訂單系統 , 從購物車確認結算時, 將訂單存儲到訂單redis和訂單隊列表(可按需水平拓展多個表), 然後通過同步worker同步到訂單中心表; 假設用戶支付訂單, 訂單狀態機會驅動狀態變更,此時,可能訂單隊列表的訂單還沒有同步到訂單中心表(不存在或者數據不對),狀態機要根據實際情況進行重試。

    ​ 如果用戶查看單個訂單詳情, 那麼可以直接從訂單redis中查到。但如果查詢訂單列表,則考慮訂單redis和列表的合併。

    ​ 同步worker在設計時, 需要考慮併發處理和重複處理的問題。比如,使用單機串行掃描處理(每臺worker掃描其中的一部分表)還是集羣處理(Map-Reduce)。另外,需要考慮是否需要對訂單隊列表添加相關字段: 處理人(哪個應用正在處理)和處理狀態(正在處理、已處理、處理失敗)、最後處理時間(應對超時)、失敗次數等。

    ​ 數據校對: 在消息異步機制場景下,可能存在消息丟失,需要考慮進行數據校對和修正來保證數據的一致性和完整性.可通過worker定期去掃描原始表, 通過對業務數據進行比對, 有問題進行補償, 掃描週期按需定義.

5. 數據異構

  • 數據異構: 訂單分庫分表按照訂單Id進行分,如果要查詢某個用戶的訂單列表,則需要聚合多個表的數據後才能返回,這樣會導致訂單表讀性能很低.此時需要對訂單表進行異構。異構一套用戶訂單表,按照用戶id進行分庫分表。另外,還需要考慮對歷史訂單數據進行歸檔處理,以提升服務的性能和穩定性。而有些數據異構的意義不大,如庫存價格,可以考慮異步加載,或者合併併發請求。

  • 數據閉環: 數據閉環如商品詳情頁,因爲數據來源太多,影響服務穩定性因素太多。因此,最好的辦法是把使用到的數據進行異構存儲,形成數據閉環,基本步驟如下:

    • 數據異構:通過如MQ機制進行接收數據變更,然後原子化存儲到合適的存儲引擎,如redis或持久化KV存儲。
    • 數據聚合:這步是可選的,數據異構目的是把數據從多個數據源拿過來,數據聚合的目的是把這些數據做個聚合,這樣前端一次調用即可,一般存儲到KV中。
    • 前端展示:前端通過一次或者少量幾次調用拿到所需要的數據。

    ​ 這種方式的好處就是數據的閉環,任何依賴系統出問題了,還是能正常工作,只是更新會有積壓,但是不影響前端展示。

    ​ 另外,如果一次需要多個數據,可以考慮使用Hash Tag機制將相關數據聚合到一個實例,如在展示商品詳情頁時需要商品基本信息”p:productId:和商品規格參數”d:productId:”,使用冒號中間的productId作爲數據分片key,這樣相同productId的商品相關數據就在一個實例。

    ​ 數據閉環和數據異構其實是一個概念,目的都是事先數據的自我控制,當其他系統出問題時不影響自己的系統,或者自己出問題時不影響其他系統。一般通過消息隊列來實現數據分發。

6. 緩存銀彈

  • 瀏覽器端緩存

  • app客戶端緩存

  • CDN緩存 (需要注意url中不能有隨機數,否則穿透CDN回源到源服務器。對於爬蟲,可以返回過期數據不回源)

  • 接入層緩存,對於沒有CDN緩存的應用來說,考慮使用nginx搭建一層接入層,該接入層考慮如下機制:

    1. url重寫:將url按照指定的順序或格式重寫,去除隨機數。

    2. 一致性哈希

    3. proxy_cache: 使用內存級/SSD級代理緩存來緩存內容。
    4. proxy_cache_lock: 使用lock機制,將多個回源合併爲一個,以減少回源量,並設置相應的lock超時時間。 當多個客戶端同時請求同一份內容時,如果開啓proxy_cache_lock(默認off)則只有一個請求被髮送至後端;其他請求將等待該內容返回;當第一個請求返回時,其他請求將從緩存中獲取內容返回;當第一個請求超過了proxy_cache_lock_timeout超時時間(默認5s),則其他請求將同時請求到後端來獲取響應,且響應不會被緩存;啓用proxy_cache_lock可以應對雪崩效應。
    5. shared_dict: 如果架構使用了nginx + lua實現,則可以考慮使用 lua shared_dict進行cache,最大的好處就是reload緩存不會丟失。

    ​ 此處注意,對於託底(或兜底,指降級後顯示的)數據或異常數據,不應該讓其緩存,否則用戶會很長一段時間看到這些數據。

  • 應用層緩存,tomcat 或者 local redis cache,或者接入層使用shared_dict來講緩存前置,以減少風暴。

  • 分佈式緩存,如果數量不大,使用local redis是最優的。但是數據量太大,單服務器存儲不了,可以使用分片機制將流量分散到多臺,或者直接用分佈式緩存。

架構緩存例子:

  1. 首先接入層(nginx + lua)讀取本地proxy cache / local cache
  2. 如果不命中, 則接入層會接着讀取分佈式redis緩存
  3. 如果還不命中, 則會回源到Tomcat, 然後讀取Tomcat應用堆內cache
  4. 如果都沒有命中, 則調用依賴業務來獲取數據, 然後異步化寫到redis集羣等

​ 此種架構, 因爲使用nginx + lua, 第二三步可使用lua-resty-lock非阻塞鎖減少峯值時需要的回源量; 如果你的服務是用戶維度的,那麼這種非阻塞鎖大部分情況下不會有太大的作用

7. 併發化 所謂 fork/join

高可用原則

1. 降級

​ 對於一個高可用服務,很重要的一個設計就是降級開關,在設計降級開關時,主要依據如下思路.

  • 開關集中化管理: 通過推送機制把開關推送到各個應用.
  • 可降級的多級讀服務: 比如服務調用降級爲只讀本地緩存、只讀分佈式緩存、只讀默認降級數據(如庫存狀態默認有貨, 即默認值).
  • 開關前置化: 如架構是nginx->tomcat, 可以將開關前置到nginx接入層,在nginx層做開關,請求流量回源後端應用或者只是一部分流量回源.
  • 業務降級: 即高峯時, 保證核心業務,保障數據最終一致性即可. 這樣可以把一些同步調用改成異步滴啊用, 優先處理高優先級數據或特殊特徵的數據, 合理分配進入系統的流量, 以保障系統可用.

2. 限流

​ 限流的目的是防止惡意請求流量, 惡意攻擊, 或防止流量超出系統峯值. 可考慮一下思路:

  • 惡意請求流量只訪問到cache.

  • 對於穿透到後端應用的流量可以考慮使用nginx的limit的模塊處理.

  • 對於惡意ip可以使用nginx deny進行屏蔽.

    原則是限制流量傳統到後端薄弱的應用層.

3. 切流量

​ DNS、HttpDns、lVS/HaProxy、nginx 做切換

4. 可回滾

​ 事務回滾、代碼回滾、部署版本回滾、數據版本回滾、靜態資源回滾就

業務涉及原則

1. 防重設計

  • 比如, 購物側結算頁需要考慮重複提交
  • 下單扣庫存時需要防止重複扣減庫存, 解決方案可考慮防重key, 防重表.
  • 重複支付, 渠道不一樣是無法防止重複支付的. 但是, 在系統設計的時候, 需要將支付的每筆情況記錄下來.

2. 冪等設計

​ 比如消息重複, 第三方異步回調等

3. 流程可定義

​ 保險, 承保流程和理賠流程是分離的, 在需要時進行關聯, 從而複用, 並能提供個性化理賠.

4. 狀態與狀態機

​ 一般會存在正向狀態(待退款, 待發貨, 已發貨, 完成)和逆向狀態(取消, 退款)等. 正向狀態和逆向狀態應該根據系統的特徵來決定要不要分離存儲. 狀態設計時應有狀態軌跡, 方便用戶跟蹤當前訂單的軌跡並記錄相關日誌, 萬一出問題時可回溯問題.

​ 如果狀態很多時,可以考慮使用狀態機驅動狀態的變更和後續流程節點操作, 能更好控制狀態遷移; 還要考慮併發狀態修改問題, 如一個訂單同時只能有一個修改; 狀態變更的有序問題, 以及狀態變更消息的先到後到問題, 如支付成功消息和用戶取消消息的時間差.

5. 後臺系統操作可反饋

6. 後臺系統審批化

7. 文檔和註釋

8. 備份 (包括代碼和人員)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章