1. 程式人生 > 實用技巧 >多雲架構落地設計和實施方案

多雲架構落地設計和實施方案

“不要把雞蛋放在同一個籃子裡”是一條知名的商業準則,在雲平臺選擇上,很多公司也遵循這樣的準則。基於多雲平臺構築“業務中臺”並不是一件簡單的事情,需要構建一種快速繼承、可持續迭代的路徑,幫助整體方案落地。本文以實際專案案例為例,分析專案的架構設計、實施步驟,以及多雲架構面臨的挑戰和機遇。
總體思路
不同雲廠商提供的雲服務不盡相同,相同的雲服務在功能、效能上也會有或多或少的差異。越是深度使用某個雲廠商的雲服務,越是難於遷移到其他雲廠商。選擇自己構建雲服務,則技術門檻,維護成本很高。確定多雲架構以後,首先需要在技術棧的選型上做好折中。一個基本的原則是通過業務架構的靈活性,去適配不同的雲廠商,儘可能的使用雲廠商提供的優秀特性,提升運行於該雲平臺的業務系統的可靠性,提升整體業務的競爭力。

上面的思路和一些客戶常見的思路有顯著差別。有些客戶選擇採用開源軟體,搭建自己的PaaS平臺;有些客戶則完全採用雲廠商的技術棧,開發兩套業務系統。這兩種方式是兩個極端,前者開發和運維難度高,往往由於技術風險評估不足,專案無法如期交付,或者產品競爭力太弱,沒有云廠商提供的服務好。後者則需要維護兩套系統,程式碼重複度高,還會被雲廠商完全繫結,失去談判的籌碼,業務發展靈活性降低。還有些客戶期望雲廠商提供足夠相容性的框架支援,在不改造現有業務系統的邏輯的情況下實現多雲部署,雲廠商這方面的努力通常由於客戶系統的複雜性和多樣性得不到落地。

開發框架選擇和架構設計
開發框架設計是多雲架構的核心,也是抽象程度最高的部分。華為雲主推ServiceComb作為微服務框架,阿里雲主推HSF、Dubbo作為微服務框架,其他國外的雲廠商,多數選擇Spring Cloud作為微服務框架,另外還有其他不同的語言和框架選擇。雖然mesher等技術為多語言協同工作提供了良好的支援,但是為了最大限度的利用框架特性,幫助快速構建穩定可靠的業務系統,選擇一個微服務開發框架仍然是必不可少的。

基於總體思路,多雲架構期望在華為雲上使用ServiceComb執行時,在阿里雲上使用HSF執行時,並且支援Spring Cloud執行時。完成這個目標,首先需要對微服務執行框架的執行時和主要組成部分有所瞭解。對於多數中臺系統,對於框架執行時的依賴,一般都是RPC框架,以及基於RPC框架做的服務治理能力,包括服務註冊發現、熔斷容錯、限流等機制。將業務邏輯核心程式碼,與微服務框架能力進行解耦,是設計的第一步。

上面的圖形展現了基本的邏輯架構。

業務核心:技術選型上使用Spring、Spring Boot。ServiceComb、HSF和Spring Cloud等微服務框架的技術底座,都可以基於Spring、 Spring Boot技術棧來構建。

在邏輯架構下,需要將微服務程式碼進行分層,包含下面三個主要目錄:
microservice-api:定義微服務的介面。該目錄包含介面定義(interface)、資料結構定義(models)。為了支援不同的微服務框架,對於介面定義和資料結構定義會有一定的要求。
microservice-service:業務邏輯實現程式碼。
microservice-endpoint-servicecomb:釋出為ServiceComb的微服務專案。
microservice-endpoint-hsf:釋出為HSF的微服務專案。
microservice-endpoint-springcloud:釋出為Spring Cloud的微服務專案。

