小型系統如何“微服務”開發
提到“微服務”,我相信網上各種“微服務”的演變案例都會給人一種“因大而分”的前提錯覺,這可能會導致許多的“小白”產生沒有機會接觸“大項目”而對“微服務”可望而不可及也。當然,這種錯覺的產生可能更多來源自於各種“微技術”的“層出不窮”所以“眼花繚亂”,例如Spring Cloud。雖然“大項目”機會不多,但也阻止不了“釘子們”通過教程把微技術跑一遍來裝飾自己可以“微”起來的自信。
“微”只是一種正常思維邏輯
想當年,入行如趕集,同樣作為小白,能把SSH框架跑一遍竟然能給自己帶來無比強大的工作自信。誰沒個“資本”年齡,只可惜自身的“浮躁”逼退了當年追求“本質”的淡定和沈穩。在網互聯網嶄露頭角的年代,系統的焦點可能不在於“單體”應用的“橫向分布”,更多在於對整體業務的“豎向分層”。無論橫分還是豎分,“分”的本質其實就是因為“重”。“分而治之”應該算是人類最基本的思維邏輯。只不過“分”的具體實現還得歸咎於站在我們對立面的是什麽問題。互聯網是把“業務”從線下往線上遷移的主要推力,在這個互聯網初始發展階段,需要體現的可能是線上業務的完整性,因此業務的厚度成為了瓶頸,所以對業務邏輯層次的垂直劃分可能是當時的關鍵解。隨著移動互聯網的普及,“線上業務”已經成為主流,業務的“厚重”已經積累到了另外一種層次,單純的“豎向分層”已經無法滿足厚重業務的積累和支撐。業務橫向分解成為了突破口。大量“分布式”方案油然而生,包括“微服務”。從許多“大而微”的初始現象來看,確實很容易把“大”和“微”聯系在一起,這也是“大”企業走在時代前沿面對“不確定性機會”所“創造”的現象。歷史可以很輕易地給人一種“知其所行”的大局視野,但時代演員卻很容易被局限當前。我個人覺得“微服務”的本質就是一種正常的思維,是一種基本解決問題的思路。“微服務”並非新招式,跟二十六種常用面向對象設計模式一樣,隨著時代經驗的積累會成為我們解決問題的“基準招式”。因此也可以大概推測,未來的發展離不開“微服務”思維模式的導向,例如項目或組織的管理模式都可能會發生變化,或者說已經在悄悄地改變。如果說“微服務”確實像我分析的是一種思維模式,那就不會有大項目和小項目之分了。
“微服務”模式小案例
在工作過程中,大項目畢竟少數,小項目才是考驗技術人“接地氣”的表現所在。有一天,我接到了一個小規模的“話費充值系統”需求,沒有太多復雜功能和邏輯的描述,就是一個能讓用戶在上面自助充值的系統。剩下的理解,靠的就是自身工作經驗的功力了。面對這樣的需求,我首先想到的就是這個關鍵業務流程:
這個流程說簡單可以簡單,說復雜可以“媲美”電商系統,例如“充值金額”相當於商品,“充值”相當於購物,“訂單”跑不掉,“充值話費”類比物流。各種電商該有的“邊界問題”幾乎都要考慮,規模雖小,但五臟都得要有。至於“五臟”有哪些,這得根據業務的邊界範圍去劃分“業務領域”了,先來根據自己的經驗嘗試一把:
這種業務劃分方式多少跟電商系統有點類似,直接呈現的是業務模型。根據“微服務”思維,每個領域都是一個獨立的服務個體單元,每個服務“對象”又有自己的“屬性”和“行為”:
每個服務的屬性有“服務標識”、“服務名稱”等,當然服務有自身的各種行為(更多以API體現),各種系統外部動作都是通過服務之間的“合作”來完成:
用戶查看充值金額:接查詢商品服務(“充值10元到賬12元”,“充值100元到賬106元”,......);
用戶發起話費充值:訂單服務接收請求→訂單服務查詢商品信息(商品ID)→訂單服務向支付服務下支付訂單→訂單服務向充值服務下充值訂單→訂單服務自身下商品訂單;
用戶支付:支付網關(外圍)接受請求→回調支付服務通知支付結果→支付服務更新支付訂單狀態→支付服務向充值服務發起充值→充值服務向充值網關(外圍)發起充值並更改充值訂單狀態;
訂單對賬:定時支付網關對賬、定時充值訂單對賬:
從以上流程可以看出,每個服務都有自己專註的“職能”,每個應用業務流程有需要1或N個服務的交互才能完成,每個服務都有自己獨立的“數據源”,互不幹擾。由於系統的初期規模預期比較小,可能每天就那麽數百筆訂單甚至可能更少,如果我們每個服務都需要“物理隔離”,未免有點大題小做。因此,項目初期我們按“單體”模式實施:
所謂的“單體”,即把所有服務代碼結合一個“項目”打包發布,也就是一個“普通”的項目並且共用一個數據庫,但每個服務的表名都有服務的標識(約定),例如商品服務的相關表名以“KW_GOODS_XXX”命名,訂單服務的相關表名以“KW_ORDER_XXX”命名,支付服務的相關表名以“KW_PAYMENT_XXX”命名,充值服務的相關表名以“KW_RECHARGE_XXX”命名,對賬服務的相關表名以“KW_ACCOUNT_XXX”命名,服務之間決不能跨越服務操作數據庫表,必須按照“業務流程設計”調用,所以“單體”只是體現在物理實施層面,邏輯層面始終保持著“微服務”的分布式特性,保留了各種不用修改一行代碼即可靈活擴展的可能性:
可能有人會問,“單體”模式的服務調用怎麽調?“分布式”模式又是怎麽調?怎麽確保擴展時服務代碼調用層面的不變?用的是什麽技術?這篇隨筆就先不談太多的技術,服務的調用過程我大概通過偽代碼圖術一下:
服務之間協作的“透明化”關鍵在於把“微服務”靈活特性所導致的“變化”打包封裝起來,就是以上偽代碼的DiscoveryClient調用代理。DiscoveryClient在技術框架內維護了所有服務的信息(Service Data Cache Container),而服務信息的加載方式是服務解耦的關鍵所在。首先,框架通過本地掃描的方式把所有本地服務信息掃描並加載至“服務容器”(本地掃描LocalService)。其次,框架會檢測本地的“服務信息”配置文件並加載至容器(靜態解析)。最後,框架會根據配置前兩步所加載的服務信息判斷是否存在“發現中心服務”並動態地周期性向“發現中心”更新服務信息(動態解析)。因此,無論是單體應用部署還是分布式應用部署,對服務調用是透明的,保留了整個系統的靈活擴展性。到這裏,整個系統的設計基本完,完整的系統架構圖如下所示:
以上系統在無任何優惠的正常運行下,確實只能算得上小規模,一臺服務器的單體部署模式足以支撐,但在每月會員日所推出“充100元送10元”商品的時候,單體應用就顯得有點力力不從心了,商品服務訪問量的增加(看得人多了)已經影響到了其它服務的穩定性,並且考慮對賬的穩定性(以免充值不到賬引起投訴),決定把商品服務和對賬服務獨立(進程)部署。通過加入網關(Nginx)進行服務分發(服務解耦):
在運營過程中,公司為了“流量經營”,不惜下血本推出“充100元送50元”的商品,系統再一次受到嚴峻的考驗,為了業務質量,不得不把所有服務獨立(進程)部署,增加支撐力度:
不知道是不是老板喝多了還是運營短路了,竟然提個“充值100元送100元”的商品,並明天上線,系統峰值支撐並發量的預測已經超出了我的想象力,我能做的只有這樣了:
系統從業務規模來看確實存在大小之分,但從設計思想層面,系統是沒有大小之分。以上“戲路”都是以服務為單元進行靈活擴展,其實業務的最小力度是服務的具體行為—API,每個API都是服務的一個獨立行為,例如查詢、變更等,完全符合“命令查詢職責分離(CQRS)模式”的設計,按服務這種API粒度進行橫向分解同樣可行,例如“支付服務”存在支付訂單查詢API、支付訂單下單API、支付結果通知接收API,我們可以通過讀寫特性把查詢API和變更類API進行分離,同樣可以以面向“消費對象”的角度進行分解:
如果某些業務存在服務鏈復雜的話(例如商品訂單),還可以自定義“編排服務”解耦“基礎服務”的復雜度:
學習總結
如果細心點可以從以上案例發現,我的整個項目開發過程跟傳統的可能會有點區別,什麽區別呢?這裏沒有突出太多的實體對象設計或者表結構設計,更沒有突出所謂的“三層結構”設計,而是直接從業務角度觸發劃分“業務對象”,而我們的服務呈現的是根據業務領域劃分的“對象”描述,與傳統按“數據實體”劃分的設計模式還是有一定的區別,從需求設計到軟件設計和開發都是“業務模型”最原始和最直觀的呈現,保證了業務準確性並減少了變更的風險成本,同還大大地降低了項目的溝通成本。這種思想有一個更加專業的術語叫“領域驅動設計”。我深知自己距離所謂的“微服務”或者“領域驅動設計”還有一大段距離,並且以上案例還可能存在諸多的細節問題,但這種類似的思想確實是我自身從業務中摸爬滾打並逐步思考和沈澱而形成的設計習慣。不是別人說什麽就做什麽,而是通過實踐去思考和領悟並向真正的源頭的“大師們”學習。
小型系統如何“微服務”開發