導讀mysql
微服務已經成爲過去幾年軟件架構設計的「事實標準」,大多數企業在推進內部數字化轉型的過程當中,服務軟件系統開始由單一或者SOA服務向微服務轉型。那麼轉型過程須要遵循哪些原則呢?本文結合過往博雲微服務落地實踐經驗,分享微服務落地實踐的過程當中思考。nginx
目前當技術人員說起微服務的時候,首先想到的是Spring Cloud、Dubbo等實現服務的技術框架。這在咱們採用微服務的初期階段是最早考慮的因素。但是隨着服務化的進行,咱們並無享受到由框架的便利性與快捷性所帶來的業務日新月異的成就感。偏偏相反,過多的服務化以及服務間冗餘且多元化通訊機制反而加劇了業務處理的負擔。這必然不是咱們想要的微服務,倒是大多數企業在執行的微服務。git
所以咱們開始從新審視整個行業,審視微服務的發展歷程。與過往不一樣的是:前期階段,咱們把更多的精力投入到業務上,而必定程度上「忽略」技術,由於此時咱們創建的信念是不管何種形式的「服務形態」必定是爲業務服務的。web
當咱們站在全局的角度,觀看整理後的服務,發現了一個及其優美的圖形化結構,各個節點的邊界清晰,職責分明;節點間的鏈路暢通,協議規整。這時咱們知道咱們終於走在了正確的道路上。redis
咱們遵循的原則spring
當通過必定時間的掙扎之後,咱們以爲微服務的關注點不在於技術自己,但並不意味着不關注技術。在反思過程當中,咱們認爲微服務實踐中有兩個原則不能變:服務必定是圍繞業務的,服務的交互是標準的。咱們把原則分爲兩個階段:初期階段,實踐階段。sql
初期階段數據庫
初期階段,遵循第一條原則,服務必定是圍繞業務的。微服務初期階段,重要的是業務梳理,而不是花費大量時間在RPC、Service Discovery、 Circuit Breaker這些概念或者Eureka,Docker,Gateway,Dubbo等技術框架的調研上,此時咱們重心關注服務的邊界與職責劃分。編程
這是遵循的兩條原則:一、保證單一業務服務高效聚合;二、下降服務間的相互調用(此舉是避免陷入大量分佈式業務的處理)。這樣的原則下,DDD爲咱們提供了幫助,也依據業務自己的特性實現了服務初期階段的整理。同時咱們發現就算藉助DDD的指導,在不一樣的業務應用中,各個服務也有不一樣的聚合形態和調用方式。所以咱們以爲微服務自己沒有一成不變的模式,一切都是圍繞業務動態變化的。合理性也僅僅體如今必定階段的時間範圍以內。json
實踐階段
當業務建模完成,咱們可以清晰的知道各個業務的職責以及與其餘業務的關聯關係,從理論層面咱們完成了業務微服務建模。此時咱們開始着手服務的落地實踐,在落地實踐階段咱們更多關注點一樣不在於技術框架,而是在於技術框架的內涵-即服務交互標準。
此時咱們遵循了第二條原則:服務的交互是標準的。所謂服務交互標準從三個層面解讀:協議標準,框架標準,接口標準。
協議標準
目前網絡應用的協議比較複雜,咱們但願選取可以符合業務場景的協議做爲通訊標準。所以咱們考慮了統一的認證鑑權協議、加密解密協議、內部接口交互協議,外圍接口服務協議等,各個協議各司其職,用來支撐服務通訊的標準化。協議標準不只僅爲平臺自身服務,同時與其餘業務單元進行通訊時,只須要遵循協議標準,就能夠實現業務單元之間的快速聯動。
框架標準
爲了支撐業務,咱們沒有依賴任何的自動化代碼生成框架,而是根據咱們的協議支持狀況,選擇最小的服務運行框架,來構建統一的業務單元支撐框架。這裏須要說明的一點,框架標準須要考慮業務特性,協議特性,不能一律而論,所以它的有效性也許只在當前構建的業務平臺自己。構建標準框架的好處是針對應用內的全部業務單元能夠快速複製,簡化由於各自開發框架不一樣致使聯調階段出現問題。
接口標準
接口分兩種:業務內部接口和業務服務接口。不管哪一種接口一樣遵循標準化原則。
業務內部接口的核心在於壓縮協議,加快業務的處理流程,所以能夠採用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
統一日誌攔截
基於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要寫不一樣的數據解析方式,從而形成大量重複的工做。
遺留問題
固然咱們落地過程的選擇也不必定盡善盡美,也有不少隨着業務處理能力的增強而在以前沒有考慮到的問題,例如:
這些問題咱們在後續不斷深刻地理解和探索中會找到相應的解決方案,你們能夠在後續繼續關注咱們的微服務解決方案。