架構師訓練營第 1 期 - 第 10 周總結
微服務 : 服務本身的設計、維護以及治理
微服務架構
目前互聯網系統最主要的架構方式
業界常關注微服務的重點錯誤
微服務框架如何使用
如何進行微服務的遠程調用
這些僅是入門
微服務最重要的點
微服務本身的設計、維護及治理
關鍵還是把服務本身設計好
實現高內聚低耦合
服務間的關係清晰
可以快速的迭代,提升業務的發展
巨無霸單體系統帶來的問題
編譯、部屬困難
編譯時間過長
配置項目過多,容易遺漏、錯誤,造成後續問題
打包構建時間過長
打包過程中若發生錯誤,得重新再來一次
工作效率低
代碼分支管理困難
代碼模塊由多個團隊共同維護修改
代碼merge時發生衝突
代碼 merge 與發佈糾結在一起,問題難釐清
發布過程時間長
數據庫連接耗盡
巨型單體應用有大量訪問
應用必須部署在一個大規模的服務器集群上
應用與數據庫的連接通常使用數據庫連接池
因為應用是單體的,所以其中的某功能可能連接不需要用到的數據庫
如論壇應用連接到產品數據庫
以每個應用10個數據庫連接計,一個數百台服務器集群將創建數千個連接
每個連接都會占用一些昂貴的系統資源
數據庫服務器將缺乏足夠的系統資源進行一般的數據操作
新增業務困難
系統複雜,牽一髮動全身
各種依賴關係不清晰
分工協作出問題
系統龐大,新手容易出錯
系統龐大,新代碼影響範圍無法清晰界定
在複雜的系統中增加新業務、維護舊功能困難
巨無霸單體系統的解決方案 (微服務重構)
拆分
縱向拆分
將依各大應用拆分成多個小應用
如果新增業務較為獨立,直接將其設計部署為一個獨立的Web應用
縱向之間垂直依賴
橫向拆分
將復用的業務拆分出來,獨立部署為微服務
通過遠程調用的方式調用
新業務只需調用這些微服務即可搭快速搭建一個應用系統
橫向之間很少依賴
變成一些小應用、小服務
應用連接服務,應用不直接連接數據庫
之間互相依賴調用,已構建系統
模塊獨立部署
服務跟應用分別發布
代碼各自獨立維護、獨立開發
服務根據各自訪問壓力決定部署個數
降低系統耦合性
職責邊界清晰,並更加簡單
應用依賴減少
不會互相衝突
微服務框架
Web Servic與企業級分布式服務
服務提供者 (Service provider)
通過 WSDL 向註冊中心描述自身提供的服務接口屬性
服務位址
提供的服務接口
服務參數
響應返回的數據格式參數
WSDL - Web Services Description Language (Web 服務描述語言)
註冊中心 (Service Broker)
使用 UDDI 發布服務提供者提供的服務
UDDI - Universal Description、Discovery,and Integration (統一描述、發現和集成)
服務請求者 (Service Requester)
從註冊中心檢索服務信息 (WSDL)
通過 SOAP 和服務提供者通信,使用相關服務
遠程調用
SOAP - Simple Object Access Protocol (簡單對象訪問協議)
Web Service 缺點
臃腫的註冊與發現機制
低效的 XML 序列化手段
包含大量冗餘的無效信息 (XML標籤等)
開銷相對較高的 HTTP 遠程通信
複雜的部署與維護手段
Web Service 難以滿足大型網站的要求
系統高性能
高可用
易部署
易維護
微服務框架需求
支援 Web Service 的服務註冊與發現、服務調用等標準功能
大型網站微服務即使是簡單服務,也需要集群部署
負載均衡
對服務提供者集群
由框架分發到不同的服務實例上,
實現高並發服務請求
失效轉移 (Fail Over)
服務提供者的失效轉移
由框架進行轉移至其他服務實例上
實現高可用
高效的遠程通信
調用次數可達億計
不讓服務調用變成系統性能瓶頸
基於長連接、私有訂製的通訊協議、二進制的序列化方式
減少數據傳輸
提供通訊速度
提升系統性能
對應用最少侵入
調用服務的開發或服務的使用,看不到是一個遠程調用
不使應用程序感知到使用微服務
支持漸進式演化和反復
技術為業務服務
是否使用微服務需要根據業務發展規則
可能分布式演化,也可能退回集中式
服務模塊支持集中式部署及分布式部署
版本管理
服務版本升級不可避免
升級時對應用沒有影響
可以容忍應用不升級服務
調用服務時,帶服務版本號
升級
服務實現升級
對服務請求者是透明的,無須關注
服務接口發生變化
服務請求者和服務提供者須同時升級
網站服務不能中斷
服務提供者
先升級接口
提供歷史版本供請求者調用
帶請求者訪問接口升級後,關閉歷史版本服務
服務請求者
訪問接口升級
Example
微服務框架 (Dubbo)架構
服務提供者
服務管理容器
檢查服務的描述信息
自動註冊至服務註冊中心
服務註冊中心
與SOAP相同
服務消費者
消費者程序透過服務接口進行調用
物理上的 Java 接口
由服務提供者提供接口包
接口包
透過訪問代理調用 Dubbo微服務框架客戶端
實現調用的透明代理
Dubbo框架客戶端
查找接口對應的實現
透過負載均衡策略模塊決定服務器
透過遠程通訊模塊進行服務調用
透過 Dubbo 自訂、私有化的通訊協定
若遠程調用失敗,實施失效轉移
微服務 : 落地實踐的策略與思路
Service Mesh 服務網格
Service Mesh
一種基礎設施,用於處理服務間的通信
一組輕量級網格代理
與應用程序部署在一起,對應用程序透明
服務程序與 Servide Mesh 實例層進行通信即可
適合大規模的複雜服務
Service Mesh 本身就有複雜性
Sidecar 模式
服務請求者調用本地的 Service Mesh 的實例
簡化服務調用者或開發者的複雜度
利用編程模式編成網格
利用 Service Mesh 去組合、分發及發現其他的服務
是對微服務調用構建一個虛擬層
由虛擬層實現遠程的
服務發現
服務的路由配置
服務調用
微服務架構實踐
微服務架構落地
業務先行,先理順業務的邊界和依賴
技術是手段不是目的
使用遠程調用技術的目的
使業務的邊界、依賴更加清晰
維護和開發更加獨立
先有獨立的模塊、後有分布式的服務
先拆分清楚,再進行遠程調用
業務耦合嚴重、邏輯複雜多變的系統進行微服務重構要謹慎
先搞清楚實施微服務的目的
業務復用?
復用的業務模塊是不是清晰
是不是需要先做業務重構
開發邊界清晰?
分布式集群提升性能?
再問使用微服務能不能達到目的
哪裡出問題?
哪裡要有一些前置條件先準備
實踐最佳策略
命令與查詢職責隔離 (CQRS)
CQRS - Command Query Responsibility Segregation
在服務接口層將查詢(讀操作)與命令(寫操作)隔離
實現服務層的讀寫分離
好處
更清晰的領域模型
可以針對讀寫分別優化,實現更好的性能
讀操作
可以使用緩存,提高讀的性能
寫操作
可以使用消息隊列,提升寫的性能
查詢服務不會修改數據,更好地保護數據
可以只連結 "從數據庫",不用連 "主數據庫"
事件溯源
將用戶請求處理過程中的每次狀態變化都記錄到事件日誌中
主要針對寫操作
案時間序列進行持久化存儲
好處
可以精確復現任何用戶狀態,進行覆核審計
可以有效監控用戶狀態變化,並在此基礎上實現分布式事務
可以據以回滾 (Undo) 及重作 (Redo)
斷路器
當某個服務出現故障、響應延遲或失敗率增加,繼續調用這個服務會
調用者請求阻塞
資源消耗增加
出現服務級聯失效
可使用斷路器阻斷對故障服務的調用
紀錄調用失敗的數目
超過一定的預值,就把斷路器打開
斷路器有三種狀況
關閉
正常調用服務
打開
無法對服務進行調用
半開
減少服務調用
服務重試及調用超時
上游調用者超時時間要大於下游調用者超時時間之和
上游才能 cover 下游超時
最重要的還是需求
倒三角模型
需求
想要達到的目標
價值
達到目標後會帶來甚麼價值
原則
為了達到價值,應該遵循甚麼原則
最佳實踐
基於原則,有哪些最佳實踐可以參考
工具
實現最佳實踐的工具、框架、技術手段、產品
選擇一個合適的工具框架
若使用微服務是從工具出發,會產生格外嚴重問題
忽略業務之間的關係
服務之間的關係一團糟
微服務網關的技術架構
基於網關的微服務架構
典型的移動互聯網時代的系統架構
所有請求都發送給一個網關服務器
網關是全局唯一的入口
由網關服務器處理所有請求
根據請求調用不同的微服務
由網關實行
微服務的聚合調用
數據的聚合或組合處理
沒有業務邏輯
微服務可以使用不同的框架
網關作用
統一的接入
所有客戶端、APP的請求都進入網關
網關自己實施負載均衡,實現高並發高性能
由網關統一優化
安全防護
防控防刷
黑白名單
網路攻擊
注入操作
在網關層統一攔截
協議適配
不同服務可以部署在不同的微服務框架
可以適配多個微服務框架協議
支撐多種意志的微服務調用
流量控制和容錯
並發太高
進行限流
進行熔斷
保護整個系統
這些均與業務邏輯無關
是技術性的一些需求
微服務網關
網關承擔了服務消費者的角色
網關從服務註冊中心發現服務
網關進行遠程調用
網關做數據聚合
返回給應用層
網關管道技術
網關沒有業務,主要職責是做各種校驗與攔截
這些職責可以通過管道技術連接起來,一路傳遞下去
責任鏈設計模式
如 Java 的 filter
把多個職責模塊,構成一個責任鏈,前後調用即可
Flower 異步網關與異步微服務框架
同步網關
調用微服務後,等待服務提供者完成服務的調用後,再返回
如果微服務還要調用其他微服務,則須等待所有均做完後才返回
網關發出請求後阻塞、同步等待
異步網關
發出請求後結束
可以再去處理別的請求
實現更高的並發能力
Flower
透過註冊中心編排一個流程圖
由每一個服務透過註冊中心找到下一個需要調用的服務
流程的最後一個服務返回網關
網關再響應給用戶
完成異步網關
利用 Servlet3 實現異步網關
socket、網路狀況,存儲在 context 中
在容器層面需要支持 servlet3
當下線程可以關閉
但不會關閉網路連接
開放平台網關
給第三方合作者使用的一個網關 API
需要進行授權認證
費用控制審計
重點
API 接口
開放平台給第三方合作者使用的一組 API
形式可以是
RESTful
WebService
RPC
協議轉換
將各種 API 輸入轉換成內部服務可以識別的形式
將內部服務的返回,封裝成 API 的格式
安全
身分識別
權限控制
訪問帶寬限制
保證平台資源被合作者公平合理使用
保護網站內部服務不會被外部應用拖垮
審計
記錄第三方應用的
訪問情況
進行監控
計費
路由
將開放平台各種訪問路由,映射到具體的內部服務
流程
將一組離散的服務組織成一個上下文相關的新服務
隱藏服務細節、提供統一街口工開發者調用
開放授權協議 OAuth 2.0
確認給第三方開放的功能
第三方如何獲得授權
Example
資源所有者 : 本人
第三方應用 : 網易音樂 ( 存取本人頭像資源)
授權服務器 : 微信伺服器
資源服務器 : 微信頭像服務器
授權碼授權
角色
Client : 網易音樂 APP
User Agent : 微信 APP
Resource Owner : 本人
Authorization Server : 微信授權服務器
過程
A :
本人利用 Client 選用 "微信" 登入
Client 將相關請求發給 UserAgent
UserAgent 將本人及 Client 相關訊息傳給 Authorization Server
B:
Authorizaton Server 確認後,將確認訊息發給 UserAgent
UserAgent 顯示介面請求 Resource Owner 確認
UserAgent將確認結果再回傳給 Authorization Server
C:
Authorization Server 產生 authorization code ,傳回給 UserAgent
UserAgent 將收到的 code 回傳給 Client
D
Client 利用 Authorization code 跟 Authorization server 要求此次登入
E
Authorization Server 產生登入 token回傳
Client
收到 token 後,可據此項資源服務器請求相對應的資源
OAuth 2.0 一共有四種授權方式
授權碼
隱式授權
資源所有者密碼
憑據和客戶端憑據
互聯網使用最多、最安全的是 "授權碼" 方式
領域驅動設計 DDD
良好的微服務系統需界定
微服務包含哪些功能
微服務邊界在哪裡
微服務之間的依賴關係
將微服務的業務關係設計好、業務處理好
為什麼需要 DDD
需求零零散散、不斷變更
工程師在各處代碼尋找可以實現需求變更的代碼
軟件只有需求分析、沒有設計
沒有一個統一的領域模型,維持其內在邏輯的一致性
功能特性不是按照領域模型內在的邏輯設計
按照各色人等自己的主觀想像設計
經由界面驅動就直接開發,頂多加一個數據庫設計
項目時間一長
修改困難重重
需求不斷延期
貧血模型 VS 充血模型
貧血模型
事務腳本模式
Service、Dao這些對象只有方法、沒有數值成員變量
方法調用時傳遞的數值對象沒有方法
當合同增加、商品類別增加、收入確認方式增加,Service 代碼就無限膨脹
充血模型
領域模型
把具體的對象拆分開
每個對象承擔自己的業務職責和業務邏輯
合併了行為和數據的領域的對象模型
通過領域模型對象的組合、交互方式,完成業務邏輯的實現
設計好了領域模型,也就設計好了業務邏輯實現
對象包含了對象的數據和計算邏輯
增加合同、商品、收入方式,都是透過增加不同的類來實現
滿足開閉原則
真正面相對象
面相業務領域所設計出來的對象
DDD 戰略設計
高層的業務架構層面的實現
領域
一個組織所做的事情極其包含的一切
組織的業務範圍和做事方式
軟件開發的目標範圍
領域設計
從領域出發
分析領域內模型及其關係
進而設計軟件系統的方法
根據業務邏輯進行設計,而不是根據用戶介面交互進行設計
子域
把整個領域拆分成多個子域
用戶
商品
訂單
庫存
物流
發票
限界上下文
解釋
子域中創建一個概念上的領域邊界
任何領域對象都只表示特定於該邊界內部的確切含意
這樣的邊界稱之
限界上下文和子域有一對一的關係
用來控制子域邊界
保證子域內的概念統一性
通常對應一個組件、一個模塊、一個微服務或一個子系統
上下文映射圖
不同子系統或模塊間的各種交互合作
DDD使用上下文映射突來設計這種關聯和交互
戰略設計
領域、子域、限界上下文、上下文映射圖等
劃分模塊和服務的邊界及依賴關係
對微服務架構設計至關重要
當設計完成,得到
一個微服務的模塊結構
微服務的架構設計
微服務有哪些
微服務包含哪些功能
微服務之間的交互依賴關係
DDD 戰術設計
低層的編程編碼方面的實現
實體
領域模型對象稱為 "實體"
每個實體都是唯一的,有唯一的標示
訂單對象是一個實體,有訂單 ID 的唯一標示
產品對象是一個實體,有產品 ID 的唯一標示
實體可能發生變化,唯一的標示不會變化
訂單狀態會變化,但訂單 ID 不會變化
實體設計是 DDD 的核心所在
通過業務分析,識別實體對象
要在業務場景和限界上下文中分析
而不是想當然爾的認定
透過相關的業務邏輯,設計實體的屬性和方法
要把握實體的特徵,實體應該承擔甚麼職責,不應該承擔甚麼職責
值對象
用來做度量或描述的對象應該被設計為值對象
不變性
創建後不能再改變
例如地址是值對象
修改地址是產生一個新的值對象
沒有唯一標示符
DDD 推薦盡可能將對象設計成值對象
聚合
一個關聯對象的集合,將其作為一個單元來處理數據更改
每個集合都有一個根和一個邊界
根是聚合中包含的單個特定實體
聚合根: 將多個實體和值對象聚合在一起的實體
邊界定義了聚合內部的內容
是一個物理邊界的一個最小單元
一個聚合不能再被拆分
微服務的最小粒度,就是包含一個聚合
一個微服務可以包含多個聚合
一個聚合不能拆到多個微服務裡面去
將來拆分微服務時,是以聚合為拆分單位
分層架構
接口層
調用請求的接口
應用層
接口調用預處理
相關的參數準備
不會做複雜的業務操作
領域實體的組合調用
事務控制
領域層
對應現實中的業務對象
真正的業務邏輯
由一個聚合根提供
可以根據自己的業務場景,來確定自己的分層架構
六邊形架構
領域模型通過應用程序封裝成一個比較獨立的模塊
不同的外部系統通過不同的 "適配器" 和領域模型交互
通過 HTTP 接口訪問,有適配器
通過 Web service 訪問 ,有適配器
通過 消息對列訪問,有適配器
戰術設計
實體、值對象、聚合、CQRS、事件溯源等
軟件組件設計原則
組件內聚原則 (高內聚)
討論組件應該包含哪些功能和類
組件能提供相對完整的功能,又不至於太過龐大
復用發布等同原則
軟件復用的最小粒度應該等同於其發布的最小粒度
希望別人用怎樣的粒度復用你的軟件,就應該以那樣的粒度發布軟件
組件是軟件復用和發布的最小粒度軟件單位
既是復用粒度
也是發佈粒度
版本號約定建議
格式
主版本號.次版本號.修訂號
主版本號升級
組件發生不向前相容的重大修訂
次版本號升級
組件向前相容
進行重要功能修訂或者 bug 修復
修訂號升級
進行不重要的功能修訂或者 bug 修復
共同封閉原則
將會同時修改,並且為相同目的而修改的類放到同一組件中
組件的目的是復用,一定會經歷各種變更
在變更時不要涉及其他組件
相關的變更都在同一個組件中
變更時,只需重新發布這個組件,不會牽連其他組件
提升組件的維護性
共同復用原則
不要強迫組件的用戶,依賴他們不需要的東西
應該將互相依賴、共同復用的類放在同一組件中
數據結構容器組件
各種數據結構容器類
對數據結構遍歷的類
排序的類
使組件中的類共同對外提供服務
不是被共同一類的類,就不應該放在同一個組件中
不被依賴的類變更,會引發組件的變更,進而引起使用組件的程序變更
會造成組件使用者不必要的困擾
也造成組件復用的困擾
組件耦合原則 (低耦合)
討論組件之間的耦合關係應該如何設計
無循環依賴原則
組件依賴關係中,不應該出現環
循環依賴 : A->B->C->A
各組件會互相影響,錯誤的正反饋會放大
Debug 很難找到 root cause
無意識地進行組件開發,不經過軟件設計
無組件的依賴關係圖
無組件包含的功能邏輯
循環依賴是在組件變更過程中逐漸形成
組件設計的邊界不清晰
組件開發設計缺乏評審
開發者只關注自己開發的組件
整個項目對組件依賴管理沒有統一的規則
穩定依賴原則
組件依賴關係必須指向更穩定的方向
組件不應該依賴一個比自己不穩定的組件
不穩定的組件應該依賴穩定的組件
一個組件被更多組件依賴,他需要是相對穩定的
穩定
較少變更的組件是穩定的
組件依賴很多其他的組件是相對不穩定的
穩定抽象原則
組件的抽象化程度應該與其穩定性程度一致
一個穩定的組件應該是抽象的
不穩定的組件應該是具體的
對具體開發的指導意義
如果設計的組件是具體的,不穩定的
可以為這個組件對外提供服務的類設計一組接口
並把這組接口封裝在一個專門的組件中
這個組件相對比較抽象、穩定
JDBC 就是一個例子
應用程序針對JDBC 接口編程
具體的實現組件是不穩定的,可以是
MySQL實現的 JDBC組件
Oracle實現的 JDBC 組件
組件的邊界與依賴關係,不僅僅是技術問題
易變與穩定、依賴與被依賴,都需要放在業務場景中去考察
組件的依賴與設計需要考慮人的因素
組織的關係問題
部門職責邊界
公司內部的政治關聯
康威定理
組織架構決定了系統架構
案例 : 用領域驅動設計驅動系統重構
系統重構不可避免
軟件開發是一個過程
相關方對軟件系統的認識會不斷改變
系統會從小到大演化
當系統現狀和對系統的認識有嚴重衝突,不重構難以繼續開發
持續的需求迭代過程中,代碼本身會逐漸腐壞、僵硬、脆弱難以維護
需求開發週期越來越長
bug 越來越多
利用領域驅動設計驅動系統重構
架構的演化
剛開始低複雜度
使用事務腳本會有較低的 Effort
使用領域驅動通常會 overengineering
當專案越來越大、進入高複雜度
使用事務腳本,其 effort 會難以接受
使用領域驅動,可以保持相對低的 effort
網站應用開發
一開始使用事務腳本方式,快速開發功能
當系統達到一定的複雜度時,進行系統重構
重構過程
統一領域知識,分解領域問題
用限界上下文識別微服務的功能、邊界和依賴關係
透過不同層面界定最後的上下文
領域邏輯層面
找出業務流程
分析每一個業務場景
拆分不同的活動
根據活動內聚性,劃分活動 (業務) 的邊界
利用UML 活動泳道圖
決定活動在哪個子域中比較合理
定義子域邊界
定義服務邊界
團隊合作層面
考慮團隊的邊界
高內聚的一個子域功能開發由同一個團隊開發
減少溝通上的問題
重新界定服務的上下文邊界
完成邏輯設計
技術實現層面
常用性
非功能性需求
技術框架
未來變化趨勢
開發過程與關鍵產出
版权声明: 本文为 InfoQ 作者【Panda】的原创文章。
原文链接:【http://xie.infoq.cn/article/c15dbe179bab94601661f16a0】。未经作者许可,禁止转载。
评论