系統架構之高可擴充套件系統設計與實現
image
一、可擴充套件的本質是什麼?
可擴充套件的意思是在面對變化時,用最少的代價去實現,平時我們聽得最多的是面向抽象 (介面) 程式設計,如果只是把這裡的抽象理解成介面,那麼就有些狹隘了,抽象是通式通法,而介面只是其中一個,所以在談可擴充套件實現之前一定要講清楚可擴充套件的本質是什麼,連本質都不知道,怎麼提出系統性解決方案。
1.1 擴充套件的本質
擴充套件的本質就是佔位符,明確告訴你這裡被佔了,具體誰佔了不清楚。那麼問題來了:佔位符到底是什麼?它是怎麼表達的?又要如何實現的?如果可以把這三個問題理清楚,就可以想到很多可擴充套件性方案,而不再是單一的面向介面程式設計。
佔位符到底是什麼
佔位符怎麼表達:要回答這個標識是用什麼來表達,變數、介面、配置項…這些都可以表達佔位符,變數能被賦值同一型別的資料;介面可以有不同的實現;配置項也可以被賦予不同的值…所以,實現可擴充套件的思路一下就打開了。
如何實現:再往深層次思考,實現一個介面,如何在執行時動態找到實現類?如果把這個問題想清楚,在實際中實現可擴充套件又會有一套系統性解決方案。整個過程就兩點:識別和執行,識別的意思就是要找到對應目標,接下來就是執行。
綜上,到這裡可能已經有自己應對可擴充套件的方法,上面已經給了從不同角度看可擴充套件性的示例,接下來就是系統化提出應對可擴充套件的方法。
結論一:擴充套件的本質就是佔位符,凡是可以表達變化的就是佔位符。
1.2 應對可擴充套件的方法
先給出應對可擴充套件的方法:規範、識別、註冊、使用,這 4 點都是從上面可推匯出來的,下面一一進行詳細說明。
規範:規範是從佔位符推匯出來的,既然是標誌有變化,一定要遵循一定的規範表達,否則別人是不知道的,如介面,就是很直接地表達這裡是有變化的,具體的實現還不知道;變數天然地表達這裡是變化的資料。
識別:有了規範定義之後,接下來就是識別,之前為什麼可擴充套件一直對我們來講很虛,那是因為規範和識別都是系統幫我們做的,我們只是知道而沒有真正實踐。規範是定義,識別是找出有哪些實現了規範。
註冊
使用:使用就很簡單,找到具體實現並執行邏輯處理。
上面四個單詞看起來簡單,除了使用是終極目標外,其它三個都是抽象的表達,比如規範如何定義、怎麼識別、如何註冊?通過上面的表述可以看到具體要怎麼實踐,這裡再總結下:
規範如何去定義:凡是可以表達變化的就能用它來定義,常見的有配置項、變數、介面、註解等;
怎麼去識別:這個要具體去看如何定義規範,如配置項的變化有一個監聽變化;註解是要掃描類來識別 annotation;
如何去註冊:如果系統的互動只是一個,那麼儲存在本地就行,如果系統的互動是多個,那麼要註冊到一個註冊中心上去。
結論二:應對可擴充套件的方法:規範、識別、註冊、使用。
1.3 擴充套件的經典案例
此處使用一個經典案例來說明可擴充套件性,並從其原理上印證上述方法。
在 Java 中,SPI 對於大部分人來講並不陌生,最典型的載入資料庫驅動就是通過 SPI 來實現的。如果你看了 SPI 的原理,再去看上面寫的,會感覺兩個思路很相似。
SPI 有它的規範,要到指定目錄下載入對應檔案;找到檔案後進行解析、識別並載入;最後就是使用。整個流程能印證上面所提到的:規範、識別、註冊、使用。所以,方法的提出有一個點就是從具體案例中進行抽象,提煉共性的東西,再去推演其它案例看能不能也滿足。
二、可擴充套件性系統實踐之路
此處以優惠券業務平臺為例講解可擴充套件性系統設計與實現,在上一篇文章中已經講了優惠券系統是一個平臺型的業務系統,要做到業務與業務的隔離、業務與平臺的隔離。
2.1 識別變化
經過整體分析之後,已經確定大業務流程:建券、發券、用券、退券,以及對應的子流程,接下來就是要分析出哪些內容會變化。
比較明顯的變化就是領券、用券門檻的變化,因為不同業務線有不同的限制條件,有的要限制不同人群,有的要限制領取次數…已經認別了變化接下來就是要處理這些變化。
結論三:找擴充套件點就是找系統經常變化的地方。
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 程式碼拉下來之後就可以編譯,並存儲到本地或者叢集快取中。
最後就是執行具體的實現邏輯。
結合這張圖看就會清晰很多,整體的業務平臺架構比較清晰,分為開放平臺、配置平臺、業務平臺和資料平臺,一個新業務方接進來很簡單,簡單配置下就可以使用。
三、小結
本篇文章主要講可擴充套件性系統的設計與實現,從可擴充套件的本質講起,可擴充套件的本質就是佔位符,凡是可表達變化的都可以稱之為佔位符,常見的有變數、介面、配置項、註解等,然後提出應對可擴充套件性的方法:規範、識別、註冊、使用四個步驟,雖然只有 8 個字,但它包含了一套系統的處理方案,不再是單一的面向介面程式設計,最後結合具體的案例進行說明如何設計可擴充套件性系統。
作者介紹:
高福來,先後在 Oracle、阿里工作,目前在滴滴小桔車服加油團隊負責營銷基礎 (優惠券、獎勵金),在分散式中介軟體和系統架構方面積累了一定的經驗,擅長用通俗易懂的語言描述複雜問題。
大家可以加我的程式設計師交流圈子:705127209,群內有阿里技術大牛講解的最新Java架構技術。作為給廣大朋友的加群福利——分散式(Dubbo、Redis、RabbitMQ、Netty、RPC、Zookeeper、高併發、高可用架構)/微服務(Spring Boot、Spring Cloud)/原始碼(Spring、Mybatis)/效能優化(JVM、TomCat、MySQL)【加群備註好訊息領取最新架構資