這個程式碼分層實施的核心關鍵是api設計,以及業務邏輯實現和服務釋出解耦。api設計需要滿足不同的微服務框架的設計要求。這裡涉及到RPC編解碼的基礎。RPC編解碼通常分為語言無關(跨平臺)和語言相關(不跨平臺)。比如HSF、Dubbo的預設編解碼是語言有關的,只能夠支援JAVA程式之間的通訊,ServiceComb預設採用Jackson編解碼,或者protobuffer編解碼,這兩種方式都基於Open API 2.0進行定義,可以做到語言無關,Spring Cloud則相對複雜一些,它是一種混合型的編碼格式,可以通過靈活的序列化定製,滿足多樣性需要,也可以嚴格遵循Jackson和Open API標準。通常語言無關的編解碼可以完全包含語言無關的編解碼要求,因此api定義的時候,需要做到語言無關,才能夠保證api能夠在不同的微服務開發框架下得到最好的實現。基於本文是描述工程實踐,不詳細探討關於跨平臺設計的原理,下面列出一些介面設計的準則:
使用簡單的型別(比如Integer, String, Boolean等)定義引數或者返回值。或者使用包含簡單型別的,符合Java
Bean規範的POJO Bean定義引數或者返回值。
儘可能不使用interface, abstract class, 存在多個實現的基類,模板類作為引數或者返回值。 不使用執行環境強相關的物件作為介面引數或者返回值。比如HttpServletRequest,RpcContext,InvocationContext,ResonseEntity等。

上面的原則從使用者的視角來看,是非常容易理解的。介面語義清晰,沒有歧義,直接通過介面定義就能夠理解介面的引數個數以及如何傳遞,不需要提供額外的文件或者檢視原始碼。有利於通過介面定義生成文件、swagger定義。

在實際專案中,開發者需要理解microservice-api是微服務之間的介面定義,介面設計需要考慮資料的序列化和反序列化。這個不同於內部介面設計。為了降低業務實現邏輯的重複度,增強內聚性,內部介面設計會更多的使用抽象、繼承進行邏輯封裝。內部介面的資料結構,還會包含一些額外的控制邏輯。比如資料庫訪問層的資料結構,提供懶載入等機制,當訪問到getter方法的時候,實際上呼叫的是代理物件的getter方法。因此,需要有一些資料結構轉換邏輯,將內部的資料結構轉換為外部介面的資料結構,以保持服務之間介面和內部介面的界限清晰,防止將內部資料結構作為引數或者返回值,導致內部資訊洩露,造成不可預期的處理結果。

api示例

interface LoginService {
SessionInfo login(String username, String password);
}

public class SessionInfo {
private String sessionId;
private String username;
}

service示例

@Service

@Primary

public class LoginServiceImpl implements LoginService {

public SessionInfo login(String username, String password) {

 // do login

}
}

ServiceComb Endpoint示例

服務端:

@RpcSchema(schemaId = “LoginServiceEndpoint”)
public class LoginServiceEndpoint implements LoginService {
@Autowired
private LoginService service;

public SessionInfo login(String username, String password) {
return service.login(username, password);
}
}

客戶端:

@Bean
public LoginService getLoginService() {
return Invoker.createProxy(SERVICE_NAME, "LoginServiceEndpoint", LoginService.class);
}

或者
@RpcReference(microserviceName=SERVICE_NAME, schemaId=”LoginServiceEndpoint”)
private LoginService loginService;

HSF Endpoint示例

服務端:

@HSFProvider(serviceInterface = LoginService.class,serviceVersion = "1.0.0")
public class LoginServiceEndpoint implements LoginService {
@Autowired
private LoginService service;

public SessionInfo login(String username, String password) {
return service.login(username, password);
}
}

客戶端:
@HSFConsumer
private LoginService loginService;

從上面的程式碼示例看,Endpoint層需要做到儘可能邏輯簡單,實現邏輯全部交給Service層,只包含介面宣告,或者包含必要的資料結構轉換邏輯。上面的方式演示了RPC的介面宣告,還可以加上REST標籤(Spring MVC,或者JAX RS),來支援REST介面。
遺留系統的改造策略

大部分公司都會存在現有系統運行於某一個雲上,把現有系統推倒重來,進行全新設計,通常不是一個好主意。遺留系統的改造需要遵循持續迭代,繼承性改造的思路。

