服務端指南 | 微服務初級設計指南

微服務架構愈來愈被重視與應用,然而在擁抱微服務的過程當中,咱們或多或少會遇到一些常見的問題。那麼,本節將介紹幾種常見的問題以及應對思路。javascript

原文地址:服務端指南 | 微服務初級設計指南
博客地址:blog.720ui.com/java

如何拆分服務

微服務要如何拆分,是否拆分粒度越小越好?通常狀況下,對於服務的拆分並不是越小越好,甚至極端的案例是把一塊功能拆分紅一個服務,這種作法是不對的。所以,拆分粒度應該保證微服務具備業務的獨立性與完整性,服務的拆分圍繞業務模塊進行拆分。例如將 VR 資訊系統進行服務拆分,分爲資訊系統、話題系統、日報系統、百科系統四個微服務系統。數據庫

可是,不少狀況下,服務的拆分圍繞業務模塊進行拆分是一種理想狀態下的拆分方法,換句話說,咱們在架構設計之初就假定咱們能夠掌握一切。然而,不一樣的服務可能由不一樣的團隊開發與維護,實際場景下,微服務的便利性更多的在於團隊內部可以產生閉環,換句話說,團隊內部能夠易於開發與維護,便於溝通與協做,可是對於外部團隊就存在很大的溝通成本與協做成本。如今,咱們來看一個案例。團隊 A 考慮到功能的複用性而開發了一個「互動組件」,其中包括 「評論模塊」功能。此時,團隊 B 並不知情也開發了一個相似的「互動組件」。而團隊 C 也有這個需求,它知道團隊 A 有這個「互動組件」,但願能夠複用,可是因爲這個「互動組件」在設計的時候更多地考慮了團隊 A 的當前業務,沒有很好的複用性,例如不支持「評論蓋樓」功能,而因爲團隊 A 出於當前其餘項目的進度緣由沒法立刻提供支持,團隊 B 評估後決定花一週時間本身開發一個符合本身業務需求的「互動組件」。此時,各個項目團隊各自維護了一個「互動組件」。此外,咱們再來看一個案例。一個 OA 系統擁有「用戶管理」、「文件管理」、「公告管理」、「政策管理」、「公文管理」、「任務管理」、「審批管理」等功能,若是按照微服務架構思想能夠圍繞業務模塊進行拆分,可是事實上這個 OA 系統的最終用戶只有 30 多人,使用微服務架構可能有點「殺雞用牛刀」的感受了。回顧下,第一個案例中,因爲團隊之間的職責與邊界致使了服務的複用存在侷限性,甚至形成各自爲戰的局面,這種狀況通常須要公司層面進行規劃和統籌。第二案例中,因爲用戶量不大,系統也不復雜,使用微服務反而帶來了沒必要要的設計和運維難度,同時也帶來了一些技術的複雜度。此外,咱們還須要考慮服務依賴,鏈式調用、數據一致性、分佈式事務等問題。json

總結下,服務的拆分是一個很是有學問的技術活,要圍繞業務模塊進行拆分,拆分粒度應該保證微服務具備業務的獨立性與完整性,儘量少的存在服務依賴,鏈式調用。可是,在實際開發過程當中,有的時候單體架構更加適合當前的項目。實際上,微服務的設計並非一蹴而就的,它是一個設計與反饋過程。所以,咱們在設計之初能夠將服務的粒度設計的大一些,並考慮其可擴展性,隨着業務的發展,進行動態地拆分也是一個不錯的選擇。緩存

論微服務的數據庫管理

