導讀
微服務已經成爲過去幾年軟件架構設計的“事實標準”,大多數企業在推動內部數字化轉型的過程中,服務軟件系統開始由單一或者SOA服務向微服務轉型。那麼轉型過程需要遵循哪些原則呢?本文結合過往博雲微服務落地實踐經驗,分享微服務落地實踐的過程中思考。
目前當技術人員提及微服務的時候,首先想到的是Spring Cloud、Dubbo等實現服務的技術框架。這在我們採用微服務的初期階段是最先考慮的因素。可是隨着服務化的進行,我們並沒有享受到由框架的便利性與快捷性所帶來的業務突飛猛進的成就感。恰恰相反,過多的服務化以及服務間冗餘且多元化通信機制反而加重了業務處理的負擔。這必然不是我們想要的微服務,卻是大多數企業在執行的微服務。
因此我們開始重新審視整個行業,審視微服務的發展歷程。與過往不同的是:前期階段,我們把更多的精力投入到業務上,而一定程度上“忽略”技術,因爲此時我們建立的信念是無論何種形式的“服務形態”一定是爲業務服務的。
當我們站在全局的角度,觀看整理後的服務,發現了一個及其優美的圖形化結構,各個節點的邊界清晰,職責分明;節點間的鏈路暢通,協議規整。這時我們知道我們終於走在了正確的道路上。
我們遵循的原則
當經過一定時間的掙扎以後,我們覺得微服務的關注點不在於技術本身,但並不意味着不關注技術。在反思過程中,我們認爲微服務實踐中有兩個原則不能變:服務一定是圍繞業務的,服務的交互是標準的。我們把原則分爲兩個階段:初期階段,實踐階段。
初期階段
初期階段,遵循第一條原則,服務一定是圍繞業務的。微服務初期階段,重要的是業務梳理,而不是花費大量時間在RPC、Service Discovery、 Circuit Breaker這些概念或者Eureka,Docker,Gateway,Dubbo等技術框架的調研上,此時我們重心關注服務的邊界與職責劃分。
這是遵循的兩條原則:1、保證單一業務服務高效聚合;2、降低服務間的相互調用(此舉是避免陷入大量分佈式業務的處理)。這樣的原則下,DDD爲我們提供了幫助,也依據業務本身的特性實現了服務初期階段的整理。同時我們發現就算藉助DDD的指導,在不同的業務應用中,各個服務也有不同的聚合形態和調用方式。因此我們覺得微服務本身沒有一成不變的模式,一切都是圍繞業務動態變化的。合理性也僅僅體現在一定階段的時間範圍之內。
實踐階段
當業務建模完成,我們能夠清晰的知道各個業務的職責以及與其他業務的關聯關係,從理論層面我們完成了業務微服務建模。此時我們開始着手服務的落地實踐,在落地實踐階段我們更多關注點同樣不在於技術框架,而是在於技術框架的內涵-即服務交互標準。
此時我們遵循了第二條原則:服務的交互是標準的。所謂服務交互標準從三個層面解讀:協議標準,框架標準,接口標準。
協議標準
目前網絡應用的協議比較複雜,我們希望選取能夠符合業務場景的協議作爲通信標準。因此我們考慮了統一的認證鑑權協議、加密解密協議、內部接口交互協議,外圍接口服務協議等,各個協議各司其職,用來支撐服務通信的標準化。協議標準不僅僅爲平臺自身服務,同時與其他業務單元進行通信時,只需要遵循協議標準,就可以實現業務單元之間的快速聯動。
框架標準
爲了支撐業務,我們沒有依賴任何的自動化代碼生成框架,而是根據我們的協議支持情況,選擇最小的服務運行框架,來構建統一的業務單元支撐框架。這裏需要說明的一點,框架標準需要考慮業務特性,協議特性,不能一概而論,因此它的有效性也許只在當前構建的業務平臺本身。構建標準框架的好處是針對應用內的所有業務單元可以快速複製,簡化因爲各自開發框架不同導致聯調階段出現問題。
接口標準
接口分兩種:業務內部接口和業務服務接口。無論哪種接口同樣遵循標準化原則。
業務內部接口的核心在於壓縮協議,加快業務的處理流程,因此可以採用RPC等高效率的協議支持的接口模式;業務服務接口的核心在於表明業務攜帶的信息,因此採用restful接口規範更合適一些。接口設計需要涵蓋但不限於標準化的請求方式、統一的參數處理、統一的結果返回、統一的異常處理、統一的日誌處理等。
服務拆分與聚合
前提:服務拆分與聚合在本篇文章中暫時不考慮web的微服務化設計,只說明後端服務的拆分與聚合實踐。
服務拆分與聚合需要遵循的原則:服務一定是圍繞業務的。但事實情況是,在現在追求“開源整合”的背景下,純粹的業務單元在不借助第三方工具的前提下,需要消耗巨大的代價才能實現業務需求,同時也出現不同業務單元對同一個工具的強依賴性。因此在服務拆分與聚合時,我們考慮了兩種形態的實現方式:業務支撐與工具支撐。
業務支撐
業務支撐需要考慮的是業務服務對象和業務內部邏輯。業務服務對象作爲整個業務單元的對外形態,通過命名能夠清晰的表達其涵蓋的業務範圍;業務內部邏輯需要對業務單元進行細粒度的拆分,類似一個實體類可以包括多個其他相關聯的實體對象(當然如果服務拆分的足夠細化,也可以把內部邏輯作爲獨立的業務單元,但是這樣會加重業務直接的通信負載)。基於業務內部邏輯構建業務服務對象的真實場景。具體的拆分細節可以依賴DDD的實踐方法進行(當然也需要根據業務做相應調整,沒有普世之道)。
工具支撐
工具支撐需要結合業務考慮,分爲兩種:通用性工具和專用性工具。通用性工具旨在爲所有業務單元運行提供統一的支撐平臺,從而減少由於工具維護花費的精力,使得業務開發人員聚焦業務實現,一般通用工具包包括統一日誌處理,統一攔截處理,返回數據統一封裝,異常統一處理等等;專用性工具聚焦某個具體的業務單元,由業務單元自身維護(例如迭代升級)。工具支撐層面不會提供對外restful或者rpc的接口,對外的表現形式爲編譯好的依賴工具包(例如Github的管理接口的封裝)。
服務架構選型
依照執行原則完成服務拆分以後,我們需要考慮的是合適的落地選型。選型方案要考慮的因素有很多:技術背景(尤其是團隊內編程語言的設定),服務支撐工具(註冊中心,網關,服務調用,負載均衡數據庫等),服務運行工具(tomcat,jetty,jboss等),服務部署工具(物理部署,虛擬化,容器等),工具的協議支撐集合(http,rpc,mtqq,idoc等)。但是無論如何選型最終一定要結合團隊開發人員當下的技能支撐,這也是我們選型的核心因素,因爲白盒相對來說始終比黑盒安全,也相對可控。
這裏給出我們的技術棧選型框架(僅限我們熟悉的內容),暫時不涉及技術框架的對比說明。
服務開發框架:springboot,dubbo,grpc,ServiceMesh(基於ServiceMesh的開發服務框架)
分佈式存儲/註冊中心:Zookeeper,Consul,Eureka,Etcd
服務網關:Kong,Openresty,Spring cloud Zuul,Spring cloud gateway
負載均衡:nginx,spring cloud Ribbon,haproxy,Kubernetes service
服務遠程調用:Spring cloud feign
緩存服務:memchace,redis
數據庫:mariadb,mysql
消息服務:RabbitMQ,NATS,Kafka
配置中心:spring cloud config,Apollo,Consul
事件機制:Cloud Event
服務編排:Conductor ,Kubernetes
服務治理:spring cloud,Dubbo,ServiceMesh
基於消息機制的分佈式事務處理(遵循CAP或者BASE理論模型的實現)
業務運行工具:jvm,nginx或者其他可運行環境支撐
開發編譯工具:Jenkins,maven,gitlab
接口文檔:Swagger
部署工具:物理部署(jar包或者可運行的編譯的二進制文件)虛擬化部署(虛擬鏡像模板)容器化部署(Docker)
我們在落地的過程中,根據團隊技術特點開發階段重點選擇了Spring Cloud中涵蓋的技術棧。方便易用,能夠快速入手。運行階段選擇具備服務編排能力的Kubernetes容器化運行環境,並且結合Devops工具鏈能夠快速迭代部署。
服務接口設計
服務接口是對外展現業務邏輯的唯一入口,接口定義的規範與否也是微服務落地的關鍵指標之一,我們在實踐的過程中參考了多個開源項目的接口設計,針對任何一個資源對象,整體分爲幾類場景:資源集合類操作,資源實體操作,異常處理,參數處理,統一數據返回,審計日誌以及其他具體場景。
統一的接口請求與響應標準
其中業務單元絕大多數端口圍繞着資源集合類、資源實體類進行操作,因此我們從restful接口規範出發,結合具體場景,規範了請求方式,請求url,請求參數,請求header,響應header,響應值等信息。
請求參數涵蓋默認語義,包括:Get(獲取信息),Post(創建),Put(全量修改),Patch(部分修改),Delete(刪除)
以Students實體對象的新建爲例,給出請求與響應標準。
URL
URL請求包括三部分:請求方式,統一前綴以及具體url,統一前綴具備一定含義的命名規則,包括api申明,供應商標識,版本說明等必要信息,例如:
Post /api/cloud/v1/students?exist={skip,replace}
請求header
type
aplication/json:用於single和bulk時,用來表示請求數據爲json格式
application/vnd.ms-excel:從excel格式的文件導入創建
Accept
aplication/json:接受json格式的響應數據
Authorization
Oauth2.0的access token(bearer token)
Accept-Language(可選)
可接受的語言,國際化,en-US表示美國英語
請求數據格式+類型
json格式:{items:[]}
請求創建students對象json(表達):
請求(批量)創建student對象列表json(表達)
請求(批量)創建student信息excel文
響應header
Content-Type
aplication/json
Content-Language(可選)
內容語言
Last-Modified
數據最近一次修改的時間戳信息
響應值
Success message:多種類型
Error message:多種類型
Exception:多種類型
統一異常處理
統一異常處理包括狀態碼以及狀態碼涵蓋的異常信息,具體部分定義如下:
-
200/201+success message(含資源數量信息+uri信息):創建成功,適用於數量不多(比如小於500)的創建操作,大於設定的值時進行異步處理,參加返回值202
-
202+success message with status uri:異步處理,返回進度查詢資源uri(/api/vendor/v1/status/{id})
-
400+success+errors(含出錯項index的錯誤列表):批量創建時部分成功,返回成功信息和錯誤信息
-
401+exception{error_code+message}:缺乏認證信息
-
403+exception{error_code+message}:未授權訪問,訪問被拒絕
-
406+exception{ error_code+message}:不支持client要求的格式或語言時返回該信息(Not Acceptable)
-
415+exception{error_code+message}:請求中的文檔格式不支持
-
422+exception{error_code+message}:不能處理的數據,比如json格式錯誤、文件內容項錯誤或會破壞業務規則
-
429+exception{ error_code+message}:太多請求,流控時使用
-
500+exception{error_code+message}:服務器內部錯誤
統一日誌攔截
基於AOP模式攔截所有請求,在請求入站與出站的時候,做統一日誌記錄以及需要的其他非業務處理(例如鑑權)
統一的數據返回標準
我們參考Restful數據返回標準,封裝我們自己的數據返回格式:code,message,body,error,統一的數據返回格式可以在接口層做統一的攔截處理。實現返回數據的標準化。
code:返回狀態碼
message:返回響應結果的語義解釋
body:響應的具體數據信息,包括metada信息,具體響應數據以及請求連接
error:代表返回的錯誤信息
具體的響應格式如下所示:
{
"code": 200,
"message": "獲取學生列表成功",
"body": {
"links": [
{
"rel": "self",
"href": "http://localhost:8080/api/cloud/v1/students?name=test&startDate=2019-01-01&endDate=2019-09-01&style=normal&sort=asc&limit=10&offset=0{&fields}",
"hreflang": null,
"media": null,
"title": null,
"type": null,
"deprecation": null
}
],
"metadata": []
"content": [
{
"id": 1,
"name": "test3",
"status": "running",
"props": "test",
"remark": "test",
"ownerId": 1,
"createrId": 1,
"menderId": 1,
"gmtCreate": "2019-03-11 10:42:15",
"gmtModify": null,
"startDate": null,
"endDate": null,
"links": [
{
"rel": "self",
"href": "http://localhost:8080/api/cloud/v1/students/1?style=normal&fields=",
"hreflang": null,
"media": null,
"title": null,
"type": null,
"deprecation": null
}
]
}
]
}
"errors": {}
}
服務接口的設計一定是圍繞標準化的規則進行的,這樣才能在後期減少因爲接口變動導致不斷出現的前後端聯調問題。因爲在實踐中我們經常遇到格式不統一導致web要寫不同的數據解析方式,從而造成大量重複的工作。
遺留問題
當然我們落地過程的選擇也不一定盡善盡美,也有很多隨着業務處理能力的加強而在之前沒有考慮到的問題,例如:
-
各個服務自身併發數據支撐能力
-
服務交互的內部代碼瓶頸,包括調用鏈路冗餘,響應偏慢等
-
數據庫的併發支撐與性能優化
-
與容器服務集成的參數配置,開發與部署環境的轉變
-
調用鏈路可能出現的迴環問題,交叉的業務單元調用,導致調用鏈路混亂
-
數據的緩存設計,加快業務響應速率
這些問題我們在後續不斷深入地理解和探索中會找到相應的解決方案,大家可以在後續繼續關注我們的微服務解決方案。