以阿里雲系統改造為華為雲系統為例,將原來基於HSF開發的微服務應用改造為基於ServcieComb開發的微服務應用。

按照前面的總體思路,首先需要將原來專案強依賴於HSF的程式碼部分分離出來,建立下面的目錄結構:
microservice-api:定義微服務的介面。該目錄包含介面定義(interface)、資料結構定義(models)。為了支援不同的微服務框架,對於介面定義和資料結構定義會有一定的要求。
microservice-service:業務邏輯實現程式碼。
microservice-endpoint-hsf:釋出為HSF的微服務專案。

這個過程相對而言是簡單的,不涉及任何業務邏輯的調整,只是程式碼目錄結構的變化和POM依賴關係的調整。調整完成後,可以對現有功能進行簡單自動化驗證,保證專案自動化測試用例能夠通過。調整後的專案功能和遺留系統是一致的,擁有一個始終無損的執行系統,對於功能比較、問題發現都是非常有幫助的。

專案調整後,就可以增加華為雲的專案:
microservice-endpoint-servicecomb:釋出為ServiceComb的微服務專案。

這個專案可以複製microservice-endpoint-hsf,將POM依賴改為ServiceComb的內容,並將釋出的介面(Endpoint)調整為ServiceComb的釋出方式。

專案調整後,就可以一份程式碼構建出兩個可執行jar包,兩個jar包分別在華為雲和阿里雲部署。

這個過程的工作量需要通過原來程式碼本身的複雜度、api介面的規範性、程式碼規模等進行評估。如果原來程式碼結構複雜,api定義不規範,工作量會顯著增加。對於良好組織的程式碼,這個過程則會非常容易。實際改造的一些專案,有些一天時間即可完成,有些則花費了一、兩個月時間。獲取到產品的業務結構、程式碼規模和通過簡單的識別現有程式碼的技術棧和開發方式,能夠幫助有效的評估工作量。

遺留系統改造的核心工作在於microservice-api中介面定義的梳理。由於HSF不是一個跨語言的開發框架,因此在介面定義裡面使用的資料結構ServiceComb可能存在不支援的情況。採用一個支援跨語言的框架的介面定義作為基準(一般的,可以將介面定義通過IDL、WSDL或者Open API描述的介面定義,比如gRPC、WebService、ServiceComb,),其他框架都實現這個介面,是微服務架構適配的核心關鍵。
資料庫適配

業務系統都會使用資料庫。各個雲廠商支援的資料庫形式多樣,有MySQL、Postgre、Oracle等,此外還有分散式資料庫,以及分庫分表等特性,資料倉庫支援等。雖然在連線池管理、事務管理、ORM框架等方面有大量的開源開發框架,比如dbcp、spring transaction、MyBatis等,它們仍然沒法覆蓋所有的應用場景。適配多雲架構對於業務程式碼開發有如下建議:

儘可能使用符合ANSI SQL Standard的SQL語句。比如MySQL在大小寫、Column引用、Limit、Group by語句等方面都有自己的特殊用法。為了更好的適配多雲資料庫,應該避免使用特殊用法,除非對於業務價值具備明顯的價值,而且沒有對等的解決方案。

選用一個被廣泛採用的ORM框架。比如MyBatis、JPA等。這些框架被廣泛支援,對於多資料庫支援得到比較充分的驗證。在使用資料庫有差異的地方,提供了非常良好的擴充套件機制。比如使用MyBatis,可以使用DatabaseProvider介面,來遮蔽語法差異。在使用JDBC或者JDBCTempate拼接URL的地方,則可以通過介面的不同實現,返回不同的SQL語句。

limit #{stratRow},#{rowCount}


limit #{rowCount} offset #{stratRow}

分散式資料庫、分庫分表、分析型資料庫對於業務開發存在不同的限制。比如對於唯一索引使用的限制、對於分庫分表鍵的修改限制等。對於這些場景,儘可能符合這些限制,調整業務實現方式,避免為了滿足某個不重要的場景需要,陷入一些分散式場景無法解決的問題當中去,而無法適配其他雲廠商的資料庫。