一般狀況下,在每一個服務都有本身的緩存和數據庫,而且緩存和數據庫是相互獨立且透明的。所以,共享緩存與共享數據庫是不對的。那若是服務 A 須要獲取服務 B 的數據怎麼辦?請讀者思考,這種狀況下,能夠直接在服務 A 建立兩個數據源(一個是服務 A 的數據庫,一個是服務 B 的數據庫)進行數據操做麼?事實上,這個方案是不提倡的,由於它破壞了微服務之間的數據獨立性。所以,更好的作法是:服務 B 提供一個獲取該數據的 API 接口,而服務 A 經過調用該接口進行業務組裝。可是,凡事無絕對,有一種特殊的場景可能須要共享數據庫,那就是舊的服務過分到新的服務的場景,新的服務複用舊的服務的數據庫從而到達功能與數據過分的需求。安全

服務多版本指南

微服務的 API 接口應該儘可能兼容以前的版本,換句話說,微服務能夠支持多版本,可是須要儘可能確保向下兼容。若是服務沒法兼容舊版本,則須要升級版本號。爲了解決這個版本不兼容問題,在設計 RESTful API 的一種實用的作法是使用版本號。通常狀況下,咱們會在 url 中保留版本號,並同時兼容多個版本。微信

【GET】  /v1/users/{user_id}  // 版本 v1 的查詢用戶列表的 API 接口
【GET】  /v2/users/{user_id}  // 版本 v2 的查詢用戶列表的 API 接口複製代碼

在 Java 語言的 Spring 框架中實現,以下所示。網絡

@RestController
@RequestMapping({"v1/c/users"})
public class SysUserV1Controller {
    @RequestMapping(value={"/{userId:\\d+}"}, method=RequestMethod.GET)
    public SysUser findOne(@PathVariable long userId){  
        // 業務實現
    }
}

@RestController
@RequestMapping({"v2/c/users"}) 
public class SysUserV2Controller {
    @RequestMapping(value={"/{userId:\\d+}"}, method=RequestMethod.GET)
    public SysUser findOne(@PathVariable long userId){  
        // 業務實現
    }
}複製代碼

此時,客戶端的產品的新功能將請求新的服務端的 API 接口地址。架構

雖然服務端會同時兼容多個版本,可是同時維護太多版本對於服務端而言是個不小的負擔,由於服務端要維護多套代碼。這種狀況下,常見的作法不是維護全部的兼容版本,而是隻維護最新的幾個兼容版本,例如維護最新的三個兼容版本。在一段時間後,當絕大多數用戶升級到較新的版本後,廢棄一些使用量較少的服務端的老版本API 接口版本,並要求使用產品的很是舊的版本的用戶強制升級。app

此外,在微服務的多版本並存的場景下,這些服務的版本能夠在同一個服務中定義,這種狀況通常出如今服務的內容變動不是很大的場景。另外一種方式,服務的版本能夠在不一樣的服務中定義,例如「資訊v1服務」、「資訊v2服務」, 這種狀況通常出如今服務框架重構中,它將抽離出一個新的工程來提供新的服務接口。

應對微服務的鏈式調用異常

通常狀況下,每一個微服務之間是獨立的,若是某個服務宕機,只會影響到當前服務,而不會對整個業務系統產生影響。可是,服務端可能會在多個微服務之間產生一條鏈式調用,並把整合後的信息返回給客戶端。在調用過程當中,若是某個服務宕機或者網絡不穩定可能形成整個請求失敗。所以,爲了應對微服務的鏈式調用異常,咱們須要在設計微服務調用鏈時不宜過長,以避免客戶端長時間等待,以及中間環節出現錯誤形成整個請求失敗。此外,能夠
考慮使用消息隊列進行業務解耦,而且使用緩存避免微服務的鏈式調用從而提升該接口的可用性。

是否須要提供外觀接口

外觀接口,指的是將多個服務的接口進行業務封裝與整合並提供一個簡單的調用接口給客戶端使用。這種設計的好處在於,客戶端再也不須要知道那麼多服務的接口,只須要調用這個外觀接口便可。可是,壞處也是顯而易見的,即增長了服務端的業務複雜度,接口性能不高,而且複用性不高。所以,筆者的建議是服務端的接口設計儘量保證職責單一,而在客戶端進行「樂高式」組裝。若是存在 SEO 優化的產品,須要被相似於百度這樣的搜索引擎收錄,能夠當首屏的時候,經過服務端渲染生成 HTML,使之讓搜索引擎收錄,若不是首屏的時候,能夠經過客戶端調用服務端 RESTful API 接口進行頁面渲染。

