前言
CNCF 與 Cloud Native 這兩個技術詞彙最近頻頻走進了程序員的視野,一切和他能搭上邊的軟件意味着標準、開放、時尚,也更能俘獲技術哥哥們的心;這篇文章不想去帶你們重溫這個詞彙後面的軟件體系,筆者以爲單憑用到了這些開源軟件,不等於咱們本身的軟件就已是 Cloud Native,在使用啞鈴和成爲肌肉男之間還隔着科學使用和自律鍛鍊兩道工序;在此,筆者想根跟你們聊聊讓咱們的應用真正變得 Cloud Native 時的理論依據:微服務的十二要素。這篇文章也是先從做者自身項目的角度(一個基於 EDAS 的微服務架構),來闡述對這十二條要素的前兩條 —— 倉庫(Code Base)與依賴(Dependency)的理解java
Code Base 的原文釋義是:"一份基準代碼,多份部署,基準代碼和應用之間老是保持一一對應的關係;不一樣環境中的相同應用,應該來源於同一份代碼"。個人理解有兩個:git
一個應用,產生自同一個倉庫。
一個倉庫,只產生一個應用。
爲何推演出這麼兩個結論呢?讓咱們先看一個實際的項目。程序員
爲何是一個應用?
給你們舉一個一個倉庫包含多個應用的反例,筆者本身的一個項目是一個的微服務的架構,和大部分的微服務架構同樣,一開始是由一個單體的應用拆解而來,拆解以後,大體簡化成四個服務:微服務網關(Gateway),兩個後臺服務(UserService, OrderService),後臺管理控制檯服務(Admin),簡單的架構示意圖以下:web
在拆分的過程一開始爲了項目上線的減小風險,將拆分以後的應用都放在了一個 GIT 倉庫中進行管理,同時也共用了同一個庫。重構以後倉庫的目錄以下:json
~/workspace/java/app/ $ tree -L 2
.
├── README.md
├── service-api # 通用的 API 接口定義
│ ├── userservice-api # 服務 UserService 的聲明
│ ├── orderservice-api # 服務 OrderService 的聲明
│ ├── rpc-api # 遠程服務調用相關的接口聲明
│ ├── common-api # UserService 與 OrderService 都依賴的聲明
| .....
├── service-impl # 對應 API 的相關具體業務實現
│ ├── userservice-impl
│ ├── orderservice-impl
│ ├── common-impl
| .....
├── web-app # Web 應用工程
│ ├── admin
│ ├── userservice
│ ├── orderservice
│ ├── gateway
一開始這些服務之間的發佈和改動彼此都不受影響,這一過程持續了大約兩個迭代,隨着迭代的不斷進行和新人的加入,後來咱們線上發現一個很奇怪的現象,每次用戶進入刷新訂單的地址列表的時候,會伴隨這一次用戶 Token 的刷新而致使用戶被踢出,線上的排查過程在 EDAS 的分佈式鏈路跟蹤系統 EagleEye 的幫助下,立刻就定位到了出問題的代碼:api
// User Service 中
public class User {架構
public void refresh() { // 刷新登陸 token }
}app
// Order Service 中
public class OrderUser extends User {分佈式
// 函數少了一個字母,致使 refresh 調用了父類的 refresh public void refesh() { // 刷新地址列表 }
}
這個故障,我先邀請你們一塊兒思考一下幾個問題:ide
從編碼角度,如何避免上述重寫的方法由於名字誤寫形成故障?
從設計角度,OrderUser 和 User,是不是繼承關係?
這個問題的根因是什麼?
以上的幾個問題中,第一個問題的答案,不少同窗都知道,就是使用 Java 自帶的 Annotation @Override,他會自動強制去檢查所修飾的方法簽名在父子類中是否一致。第二個問題,須要從領域邊界來講,這是一個典型的邊界劃分的問題,即:訂單中的用戶,和會員登陸中的用戶,是否是相同的「用戶」?會員中的用戶,其實只須要關心用戶名密碼,其餘都是這個用戶的屬性;而訂單中的用戶,最重要的確定是聯繫方式,即一個聯繫方式,肯定一我的。雖然他們都叫作用戶,可是在彼此的上下文中,確定是不同的概念。因此這裏的 OrderUser 和 User 是不能用繼承關係的,由於他們就不是一個 "IS A" 的關係。
__倉庫共享__,加上沒有多加思考的模型,致使依賴混亂;若是兩個 User 對象之間代碼上能作到隔離,不是那麼輕易的產生「關係」,這一切或許能夠避免。
爲何是一個倉庫?
嚴格意義上說,一個應用的全部代碼都確定來源於不一樣的倉庫?咱們所依賴的三方庫如(fastjson, edas-sdk 等)確定是來源於其餘的倉庫;這些類庫是有確切的名稱和版本號,且已經構建好的"製品",這裏所說的一個倉庫,是指源碼級別的「在製品」。可能在不少的項目中不會存在這樣的狀況,以 GIT 爲例,他通常發生在 submodule 爲組織結構的工程中,場景通常是啥呢?在咱們這個工程中確實是有一個這樣的例子:
爲了解掉第一個問題,咱們決定拆倉庫,倉庫的粒度按照應用粒度分,同時把 common 相關的都拆到一個叫作 common 倉庫中去;業務服務都好說,這裏特殊處理的是 admin 應用,admin 是一個後臺管理應用,變化頻度特別大,須要依賴 UserService 和 OrderService 一大堆的接口。關於和其餘倉庫接口依賴的處理,這裏除了常見的 Maven 依賴方式以外,還有另一個解決方案就是 git submodule,關於兩個方案的對比,我簡單羅列在了下表之中:
優勢 缺點
Maven 依賴 可指定已固化的版本進行依賴 必須發佈成二方包
Submodule 依賴 靈活、可直接共享代碼庫 變動不可控
我以爲若是這個項目組只有一兩我的的時候,不會帶來協做的問題;上面的方案隨便哪個都是不須要花太多時間作特殊討論,挑本身最熟悉最拿手的方案確定不會有錯,所謂小團隊靠技術嗎,說的就是這麼個道理;咱們當時是一個小團隊,同時團隊中也有同窗對 submodule 處理過相似的狀況,因此方案的選擇上就很天然了。
後來隨着時間的推移,團隊慢慢變大,就發現須要制定一些流程和和規範來約束一些行爲,以此保障團隊的協做關係的時候;這時候發現以前靠一己之力打拼下來的地盤在多人寫做下變得脆弱不堪,尤爲是另一個 submodule 變成一個團隊進行維護的時候,submodule 的版本管理幾乎不可預期,並且他的接口變更和改動是徹底不會理會被依賴方的感覺的,由於他也不知道是否被依賴;長此以往,你就會明白什麼叫作你的項目被__腐化__了。簡單理解__腐化__這個詞就是,你已經開始懼怕你所作的一切改動,由於你不知道你的改動是否會引來額外的麻煩。從這個角度也能夠去理解爲何一門語言設計出來爲何要有 __private__、__public__這些表示範圍的修飾詞。正由於有這些詞的存在,才讓你的業務代碼的高內聚成爲的有可能,小到設計一個方法一個類、再引伸到一個接口一個服務、再到一個系統一個倉庫,這個原則始終不變。
上述問題帶來的解法很簡單,就是變成顯示依賴的關係,所謂顯示依賴是指的兩個依賴之間是肯定的。什麼是肯定的?肯定 == No Supprise !對,無論何時,線上仍是線下,我依賴你測試環境的接口返回是一個整數,到了線上,返回的也必須是一個整數、不能變成浮點數。而讓肯定性變得可行的,不是君子協定;只能是一個版本依賴工具。好比說 Java 中的 Maven 正式的版本依賴。
結語職責內聚、依賴肯定,是咱們的應用變得真正 Cloud Native 的前提。沒有了這些基本的內功,懂的開源軟件再多、對微服務棧再熟悉,也會有各類意想不到的事情出來,試想一下,若是應用的職責處處分散,那到時候擴容到底擴誰呢?若是依賴方變得及其不肯定,誰又來爲每次發版的不肯定的成本買單?Be Cloud Native,請從應用代碼託管的住所開始。