快取適配
各個廠商均支援Redis作為快取,但是在Redis發展路徑上,有不同的分支。一個分支是Proxy叢集模式,一個分支是Redis的原生叢集方式。Redis提供的客戶端API也存在兩套,一個是Jedis,一個是JedisCluster,兩套支援的命令集合不盡相同。比如Proxy叢集模式能夠非常好的支援所有的Jedis命令,而Redis的原生叢集方式只支援JedisCluster命令。很多客戶常用的pipeline指令,在Redis原生叢集方式下不支援。

Proxy叢集能夠更好的遮蔽底層服務的差異,在沒有特殊需要的情況下,建議使用者使用Proxy叢集模式,雲廠商需要通過Proxy叢集模式提供對於Jedis不同指令的支援。使用者也可以選擇Redis的原生叢集,這個在不同的雲產生也都提供了支援,使用者可以在業務場景上避免使用原生叢集不支援的命令,這樣就可以在多雲環境上部署。

總結起來,快取的多雲支援的最佳實踐:

及時升級Client API版本,使用比較新的Client API,並且只使用JedisCluster提供的指令集合。

訊息中介軟體適配
相對於資料庫和快取,訊息中介軟體的適配的標準性更弱一點。雖然早期JAVA提出了JMS等標準,但是目前主流的訊息中介軟體都沒有提供JMS介面的實現。阿里的RocketMQ、華為基於Kafka的DMS的介面均不一致。而且訊息中介軟體的服務質量並不一樣,比如在訊息有序投遞、重複投遞等方面是通過訊息中介軟體配置提供的,而不受客戶端介面控制。有些客戶的業務邏輯依賴於特定訊息中介軟體的機制,因此需要對訊息中介軟體使用的介面、介面行為進行抽象,分析其他雲廠商的中介軟體能否提供對應的介面實現並滿足對應的服務質量要求,有時候需要業務程式碼做一些補償,以彌補不同中介軟體服務質量的差異。

其他中介軟體適配
其他中介軟體包括日誌伺服器、定時任務服務、物件儲存服務等等。適配的總體思路一致,這裡不詳細描述裡面的細節。

多雲架構的挑戰和建議
和做協議標準一樣,多雲架構除了給客戶帶來商業上的靈活性,還會促進業務系統軟體架構的改善,提升整體的軟體質量。因為多雲架構需要對使用的技術點進行權衡,技術選型上更多采用相對中立和標準的方案,這些方案被廣泛驗證,相對於使用一些私有特性來說,更加穩定,同時可以促進專案開發人員之間的技術交流和能力繼承。在給客戶做多雲架構落地的實踐中,通過架構交流,幫助客戶梳理了整體的架構演進方向,還發現了很多歷史問題,對於客戶的程式碼質量提升起到了很大作用。

多雲架構也面臨特別多的挑戰。首先是短期內企業維護成本的增加和技術成本的增加,需要投入專家解決前期的架構適配問題,為系統持續演進搭好框架。多雲系統執行,還會增加業務資料同步,不同雲上系統如何進行協同的問題。只有當客戶多雲系統相對獨立,沒有資料共享和業務互動的場景才比較簡單。多雲系統還會影響到客戶的交付效率,不同雲的持續交付方式存在較大的差異,運維人員的體驗不同,會降低日常的效率。

因此,多雲架構更多的是準備“備胎”,客戶的主要業務還是以單一雲廠商提供。多雲架構給客戶對比不同雲廠商的服務質量提供了非常準確的參考,客戶能夠更加準確的選擇優質的供應商。

微服務框架層的抽象是做多雲架構最難抽象的部分,也是多數開發者最難把握的一層。ServiceComb 微服務開發框架作為參考介面規範,可以非常好的適配到HSF、Spring Cloud等框架。在介面定義的時候,可以採用它作為基線,先測試ServiceComb,再測試HSF和Spring Cloud等適配,對於新介面開發、歷史系統遷移都是一個非常好的中間產品。