如何快速追蹤與定位問題

在微服務複雜的鏈式調用中,咱們會比單體架構更難以追蹤與定位問題。所以,在設計的時候,須要特別注意。一種比較好的方案是,當 RESTful API 接口出現非 2xx 的 HTTP 錯誤碼響應時,採用全局的異常結構響應信息。其中,code 字段用來表示某類錯誤的錯誤碼,在微服務中應該加上「{biz_name}/」前綴以便於定位錯誤發生在哪一個業務系統上。咱們來看一個案例,假設「用戶中心」某個接口沒有權限獲取資源而出現錯誤,咱們的業務系統能夠響應「UC/AUTH_DENIED」,而且經過自動生成的 UUID 值的 request_id 字段,在日誌系統中得到錯誤的詳細信息。

HTTP/1.1 400 Bad Request
Content-Type: application/json
{
    "code": "INVALID_ARGUMENT",
    "message": "{error message}",
    "cause": "{cause message}",
    "request_id": "01234567-89ab-cdef-0123-456789abcdef",
    "host_id": "{server identity}",
    "server_time": "2014-01-01T12:00:00Z"
}複製代碼

此外,咱們須要在記錄日誌時,標記出錯誤來源以及錯誤詳情便於更好地分析與定位問題。

微服務的安全

OAuth 是一個關於受權的開放網絡標準,它容許第三方網站在用戶受權的前提下訪問用戶在服務商那裏存儲的各類信息。實際上,OAuth 2.0 容許用戶提供一個令牌給第三方網站,一個令牌對應一個特定的第三方網站,同時該令牌只能在特定的時間內訪問特定的資源。用戶在客戶端使用用戶名和密碼在用戶中心得到受權,而後客戶端在訪問應用是附上 Token 令牌。此時,應用接收到客戶端的 Token 令牌到用戶中心進行認證。

通常狀況下,access token 會添加到 HTTP Header 的 Authorization 參數中使用,其中常用到的是 Bearer Token 與 Mac Token。其中,Bearer Token 適用於安全的網絡下 API 受權。MAC Token 適用於不安全的網絡下 API 受權。

微服務的數據一致性

如何保證多個微服務的數據的一致性是一個必須面對的問題。例如,「話題系統」的數據變動的同時要保證「用戶動態系統」的數據級聯更新。若是,這個時候「用戶動態系統」發生宕機,或者網絡鏈接異常、網絡超時,就會致使數據的不一致。目前,分佈式事務並無很好的解決方案,難以知足數據強一致性,通常狀況下,保證系統通過一段較短的時間的自我恢復和修正,數據最終達到一致。這個自我恢復和修正的方式,能夠在每次更新的時候進行修復,或者採起週期性的進行校驗操做來保證。

咱們還能夠引入可靠的消息隊列,只要保證當前「話題系統」的可靠事件投遞而且消息中間件確保事件傳遞至少一次,那麼訂閱這個事件的消費者(用戶動態系統)保證事件可以在本身的業務內被消費便可。在消費者(用戶動態系統)處理的過程當中出現異常,能夠將事件放入重試隊列並根據具體的策略進行失敗重試,若是屢次重試失敗能夠寫入錯誤日誌並主動通知開發人員進行手工介入。注意的是,這個過程要保證可靠事件投遞與避免重複消費,其中尤爲重要是接口要保證冪等性,例如支付系統不能由於重複收到支付事件而致使屢次支付。

此外,咱們能夠採用業務補償等方式保證數據的一致性。

(完)

更多精彩文章,盡在「服務端思惟」微信公衆號!

相關文章
相關標籤/搜索