系統架構之高可擴展系統設計與實現

可擴展性是衡量架構設計的一個因素,也經常被開發者提到。但是,一個系統要設計出比較好的可擴展性是有一定難度的,而且可擴展性體現在不同層次上,有大的可擴展性,也有小的可擴展性,本文從可擴展的本質出發,通過平時常用的框架來印證,最後通過實際案例說明如何設計高可擴展性系統。

webp

image

一、可擴展的本質是什麼?

可擴展的意思是在面對變化時,用最少的代價去實現,平時我們聽得最多的是面向抽象 (接口) 編程,如果只是把這裏的抽象理解成接口,那麼就有些狹隘了,抽象是通式通法,而接口只是其中一個,所以在談可擴展實現之前一定要講清楚可擴展的本質是什麼,連本質都不知道,怎麼提出系統性解決方案。

1.1 擴展的本質

擴展的本質就是佔位符,明確告訴你這裏被佔了,具體誰佔了不清楚。那麼問題來了:佔位符到底是什麼?它是怎麼表達的?又要如何實現的?如果可以把這三個問題理清楚,就可以想到很多可擴展性方案,而不再是單一的面向接口編程。

佔位符到底是什麼:佔位符僅僅是一個標識,標誌這裏會有變化,一句話可以概括:凡是可以表達變化的就是佔位符,然而具體的變化實現又沒有給出,真正體現了做什麼和怎麼做的分離

佔位符怎麼表達:要回答這個標識是用什麼來表達,變量、接口、配置項…這些都可以表達佔位符,變量能被賦值同一類型的數據;接口可以有不同的實現;配置項也可以被賦予不同的值…所以,實現可擴展的思路一下就打開了。

如何實現:再往深層次思考,實現一個接口,如何在執行時動態找到實現類?如果把這個問題想清楚,在實際中實現可擴展又會有一套系統性解決方案。整個過程就兩點:識別和執行,識別的意思就是要找到對應目標,接下來就是執行。

綜上,到這裏可能已經有自己應對可擴展的方法,上面已經給了從不同角度看可擴展性的示例,接下來就是系統化提出應對可擴展的方法。

結論一:擴展的本質就是佔位符,凡是可以表達變化的就是佔位符

1.2 應對可擴展的方法

先給出應對可擴展的方法:規範、識別、註冊、使用,這 4 點都是從上面可推導出來的,下面一一進行詳細說明。

規範:規範是從佔位符推導出來的,既然是標誌有變化,一定要遵循一定的規範表達,否則別人是不知道的,如接口,就是很直接地表達這裏是有變化的,具體的實現還不知道;變量天然地表達這裏是變化的數據。

識別:有了規範定義之後,接下來就是識別,之前爲什麼可擴展一直對我們來講很虛,那是因爲規範和識別都是系統幫我們做的,我們只是知道而沒有真正實踐。規範是定義,識別是找出有哪些實現了規範。

註冊:識別出來之後,就要把信息存儲起來,可以存儲在本地,也可以存儲在遠程,如果存儲在遠程就是一個註冊的過程,這裏的註冊就是存儲的意思。簡單理解就是識別出來之後要集中管理。

使用:使用就很簡單,找到具體實現並執行邏輯處理。

上面四個單詞看起來簡單,除了使用是終極目標外,其它三個都是抽象的表達,比如規範如何定義、怎麼識別、如何註冊?通過上面的表述可以看到具體要怎麼實踐,這裏再總結下:

規範如何去定義:凡是可以表達變化的就能用它來定義,常見的有配置項、變量、接口、註解等;

怎麼去識別:這個要具體去看如何定義規範,如配置項的變化有一個監聽變化;註解是要掃描類來識別 annotation;

如何去註冊:如果系統的交互只是一個,那麼存儲在本地就行,如果系統的交互是多個,那麼要註冊到一個註冊中心上去。

結論二:應對可擴展的方法:規範、識別、註冊、使用

1.3 擴展的經典案例

此處使用一個經典案例來說明可擴展性,並從其原理上印證上述方法。

在 Java 中,SPI 對於大部分人來講並不陌生,最典型的加載數據庫驅動就是通過 SPI 來實現的。如果你看了 SPI 的原理,再去看上面寫的,會感覺兩個思路很相似。

SPI 有它的規範,要到指定目錄下加載對應文件;找到文件後進行解析、識別並加載;最後就是使用。整個流程能印證上面所提到的:規範、識別、註冊、使用。所以,方法的提出有一個點就是從具體案例中進行抽象,提煉共性的東西,再去推演其它案例看能不能也滿足。

二、可擴展性系統實踐之路

此處以優惠券業務平臺爲例講解可擴展性系統設計與實現,在上一篇文章中已經講了優惠券系統是一個平臺型的業務系統,要做到業務與業務的隔離、業務與平臺的隔離。

2.1 識別變化

經過整體分析之後,已經確定大業務流程:建券、發券、用券、退券,以及對應的子流程,接下來就是要分析出哪些內容會變化。

webp

比較明顯的變化就是領券、用券門檻的變化,因爲不同業務線有不同的限制條件,有的要限制不同人羣,有的要限制領取次數…已經認別了變化接下來就是要處理這些變化。

結論三:找擴展點就是找系統經常變化的地方

2.2 處理變化的常見手段

2.2.1 野蠻處理

一個最簡單的處理,就是在代碼中寫 if else,它的特點是簡單直接上線,不足的點長期下去,系統會變得很難維護、可擴展性較差。

if(productId = ProductEnum.A){//具體的處理  }elseif(productId = ProductEnum.B){//具體的處理  }elseif(productId = ProductEnum.C){//具體的處理   }else{      ......  }

這種代碼放到現在,很多系統還是這麼做的,而且是在業務發展初期最喜歡用這種野蠻處理方式,搞上去就能直接上線,快速支持業務。

2.2.2 面向接口設計

對上面野蠻方式的一個常見處理就是面向接口設計,抽象出一個限制條件檢查的接口,不同的業務線有對應的實現,通過配置指定業務線下所有的實現,將這些實現放到一個映射中,在程序執行過程中,通過業務線就可以執行所有的接口實現類並依次執行。

List ruleList = RuleFacotry.getByProductId(prodcutId);

這種方法比第一種明顯要好,體現了一定的可擴展性,新加一個規則限制,重新實現接口就行,然後在配置項中加上這個新的實現,代碼的改動量也還好。它有一個明顯的問題就是每次新加一個實現就要發佈上線,有沒有辦法不發佈上線就能滿足目的呢?有,就是下面提到的一類可擴展性設計的方法。

2.3 一類可擴展性設計的方法

再來明確一下目標:系統具備可擴展性和不發佈系統就能實現新增功能。

還是使用上面說的方法:規範、認別、註冊、使用,下面結合這個具體的案例來說明。

規範:這裏是用接口來作爲規範描述限制條件,包含入參和出參,這裏有一個開放平臺,實現了一個接口後就可以提交代碼。

識別:在建優惠券時,會加載業務線有哪些業務規則實現,在領取、使用時可以進行配置選擇,此時只是插入一個變量標識使用某個限制條件 (如限人羣,這個實現的邏輯可能會變化,通過變量名來標識變化)。

註冊:系統在執行的過程中,發現有限制條件的變量名,拿這個變量名從開放平臺中拉取具體的實現存儲在本地 (有一個緩存時間,具體的過期時間依業務考慮,我們取的是 30 分鐘)。

執行:拿到具體的實現後,依次執行。

再整理下流程步驟,讓大家更進一步掌握該設計方法:

在開放平臺提交限制條件接口的實現代碼,有限制人羣的實現、限制領取券次數…

在開放平臺提交之後,會入庫存儲,數據庫裏會存儲一個業務線對應的多個限制實現。

創建優惠券時,會加載業務下的限制規則,通過配置選擇具體要使用到的限制規則 (相同業務線下的不同優惠券可以有不同的規則限制),配置選擇後,會在規範字段中存儲規則實現的 id(規則實現可能會變化,會有多次提交),所以這裏存儲的是 id,在執行的時候可以拿到這個 id。

在領券、用券時,會檢查規則限制有哪些,通過 id 列表從遠程開放平臺拉取具體實現,把 java 代碼拉下來之後就可以編譯,並存儲到本地或者集羣緩存中。

最後就是執行具體的實現邏輯。

webp

結合這張圖看就會清晰很多,整體的業務平臺架構比較清晰,分爲開放平臺、配置平臺、業務平臺和數據平臺,一個新業務方接進來很簡單,簡單配置下就可以使用。

加架構Java架構圈子:867857579  領取資料,裏面會免費分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的資料


三、小結

本篇文章主要講可擴展性系統的設計與實現,從可擴展的本質講起,可擴展的本質就是佔位符,凡是可表達變化的都可以稱之爲佔位符,常見的有變量、接口、配置項、註解等,然後提出應對可擴展性的方法:規範、識別、註冊、使用四個步驟,雖然只有 8 個字,但它包含了一套系統的處理方案,不再是單一的面向接口編程,最後結合具體的案例進行說明如何設計可擴展性系統。

作者介紹:

高福來,先後在 Oracle、阿里工作,目前在滴滴小桔車服加油團隊負責營銷基礎 (優惠券、獎勵金),在分佈式中間件和系統架構方面積累了一定的經驗,擅長用通俗易懂的語言描述複雜問題。



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