淺談「微服務」

  1. 微服務概述程序員

    1.1 易於擴展web

    1.2 部署簡單算法

    1.3 技術異構性sql

  2. 數據庫的服務化切分docker

    2.1 什麼是「分庫分表」?數據庫

    2.2 數據庫擴展的幾種方式編程

    2.3 分庫分表的幾種方式緩存

    2.4 引入分庫分表中間件後面臨的問題安全

    2.5 現有分庫分表中間件的橫向對比服務器

  3. 微服務架構中的分佈式事務

    3.1 什麼是事務?

    3.2 事務的四大特性 ACID

    3.3 事務的隔離級別

    3.4 什麼是分佈式事務?

    3.5 CAP理論

    3.6 BASE理論

    3.7 酸鹼平衡

    3.8 分佈式事務協議

    3.9 分佈式事務的解決方案

  4. 服務部署

    4.1 持續集成、持續部署、持續交付

    4.2 微服務與持續集成

    4.3 微服務構建物


1. 什麼是微服務?

咱們首先給出微服務的定義,而後再對該定義給出詳細的解釋。

微服務就是一些可獨立運行、可協同工做的小的服務。

從概念中咱們能夠提取三個關鍵詞:可獨立運行、可協同工做、小。這三個詞高度歸納了微服務的核心特性。下面咱們就對這三個詞做詳細解釋。

  1. 可獨立運行

    微服務是一個個能夠獨立開發、獨立部署、獨立運行的系統或者進程。

  2. 可協同工做

    採用了微服務架構後,整個系統被拆分紅多個微服務,這些服務之間每每不是徹底獨立的,在業務上存在必定的耦合,即一個服務可能須要使用另外一個服務所提供的功能。這就是所謂的「可協同工做」。與單服務應用不一樣的是,多個微服務之間的調用時經過RPC通訊來實現,而非單服務的本地調用,因此通訊的成本相對要高一些,但帶來的好處也是可觀的。

  3. 小而美

    微服務的思想是,將一個擁有複雜功能的龐大系統,按照業務功能,拆分紅多個相互獨立的子系統,這些子系統則被稱爲「微服務」。每一個微服務只承擔某一項職責,從而相對於單服務應用來講,微服務的體積是「小」的。小也就意味着每一個服務承擔的職責變少,根據單一職責原則,咱們在系統設計時,要儘可能使得每一項服務只承擔一項職責,從而實現系統的「高內聚」。

2. 微服務的優勢

1. 易於擴展

在單服務應用中,若是目前性能到達瓶頸,沒法支撐目前的業務量,此時通常採用集羣模式,即增長服務器集羣的節點,並將這個單服務應用「複製」到全部的節點上,從而提高總體性能。然而這種擴展的粒度是比較粗糙的。若是隻是系統中某一小部分存在性能問題,在單服務應用中,也要將整個應用進行擴展,這種方式簡單粗暴,沒法對症下藥。而當咱們使用了微服務架構後,若是某一項服務的性能到達瓶頸,那麼咱們只須要增長該服務的節點數便可,其餘服務無需變化。這種擴展更加具備針對性,可以充分利用計算機硬件/軟件資源。並且只擴展單個服務影響的範圍較小,從而系統出錯的機率也就越低。

2. 部署簡單

對於單服務應用而言,全部代碼均在一個項目中,從而致使任何微小的改變都須要將整個項目打包、發佈、部署,而這一系列操做的代價是高昂的。久而久之,團隊爲了下降發佈的頻率,會使得每次發佈都伴隨着大量的修改,修改越多也就意味着出錯的機率也越大。 當咱們採用微服務架構之後,每一個服務只承擔少數職責,從而每次只須要發佈發生修改的系統,其餘系統依然可以正常運行,波及範圍較小。此外,相對於單服務應用而言,每一個微服務系統修改的代碼相對較少,從而部署後出現錯誤的機率也相對較低。

3. 技術異構性

對於單服務應用而言,一個系統的全部模塊均整合在一個項目中,因此這些模塊只能選擇相同的技術。但有些時候,單一技術沒辦法知足不一樣的業務需求。如對於項目的算法團隊而言,函數試編程語言可能更適合算法的開發,而對於業務開發團隊而言,相似於Java的強類型語言具備更高的穩定性。然而在單服務應用中只能互相權衡,選擇同一種語言,而當咱們使用微服務結構後,這個問題就可以引刃而解。咱們將一個完整的系統拆分紅了多個獨立的服務,從而每一個服務均可以根據各自不一樣的特色,選擇最爲合適的技術體系。

固然,並非全部的微服務系統都具有技術異構性,要實現技術異構性,必須保證全部服務都提供通用接口。咱們知道,在微服務系統中,服務之間採用RPC接口通訊,而實現RPC通訊的方式有不少。有一些RPC通訊方式與語言強耦合,如Java的RMI技術,它就要求通訊的雙方都必須採用Java語言開發。固然,也有一些RPC通訊方式與語言無關,如基於HTTP協議的REST。這種通訊方式對通訊雙方所採用的語言沒有作任何限制,只要通訊過程當中傳輸的數據遵循REST規範便可。固然,與語言無關也就意味着通訊雙方沒有類型檢查,從而會提升出錯的機率。因此,究竟選擇與語言無關的RPC通訊方式,仍是選擇與語言強耦合的RPC通訊方式,須要咱們根據實際的業務場景合理地分析。

2. 數據庫的服務化切分

2.1 什麼是「分庫分表」?

隨着大數據時代的到來,業務系統的數據量日益增大,數據存儲能力逐漸成爲影響系統性能的瓶頸。目前主流的關係型數據庫單表存儲上限爲1000萬條記錄,而這一存儲能力顯然已經沒法知足大數據背景下的業務系統存儲要求了。隨着微服務架構、分佈式存儲等概念的出現,數據存儲問題也漸漸迎來了起色。而數據分片是目前解決海量數據持久化存儲與高效查詢的一種重要手段。數據分庫分表的過程在系統設計階段完成,要求系統設計人員根據系統預期的業務量,將將來可能出現瓶頸的數據庫、數據表按照必定規則拆分紅多個庫、多張表。這些數據庫和數據表須要部署在不一樣的服務器上,從而將數據讀寫壓力分攤至集羣中的各個節點,提高數據庫總體處理能力,避免出現讀寫瓶頸的現象。

目前數據分片的方式一共有兩種:離散分片和連續分片。

離散分片是按照數據的某一字段哈希取模後進行分片存儲。只要哈希算法選擇得當,數據就會均勻地分佈在不一樣的分片中,從而將讀寫壓力平均分配給全部分片,總體上提高數據的讀寫能力。然而,離散存儲要求數據之間有較強的獨立性,但實際業務系統並不是如此,不一樣分片之間的數據每每存在必定的關聯性,所以在某些場景下須要跨分片鏈接查詢。因爲目前全部的關係型數據庫出於安全性考慮,均不支持跨庫鏈接。所以,跨庫操做須要由數據分庫分表中間件來完成,這極大影響數據的查詢效率。此外,當數據存儲能力出現瓶頸須要擴容時,離散分片規則須要將全部數據從新進行哈希取模運算,這無疑成爲限制系統可擴展性的一個重要因素。雖然,一致性哈希能在必定程度上減小系統擴容時的數據遷移,但數據遷移問題仍然不可避免。對於一個已經上線運行的系統而言,系統中止對外服務進行數據遷移的代價太大。

第二種數據分片的方式即爲連續分片,它能解決系統擴容時產生的數據遷移問題。這種方式要求數據按照時間或連續自增主鍵連續存儲。從而一段時間內的數據或相鄰主鍵的數據會被存儲在同一個分片中。當須要增長分片時,不會影響現有的分片。所以,連續分片能解決擴容所帶來的數據遷移問題。可是,數據的存儲時間和讀寫頻率每每呈正比,也就是大量的讀寫每每都集中在最新存儲的那一部分數據,這就會致使熱點問題,並不能起到分攤讀寫壓力的初衷。

2.2 數據庫擴展的幾種方式

數據庫擴展一共有四種分配方式,分別是:垂直分庫、垂直分表、水平分表、水平數據分片。每一種策略都有各自的適用場景。

  1. 垂直分庫

    垂直分庫便是將一個完整的數據庫根據業務功能拆分紅多個獨立的數據庫,這些數據庫能夠運行在不一樣的服務器上,從而提高數據庫總體的數據讀寫性能。這種方式在微服務架構中很是經常使用。微服務架構的核心思想是將一個完整的應用按照業務功能拆分紅多個可獨立運行的子系統,這些子系統稱爲「微服務」,各個服務之間經過RPC接口通訊,這樣的結構使得系統耦合度更低、更易於擴展。垂直分庫的理念與微服務的理念不謀而合,能夠將本來完整的數據按照微服務拆分系統的方式,拆分紅多個獨立的數據庫,使得每一個微服務系統都有各自獨立的數據庫,從而能夠避免單個數據庫節點壓力過大,影響系統的總體性能,以下圖所示。

    title

     

  2. 垂直分表

    垂直分表若是一張表的字段很是多,那麼頗有可能會引發數據的跨頁存儲,這會形成數據庫額外的性能開銷,而垂直分表能夠解決這個問題。垂直分表就是將一張表中不經常使用的字段拆分到另外一張表中,從而保證第一章表中的字段較少,避免出現數據庫跨頁存儲的問題,從而提高查詢效率。而另外一張表中的數據經過外鍵與第一張表進行關聯,以下圖所示。

    title

     

  3. 水平分表

    若是一張表中的記錄數過多(超過1000萬條記錄),那麼會對數據庫的讀寫性能產生較大的影響,雖然此時仍然可以正確地讀寫,但讀寫的速度已經到了業務沒法忍受的地步,此時就須要使用水平分表來解決這個問題。水平分表是將一張含有不少記錄數的表水平切分,拆分紅幾張結構相同的表。舉個例子,假設一張訂單表目前存儲了2000萬條訂單的數據,致使數據讀寫效率極低。此時能夠採用水平分表的方式,將訂單表拆分紅100張結構相同的訂單表,分別叫作order_一、order_2……、order_100。而後能夠根據訂單所屬用戶的id進行哈希取模後均勻地存儲在這100張表中,從而每張表中只存儲了20萬條訂單記錄,極大提高了訂單的讀寫效率,以下圖所示。 固然,若是拆分出來的表都存儲在同一個數據庫節點上,那麼當請求量過大的時候,畢竟單臺服務器的處理能力是有限的,數據庫仍然會成爲系統的瓶頸,因此爲了解決這個問題,就出現了水平數據分片的解決方案。

    title

     

  4. 水平分庫分表

    水平數據分片與數據分片區別在於:水平數據分片首先將數據表進行水平拆分,而後按照某一分片規則存儲在多臺數據庫服務器上。從而將單庫的壓力分攤到了多庫上,從而避免由於數據庫硬件資源有限致使的數據庫性能瓶頸,以下圖所示。

    title

     

2.3 分庫分表的幾種方式

目前經常使用的數據分片策略有兩種,分別是連續分片和離散分片。

  1. 離散分片

    離散分片是指將數據打散以後均勻地存儲在邏輯表的各個分片中,從而使的對同一張邏輯表的數據讀取操做均勻地落在不一樣庫的不一樣表上,從而提升讀寫速度。離散分片通常以哈希取模的方式實現。好比:一張邏輯表有4個分片,那麼在讀寫數據的時候,中間件首先會取得分片字段的哈希值,而後再模以4,從而計算出該條記錄所在的分片。在這種方法中,只要哈希算法選的好,那麼數據分片將會比較均勻,從而數據讀寫就會比較均勻地落在各個分片上,從而就有較高的讀寫效率。可是,這種方式也存在一個最大的缺陷——數據庫擴容成本較高。採用這種方式,若是須要再增長分片,原先的分片算法將失效,而且全部記錄都須要從新計算所在分片的位置。對於一個已經上線的系統來講,行級別的數據遷移成本至關高,並且因爲數據遷移期間系統仍在運行,仍有新數據產生,從而沒法保證遷移過程數據的一致性。若是爲了不這個問題而停機遷移,那必然會對業務形成巨大影響。固然,若是爲了不數據遷移,在一開始的時候就分片較多的分片,那須要承擔較高的費用,這對於中小公司來講是沒法承受的。

  2. 連續分片

    連續分片指的是按照某一種分片規則,將某一個區間內的數據存儲在同一個分片上。好比按照時間分片,每月生成一張物理表。那麼在讀寫數據時,直接根據當前時間就能夠找到數據所在的分片。再好比能夠按照記錄ID分片,這種分片方式要求ID須要連續遞增。因爲Mysql數據庫單表支持最大的記錄數約爲1000萬,所以咱們能夠根據記錄的ID,使得每一個分片存儲1000萬條記錄,當目前的記錄數即將到達存儲上限時,咱們只需增長分片便可,原有的數據無需遷移。連續分片的一個最大好處就是方便擴容,由於它不須要任何的數據遷移。可是,連續分片有個最大的缺點就是熱點問題。連續分片使得新插入的數據集中在同一個分片上,而每每新插入的數據讀寫頻率較高,所以,讀寫操做都會集中在最新的分片上,從而沒法體現數據分片的優點。

2.4 引入分庫分表中間件後面臨的問題

  1. 跨庫操做

    在關係型數據庫中,多張表之間每每存在關聯,咱們在開發過程當中須要使用JOIN操做進行多表鏈接。可是當咱們使用了分庫分表模式後,因爲數據庫廠商處於安全考慮,不容許跨庫JOIN操做,從而若是須要鏈接的兩張表被分到不一樣的庫中後,就沒法使用SQL提供的JOIN關鍵字來實現錶鏈接,咱們可能須要在業務系統層面,經過屢次SQL查詢,完成數據的組裝和拼接。這一方面會增長業務系統的複雜度,另外一方面會增長業務系統的負載。 所以,當咱們使用分庫分表模式時,須要根據具體的業務場景,合理地設置分片策略、設置分片字段,這將會在本文的後續章節中介紹。

  2. 分佈式事務

    咱們知道,數據庫提供了事務的功能,以保證數據一致性。然而,這種事務只是針對單數據庫而言的,數據庫廠商並未提供跨庫事務。所以,當咱們使用了分庫分表以後,就須要咱們在業務系統層面實現分佈式事務。關於分佈式事務的詳細內容,能夠參考筆者的另外一篇文章《經常使用的分佈式事務解決方案》

2.5 現有分庫分表中間件的橫向對比

  • Cobar實現數據庫的透明分庫,讓開發人員可以在無感知的狀況下操縱數據庫集羣,從而簡化數據庫的編程模型。然而Cobar僅實現了分庫功能,並未實現分表功能。分庫能夠解決單庫IO、CPU、內存的瓶頸,但沒法解決單表數據量過大的問題。此外,Cobar是一個獨立運行的系統,它處在應用系統與數據庫系統之間,所以增長了額外的部署複雜度,增長了運維成本。

  • 爲了解決上述問題,Cobar還推出了一個Cobar-Client項目,它只是一個安裝在應用程序的Jar包,並非一個獨立運行的系統,必定程度上下降了系統的複雜度。但和Cobar同樣,仍然只支持分庫,並不支持分表,也不支持讀寫分離。

  • MyCat是基於Cobar二次開發的數據庫中間件,和Cobar相比,它增長了讀寫分離的功能,並修復了Cobar的一些bug。可是,MyCat和Cobar同樣,都是一套須要獨立部署的系統,所以會增長部署的複雜度,提升了後期系統運維的成本。

3. 微服務架構中的分佈式事務

衆所周知,數據庫能實現本地事務,也就是在同一個數據庫中,你能夠容許一組操做要麼全都正確執行,要麼全都不執行。這裏特別強調了本地事務,也就是目前的數據庫只能支持同一個數據庫中的事務。但如今的系統每每採用微服務架構,業務系統擁有獨立的數據庫,所以就出現了跨多個數據庫的事務需求,這種事務即爲「分佈式事務」。那麼在目前數據庫不支持跨庫事務的狀況下,咱們應該如何實現分佈式事務呢?本文首先會爲你們梳理分佈式事務的基本概念和理論基礎,而後介紹幾種目前經常使用的分佈式事務解決方案。廢話很少說,那就開始吧~

3.1 什麼是事務?

事務由一組操做構成,咱們但願這組操做可以所有正確執行,若是這一組操做中的任意一個步驟發生錯誤,那麼就須要回滾以前已經完成的操做。也就是同一個事務中的全部操做,要麼全都正確執行,要麼全都不要執行。

3.2 事務的四大特性 ACID

說到事務,就不得不提一下事務著名的四大特性。

  • 原子性

    原子性要求,事務是一個不可分割的執行單元,事務中的全部操做要麼全都執行,要麼全都不執行。

  • 一致性

    一致性要求,事務在開始前和結束後,數據庫的完整性約束沒有被破壞。

  • 隔離性

    事務的執行是相互獨立的,它們不會相互干擾,一個事務不會看到另外一個正在運行過程當中的事務的數據。

  • 持久性

    持久性要求,一個事務完成以後,事務的執行結果必須是持久化保存的。即便數據庫發生崩潰,在數據庫恢復後事務提交的結果仍然不會丟失。

注意:事務只能保證數據庫的高可靠性,即數據庫自己發生問題後,事務提交後的數據仍然能恢復;而若是不是數據庫自己的故障,如硬盤損壞了,那麼事務提交的數據可能就丟失了。這屬於『高可用性』的範疇。所以,事務只能保證數據庫的『高可靠性』,而『高可用性』須要整個系統共同配合實現。

3.3 事務的隔離級別

這裏擴展一下,對事務的隔離性作一個詳細的解釋。

在事務的四大特性ACID中,要求的隔離性是一種嚴格意義上的隔離,也就是多個事務是串行執行的,彼此之間不會受到任何干擾。這確實可以徹底保證數據的安全性,但在實際業務系統中,這種方式性能不高。所以,數據庫定義了四種隔離級別,隔離級別和數據庫的性能是呈反比的,隔離級別越低,數據庫性能越高,而隔離級別越高,數據庫性能越差。

3.3.1 事務併發執行會出現的問題

咱們先來看一下在不一樣的隔離級別下,數據庫可能會出現的問題:

  1. 更新丟失

    當有兩個併發執行的事務,更新同一行數據,那麼有可能一個事務會把另外一個事務的更新覆蓋掉。 當數據庫沒有加任何鎖操做的狀況下會發生。

  2. 髒讀

    一個事務讀到另外一個還沒有提交的事務中的數據。 該數據可能會被回滾從而失效。 若是第一個事務拿着失效的數據去處理那就發生錯誤了。

  3. 不可重複讀

    不可重複度的含義:一個事務對同一行數據讀了兩次,卻獲得了不一樣的結果。它具體分爲以下兩種狀況:

    • 虛讀:在事務1兩次讀取同一記錄的過程當中,事務2對該記錄進行了修改,從而事務1第二次讀到了不同的記錄。
    • 幻讀:事務1在兩次查詢的過程當中,事務2對該表進行了插入、刪除操做,從而事務1第二次查詢的結果發生了變化。

不可重複讀 與 髒讀 的區別? 髒讀讀到的是還沒有提交的數據,而不可重複讀讀到的是已經提交的數據,只不過在兩次讀的過程當中數據被另外一個事務改過了。

3.3.2 數據庫的四種隔離級別

數據庫一共有以下四種隔離級別:

  1. Read uncommitted 讀未提交

    在該級別下,一個事務對一行數據修改的過程當中,不容許另外一個事務對該行數據進行修改,但容許另外一個事務對該行數據讀。 所以本級別下,不會出現更新丟失,但會出現髒讀、不可重複讀。

  2. Read committed 讀提交

    在該級別下,未提交的寫事務不容許其餘事務訪問該行,所以不會出現髒讀;可是讀取數據的事務容許其餘事務的訪問該行數據,所以會出現不可重複讀的狀況。

  3. Repeatable read 重複讀

    在該級別下,讀事務禁止寫事務,但容許讀事務,所以不會出現同一事務兩次讀到不一樣的數據的狀況(不可重複讀),且寫事務禁止其餘一切事務。

  4. Serializable 序列化

    該級別要求全部事務都必須串行執行,所以能避免一切因併發引發的問題,但效率很低。

隔離級別越高,越能保證數據的完整性和一致性,可是對併發性能的影響也越大。對於多數應用程序,能夠優先考慮把數據庫系統的隔離級別設爲Read Committed。它可以避免髒讀取,並且具備較好的併發性能。儘管它會致使不可重複讀、幻讀和第二類丟失更新這些併發問題,在可能出現這類問題的個別場合,能夠由應用程序採用悲觀鎖或樂觀鎖來控制。

3.4 什麼是分佈式事務?

到此爲止,所介紹的事務都是基於單數據庫的本地事務,目前的數據庫僅支持單庫事務,並不支持跨庫事務。而隨着微服務架構的普及,一個大型業務系統每每由若干個子系統構成,這些子系統又擁有各自獨立的數據庫。每每一個業務流程須要由多個子系統共同完成,並且這些操做可能須要在一個事務中完成。在微服務系統中,這些業務場景是廣泛存在的。此時,咱們就須要在數據庫之上經過某種手段,實現支持跨數據庫的事務支持,這也就是你們常說的「分佈式事務」。

這裏舉一個分佈式事務的典型例子——用戶下單過程。 當咱們的系統採用了微服務架構後,一個電商系統每每被拆分紅以下幾個子系統:商品系統、訂單系統、支付系統、積分系統等。整個下單的過程以下:

  1. 用戶經過商品系統瀏覽商品,他看中了某一項商品,便點擊下單
  2. 此時訂單系統會生成一條訂單
  3. 訂單建立成功後,支付系統提供支付功能
  4. 當支付完成後,由積分系統爲該用戶增長積分

上述步驟二、三、4須要在一個事務中完成。對於傳統單體應用而言,實現事務很是簡單,只需將這三個步驟放在一個方法A中,再用Spring的@Transactional註解標識該方法便可。Spring經過數據庫的事務支持,保證這些步驟要麼全都執行完成,要麼全都不執行。但在這個微服務架構中,這三個步驟涉及三個系統,涉及三個數據庫,此時咱們必須在數據庫和應用系統之間,經過某項黑科技,實現分佈式事務的支持。

3.5 CAP理論

CAP理論說的是:在一個分佈式系統中,最多隻能知足C、A、P中的兩個需求。

CAP的含義:

  • C:Consistency 一致性

    同一數據的多個副本是否實時相同。

  • A:Availability 可用性

    可用性:必定時間內 & 系統返回一個明確的結果 則稱爲該系統可用。

  • P:Partition tolerance 分區容錯性

    將同一服務分佈在多個系統中,從而保證某一個系統宕機,仍然有其餘系統提供相同的服務。

CAP理論告訴咱們,在分佈式系統中,C、A、P三個條件中咱們最多隻能選擇兩個。那麼問題來了,究竟選擇哪兩個條件較爲合適呢?

對於一個業務系統來講,可用性和分區容錯性是必需要知足的兩個條件,而且這二者是相輔相成的。業務系統之因此使用分佈式系統,主要緣由有兩個:

  • 提高總體性能

    當業務量猛增,單個服務器已經沒法知足咱們的業務需求的時候,就須要使用分佈式系統,使用多個節點提供相同的功能,從而總體上提高系統的性能,這就是使用分佈式系統的第一個緣由。

  • 實現分區容錯性

    單一節點 或 多個節點處於相同的網絡環境下,那麼會存在必定的風險,萬一該機房斷電、該地區發生天然災害,那麼業務系統就全面癱瘓了。爲了防止這一問題,採用分佈式系統,將多個子系統分佈在不一樣的地域、不一樣的機房中,從而保證系統高可用性。

這說明分區容錯性是分佈式系統的根本,若是分區容錯性不能知足,那使用分佈式系統將失去意義。

此外,可用性對業務系統也尤其重要。在大談用戶體驗的今天,若是業務系統時常出現「系統異常」、響應時間過長等狀況,這使得用戶對系統的好感度大打折扣,在互聯網行業競爭激烈的今天,相同領域的競爭者不甚枚舉,系統的間歇性不可用會立馬致使用戶流向競爭對手。所以,咱們只能經過犧牲一致性來換取系統的可用性分區容錯性。這也就是下面要介紹的BASE理論。

3.6 BASE理論

CAP理論告訴咱們一個悲慘但不得不接受的事實——咱們只能在C、A、P中選擇兩個條件。而對於業務系統而言,咱們每每選擇犧牲一致性來換取系統的可用性和分區容錯性。不過這裏要指出的是,所謂的「犧牲一致性」並非徹底放棄數據一致性,而是犧牲強一致性換取弱一致性。下面來介紹下BASE理論。

  • BA:Basic Available 基本可用
    • 整個系統在某些不可抗力的狀況下,仍然可以保證「可用性」,即必定時間內仍然可以返回一個明確的結果。只不過「基本可用」和「高可用」的區別是:
      • 「必定時間」能夠適當延長 當舉行大促時,響應時間能夠適當延長
      • 給部分用戶返回一個降級頁面 給部分用戶直接返回一個降級頁面,從而緩解服務器壓力。但要注意,返回降級頁面仍然是返回明確結果。
  • S:Soft State:柔性狀態 同一數據的不一樣副本的狀態,能夠不須要實時一致。
  • E:Eventual Consisstency:最終一致性 同一數據的不一樣副本的狀態,能夠不須要實時一致,但必定要保證通過必定時間後仍然是一致的。

3.7 酸鹼平衡

ACID可以保證事務的強一致性,即數據是實時一致的。這在本地事務中是沒有問題的,在分佈式事務中,強一致性會極大影響分佈式系統的性能,所以分佈式系統中遵循BASE理論便可。但分佈式系統的不一樣業務場景對一致性的要求也不一樣。如交易場景下,就要求強一致性,此時就須要遵循ACID理論,而在註冊成功後發送短信驗證碼等場景下,並不須要實時一致,所以遵循BASE理論便可。所以要根據具體業務場景,在ACID和BASE之間尋求平衡。

3.8 分佈式事務協議

下面介紹幾種實現分佈式事務的協議。

3.8.1 兩階段提交協議 2PC

分佈式系統的一個難點是如何保證架構下多個節點在進行事務性操做的時候保持一致性。爲實現這個目的,二階段提交算法的成立基於如下假設:

  • 該分佈式系統中,存在一個節點做爲協調者(Coordinator),其餘節點做爲參與者(Cohorts)。且節點之間能夠進行網絡通訊。
  • 全部節點都採用預寫式日誌,且日誌被寫入後即被保持在可靠的存儲設備上,即便節點損壞不會致使日誌數據的消失。
  • 全部節點不會永久性損壞,即便損壞後仍然能夠恢復。

1. 第一階段(投票階段):

  1. 協調者節點向全部參與者節點詢問是否能夠執行提交操做(vote),並開始等待各參與者節點的響應。
  2. 參與者節點執行詢問發起爲止的全部事務操做,並將Undo信息和Redo信息寫入日誌。(注意:若成功這裏其實每一個參與者已經執行了事務操做)
  3. 各參與者節點響應協調者節點發起的詢問。若是參與者節點的事務操做實際執行成功,則它返回一個"贊成"消息;若是參與者節點的事務操做實際執行失敗,則它返回一個"停止"消息。

2. 第二階段(提交執行階段):

當協調者節點從全部參與者節點得到的相應消息都爲"贊成"時:

  1. 協調者節點向全部參與者節點發出"正式提交(commit)"的請求。
  2. 參與者節點正式完成操做,並釋放在整個事務期間內佔用的資源。
  3. 參與者節點向協調者節點發送"完成"消息。
  4. 協調者節點受到全部參與者節點反饋的"完成"消息後,完成事務。

若是任一參與者節點在第一階段返回的響應消息爲"停止",或者 協調者節點在第一階段的詢問超時以前沒法獲取全部參與者節點的響應消息時:

  1. 協調者節點向全部參與者節點發出"回滾操做(rollback)"的請求。
  2. 參與者節點利用以前寫入的Undo信息執行回滾,並釋放在整個事務期間內佔用的資源。
  3. 參與者節點向協調者節點發送"回滾完成"消息。
  4. 協調者節點受到全部參與者節點反饋的"回滾完成"消息後,取消事務。

無論最後結果如何,第二階段都會結束當前事務。

二階段提交看起來確實可以提供原子性的操做,可是不幸的事,二階段提交仍是有幾個缺點的:

  1. 執行過程當中,全部參與節點都是事務阻塞型的。當參與者佔有公共資源時,其餘第三方節點訪問公共資源不得不處於阻塞狀態。
  2. 參與者發生故障。協調者須要給每一個參與者額外指定超時機制,超時後整個事務失敗。(沒有多少容錯機制)
  3. 協調者發生故障。參與者會一直阻塞下去。須要額外的備機進行容錯。(這個能夠依賴後面要講的Paxos協議實現HA)
  4. 二階段沒法解決的問題:協調者再發出commit消息以後宕機,而惟一接收到這條消息的參與者同時也宕機了。那麼即便協調者經過選舉協議產生了新的協調者,這條事務的狀態也是不肯定的,沒人知道事務是否被已經提交。

爲此,Dale Skeen和Michael Stonebraker在「A Formal Model of Crash Recovery in a Distributed System」中提出了三階段提交協議(3PC)。

3.8.2 三階段提交協議 3PC

與兩階段提交不一樣的是,三階段提交有兩個改動點。

  • 引入超時機制。同時在協調者和參與者中都引入超時機制。
  • 在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段以前各參與節點的狀態是一致的。

也就是說,除了引入超時機制以外,3PC把2PC的準備階段再次一分爲二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。

1. CanCommit階段

3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參與者發送commit請求,參與者若是能夠提交就返回Yes響應,不然返回No響應。

  1. 事務詢問

    協調者向參與者發送CanCommit請求。詢問是否能夠執行事務提交操做。而後開始等待參與者的響應。

  2. 響應反饋

    參與者接到CanCommit請求以後,正常狀況下,若是其自身認爲能夠順利執行事務,則返回Yes響應,並進入預備狀態。不然反饋No

2. PreCommit階段

協調者根據參與者的反應狀況來決定是否能夠記性事務的PreCommit操做。根據響應狀況,有如下兩種可能。 假如協調者從全部的參與者得到的反饋都是Yes響應,那麼就會執行事務的預執行。

  1. 發送預提交請求

    協調者向參與者發送PreCommit請求,並進入Prepared階段。

  2. 事務預提交

    參與者接收到PreCommit請求後,會執行事務操做,並將undo和redo信息記錄到事務日誌中。

  3. 響應反饋

    若是參與者成功的執行了事務操做,則返回ACK響應,同時開始等待最終指令。

假若有任何一個參與者向協調者發送了No響應,或者等待超時以後,協調者都沒有接到參與者的響應,那麼就執行事務的中斷。

  1. 發送中斷請求

    協調者向全部參與者發送abort請求。

  2. 中斷事務

    參與者收到來自協調者的abort請求以後(或超時以後,仍未收到協調者的請求),執行事務的中斷。

3. doCommit階段 該階段進行真正的事務提交,也能夠分爲如下兩種狀況。

該階段進行真正的事務提交,也能夠分爲如下兩種狀況。

3.1 執行提交

  1. 發送提交請求

    協調接收到參與者發送的ACK響應,那麼他將從預提交狀態進入到提交狀態。並向全部參與者發送doCommit請求。

  2. 事務提交

    參與者接收到doCommit請求以後,執行正式的事務提交。並在完成事務提交以後釋放全部事務資源。

  3. 響應反饋

    事務提交完以後,向協調者發送Ack響應。

  4. 完成事務

    協調者接收到全部參與者的ack響應以後,完成事務。

3.2 中斷事務 協調者沒有接收到參與者發送的ACK響應(多是接受者發送的不是ACK響應,也可能響應超時),那麼就會執行中斷事務。

  1. 發送中斷請求

    協調者向全部參與者發送abort請求

  2. 事務回滾

    參與者接收到abort請求以後,利用其在階段二記錄的undo信息來執行事務的回滾操做,並在完成回滾以後釋放全部的事務資源。

  3. 反饋結果

    參與者完成事務回滾以後,向協調者發送ACK消息

  4. 中斷事務

    協調者接收到參與者反饋的ACK消息以後,執行事務的中斷。

3.9 分佈式事務的解決方案

分佈式事務的解決方案有以下幾種:

  • 全局消息
  • 基於可靠消息服務的分佈式事務
  • TCC
  • 最大努力通知

3.9.1 方案1:全局事務(DTP模型)

全局事務基於DTP模型實現。DTP是由X/Open組織提出的一種分佈式事務模型——X/Open Distributed Transaction Processing Reference Model。它規定了要實現分佈式事務,須要三種角色:

  • AP:Application 應用系統

    它就是咱們開發的業務系統,在咱們開發的過程當中,可使用資源管理器提供的事務接口來實現分佈式事務。

  • TM:Transaction Manager 事務管理器

    • 分佈式事務的實現由事務管理器來完成,它會提供分佈式事務的操做接口供咱們的業務系統調用。這些接口稱爲TX接口。
    • 事務管理器還管理着全部的資源管理器,經過它們提供的XA接口來同一調度這些資源管理器,以實現分佈式事務。
    • DTP只是一套實現分佈式事務的規範,並無定義具體如何實現分佈式事務,TM能夠採用2PC、3PC、Paxos等協議實現分佈式事務。
  • RM:Resource Manager 資源管理器

    • 可以提供數據服務的對象均可以是資源管理器,好比:數據庫、消息中間件、緩存等。大部分場景下,數據庫即爲分佈式事務中的資源管理器。
    • 資源管理器可以提供單數據庫的事務能力,它們經過XA接口,將本數據庫的提交、回滾等能力提供給事務管理器調用,以幫助事務管理器實現分佈式的事務管理。
    • XA是DTP模型定義的接口,用於向事務管理器提供該資源管理器(該數據庫)的提交、回滾等能力。
    • DTP只是一套實現分佈式事務的規範,RM具體的實現是由數據庫廠商來完成的。
  1. 有沒有基於DTP模型的分佈式事務中間件?
  1. DTP模型有啥優缺點?

3.9.2 方案2:基於可靠消息服務的分佈式事務

這種實現分佈式事務的方式須要經過消息中間件來實現。假設有A和B兩個系統,分別能夠處理任務A和任務B。此時系統A中存在一個業務流程,須要將任務A和任務B在同一個事務中處理。下面來介紹基於消息中間件來實現這種分佈式事務。

 

title

 

  • 在系統A處理任務A前,首先向消息中間件發送一條消息
  • 消息中間件收到後將該條消息持久化,但並不投遞。此時下游系統B仍然不知道該條消息的存在。
  • 消息中間件持久化成功後,便向系統A返回一個確認應答;
  • 系統A收到確認應答後,則能夠開始處理任務A;
  • 任務A處理完成後,向消息中間件發送Commit請求。該請求發送完成後,對系統A而言,該事務的處理過程就結束了,此時它能夠處理別的任務了。 但commit消息可能會在傳輸途中丟失,從而消息中間件並不會向系統B投遞這條消息,從而系統就會出現不一致性。這個問題由消息中間件的事務回查機制完成,下文會介紹。
  • 消息中間件收到Commit指令後,便向系統B投遞該消息,從而觸發任務B的執行;
  • 當任務B執行完成後,系統B向消息中間件返回一個確認應答,告訴消息中間件該消息已經成功消費,此時,這個分佈式事務完成。

上述過程能夠得出以下幾個結論:

  1. 消息中間件扮演者分佈式事務協調者的角色。
  2. 系統A完成任務A後,到任務B執行完成之間,會存在必定的時間差。在這個時間差內,整個系統處於數據不一致的狀態,但這短暫的不一致性是能夠接受的,由於通過短暫的時間後,系統又能夠保持數據一致性,知足BASE理論。

上述過程當中,若是任務A處理失敗,那麼須要進入回滾流程,以下圖所示:

title

 

  • 若系統A在處理任務A時失敗,那麼就會向消息中間件發送Rollback請求。和發送Commit請求同樣,系統A發完以後即可以認爲回滾已經完成,它即可以去作其餘的事情。
  • 消息中間件收到回滾請求後,直接將該消息丟棄,而不投遞給系統B,從而不會觸發系統B的任務B。

此時系統又處於一致性狀態,由於任務A和任務B都沒有執行。

上面所介紹的Commit和Rollback都屬於理想狀況,但在實際系統中,Commit和Rollback指令都有可能在傳輸途中丟失。那麼當出現這種狀況的時候,消息中間件是如何保證數據一致性呢?——答案就是超時詢問機制。

 

title

 

系統A除了實現正常的業務流程外,還需提供一個事務詢問的接口,供消息中間件調用。當消息中間件收到一條事務型消息後便開始計時,若是到了超時時間也沒收到系統A發來的Commit或Rollback指令的話,就會主動調用系統A提供的事務詢問接口詢問該系統目前的狀態。該接口會返回三種結果:

  • 提交

    若得到的狀態是「提交」,則將該消息投遞給系統B。

  • 回滾

    若得到的狀態是「回滾」,則直接將條消息丟棄。

  • 處理中

    若得到的狀態是「處理中」,則繼續等待。

消息中間件的超時詢問機制可以防止上游系統因在傳輸過程當中丟失Commit/Rollback指令而致使的系統不一致狀況,並且能下降上游系統的阻塞時間,上游系統只要發出Commit/Rollback指令後即可以處理其餘任務,無需等待確認應答。而Commit/Rollback指令丟失的狀況經過超時詢問機制來彌補,這樣大大下降上游系統的阻塞時間,提高系統的併發度。

下面來講一說消息投遞過程的可靠性保證。 當上遊系統執行完任務並向消息中間件提交了Commit指令後,即可以處理其餘任務了,此時它能夠認爲事務已經完成,接下來消息中間件**必定會保證消息被下游系統成功消費掉!**那麼這是怎麼作到的呢?這由消息中間件的投遞流程來保證。

消息中間件向下遊系統投遞完消息後便進入阻塞等待狀態,下游系統便當即進行任務的處理,任務處理完成後便向消息中間件返回應答。消息中間件收到確認應答後便認爲該事務處理完畢!

若是消息在投遞過程當中丟失,或消息的確認應答在返回途中丟失,那麼消息中間件在等待確認應答超時以後就會從新投遞,直到下游消費者返回消費成功響應爲止。固然,通常消息中間件能夠設置消息重試的次數和時間間隔,好比:當第一次投遞失敗後,每隔五分鐘重試一次,一共重試3次。若是重試3次以後仍然投遞失敗,那麼這條消息就須要人工干預。

title

 

 

title

 

有的同窗可能要問:消息投遞失敗後爲何不回滾消息,而是不斷嘗試從新投遞?

這就涉及到整套分佈式事務系統的實現成本問題。 咱們知道,當系統A將向消息中間件發送Commit指令後,它便去作別的事情了。若是此時消息投遞失敗,須要回滾的話,就須要讓系統A事先提供回滾接口,這無疑增長了額外的開發成本,業務系統的複雜度也將提升。對於一個業務系統的設計目標是,在保證性能的前提下,最大限度地下降系統複雜度,從而可以下降系統的運維成本。

不知你們是否發現,上游系統A向消息中間件提交Commit/Rollback消息採用的是異步方式,也就是當上遊系統提交完消息後即可以去作別的事情,接下來提交、回滾就徹底交給消息中間件來完成,而且徹底信任消息中間件,認爲它必定能正確地完成事務的提交或回滾。然而,消息中間件向下遊系統投遞消息的過程是同步的。也就是消息中間件將消息投遞給下游系統後,它會阻塞等待,等下游系統成功處理完任務返回確認應答後才取消阻塞等待。爲何這二者在設計上是不一致的呢?

首先,上游系統和消息中間件之間採用異步通訊是爲了提升系統併發度。業務系統直接和用戶打交道,用戶體驗尤其重要,所以這種異步通訊方式可以極大程度地下降用戶等待時間。此外,異步通訊相對於同步通訊而言,沒有了長時間的阻塞等待,所以系統的併發性也大大增長。但異步通訊可能會引發Commit/Rollback指令丟失的問題,這就由消息中間件的超時詢問機制來彌補。

那麼,消息中間件和下游系統之間爲何要採用同步通訊呢?

異步能提高系統性能,但隨之會增長系統複雜度;而同步雖然下降系統併發度,但實現成本較低。所以,在對併發度要求不是很高的狀況下,或者服務器資源較爲充裕的狀況下,咱們能夠選擇同步來下降系統的複雜度。 咱們知道,消息中間件是一個獨立於業務系統的第三方中間件,它不和任何業務系統產生直接的耦合,它也不和用戶產生直接的關聯,它通常部署在獨立的服務器集羣上,具備良好的可擴展性,因此沒必要太過於擔憂它的性能,若是處理速度沒法知足咱們的要求,能夠增長機器來解決。並且,即便消息中間件處理速度有必定的延遲那也是能夠接受的,由於前面所介紹的BASE理論就告訴咱們了,咱們追求的是最終一致性,而非實時一致性,所以消息中間件產生的時延致使事務短暫的不一致是能夠接受的。

3.9.3 方案3:最大努力通知(按期校對)

最大努力通知也被稱爲按期校對,其實在方案二中已經包含,這裏再單獨介紹,主要是爲了知識體系的完整性。這種方案也須要消息中間件的參與,其過程以下:

 

title

 

  • 上游系統在完成任務後,向消息中間件同步地發送一條消息,確保消息中間件成功持久化這條消息,而後上游系統能夠去作別的事情了;
  • 消息中間件收到消息後負責將該消息同步投遞給相應的下游系統,並觸發下游系統的任務執行;
  • 當下遊系統處理成功後,向消息中間件反饋確認應答,消息中間件即可以將該條消息刪除,從而該事務完成。

上面是一個理想化的過程,但在實際場景中,每每會出現以下幾種意外狀況:

  1. 消息中間件向下遊系統投遞消息失敗
  2. 上游系統向消息中間件發送消息失敗

對於第一種狀況,消息中間件具備重試機制,咱們能夠在消息中間件中設置消息的重試次數和重試時間間隔,對於網絡不穩定致使的消息投遞失敗的狀況,每每重試幾回後消息即可以成功投遞,若是超過了重試的上限仍然投遞失敗,那麼消息中間件再也不投遞該消息,而是記錄在失敗消息表中,消息中間件須要提供失敗消息的查詢接口,下游系統會按期查詢失敗消息,並將其消費,這就是所謂的「按期校對」。

若是重複投遞和按期校對都不能解決問題,每每是由於下游系統出現了嚴重的錯誤,此時就須要人工干預。

對於第二種狀況,須要在上游系統中創建消息重發機制。能夠在上游系統創建一張本地消息表,並將 任務處理過程向本地消息表中插入消息 這兩個步驟放在一個本地事務中完成。若是向本地消息表插入消息失敗,那麼就會觸發回滾,以前的任務處理結果就會被取消。若是這量步都執行成功,那麼該本地事務就完成了。接下來會有一個專門的消息發送者不斷地發送本地消息表中的消息,若是發送失敗它會返回重試。固然,也要給消息發送者設置重試的上限,通常而言,達到重試上限仍然發送失敗,那就意味着消息中間件出現嚴重的問題,此時也只有人工干預才能解決問題。

對於不支持事務型消息的消息中間件,若是要實現分佈式事務的話,就能夠採用這種方式。它可以經過重試機制+按期校對實現分佈式事務,但相比於第二種方案,它達到數據一致性的週期較長,並且還須要在上游系統中實現消息重試發佈機制,以確保消息成功發佈給消息中間件,這無疑增長了業務系統的開發成本,使得業務系統不夠純粹,而且這些額外的業務邏輯無疑會佔用業務系統的硬件資源,從而影響性能。

所以,儘可能選擇支持事務型消息的消息中間件來實現分佈式事務,如RocketMQ。

3.9.4 方案4:TCC(兩階段型、補償型)

TCC即爲Try Confirm Cancel,它屬於補償型分佈式事務。顧名思義,TCC實現分佈式事務一共有三個步驟:

  • Try:嘗試待執行的業務
    • 這個過程並未執行業務,只是完成全部業務的一致性檢查,並預留好執行所需的所有資源
  • Confirm:執行業務
    • 這個過程真正開始執行業務,因爲Try階段已經完成了一致性檢查,所以本過程直接執行,而不作任何檢查。而且在執行的過程當中,會使用到Try階段預留的業務資源。
  • Cancel:取消執行的業務
    • 若業務執行失敗,則進入Cancel階段,它會釋放全部佔用的業務資源,並回滾Confirm階段執行的操做。

下面以一個轉帳的例子來解釋下TCC實現分佈式事務的過程。

假設用戶A用他的帳戶餘額給用戶B發一個100元的紅包,而且餘額系統和紅包系統是兩個獨立的系統。

  • Try

    • 建立一條轉帳流水,並將流水的狀態設爲交易中
    • 將用戶A的帳戶中扣除100元(預留業務資源)
    • Try成功以後,便進入Confirm階段
    • Try過程發生任何異常,均進入Cancel階段
  • Confirm

    • 向B用戶的紅包帳戶中增長100元
    • 將流水的狀態設爲交易已完成
    • Confirm過程發生任何異常,均進入Cancel階段
    • Confirm過程執行成功,則該事務結束
  • Cancel

    • 將用戶A的帳戶增長100元
    • 將流水的狀態設爲交易失敗

在傳統事務機制中,業務邏輯的執行和事務的處理,是在不一樣的階段由不一樣的部件來完成的:業務邏輯部分訪問資源實現數據存儲,其處理是由業務系統負責;事務處理部分經過協調資源管理器以實現事務管理,其處理由事務管理器來負責。兩者沒有太多交互的地方,因此,傳統事務管理器的事務處理邏輯,僅須要着眼於事務完成(commit/rollback)階段,而沒必要關注業務執行階段。

TCC全局事務必須基於RM本地事務來實現全局事務

TCC服務是由Try/Confirm/Cancel業務構成的, 其Try/Confirm/Cancel業務在執行時,會訪問資源管理器(Resource Manager,下文簡稱RM)來存取數據。這些存取操做,必需要參與RM本地事務,以使其更改的數據要麼都commit,要麼都rollback。

這一點不難理解,考慮一下以下場景:

 

title

 

假設圖中的服務B沒有基於RM本地事務(以RDBS爲例,可經過設置auto-commit爲true來模擬),那麼一旦[B:Try]操做中途執行失敗,TCC事務框架後續決定回滾全局事務時,該[B:Cancel]則須要判斷[B:Try]中哪些操做已經寫到DB、哪些操做尚未寫到DB:假設[B:Try]業務有5個寫庫操做,[B:Cancel]業務則須要逐個判斷這5個操做是否生效,並將生效的操做執行反向操做。

不幸的是,因爲[B:Cancel]業務也有n(0<=n<=5)個反向的寫庫操做,此時一旦[B:Cancel]也中途出錯,則後續的[B:Cancel]執行任務更加繁重。由於,相比第一次[B:Cancel]操做,後續的[B:Cancel]操做還須要判斷先前的[B:Cancel]操做的n(0<=n<=5)個寫庫中哪幾個已經執行、哪幾個尚未執行,這就涉及到了冪等性問題。而對冪等性的保障,又極可能還須要涉及額外的寫庫操做,該寫庫操做又會由於沒有RM本地事務的支持而存在相似問題。。。可想而知,若是不基於RM本地事務,TCC事務框架是沒法有效的管理TCC全局事務的。

反之,基於RM本地事務的TCC事務,這種狀況則會很容易處理:[B:Try]操做中途執行失敗,TCC事務框架將其參與RM本地事務直接rollback便可。後續TCC事務框架決定回滾全局事務時,在知道「[B:Try]操做涉及的RM本地事務已經rollback」的狀況下,根本無需執行[B:Cancel]操做。

換句話說,基於RM本地事務實現TCC事務框架時,一個TCC型服務的cancel業務要麼執行,要麼不執行,不須要考慮部分執行的狀況。

TCC事務框架應該提供Confirm/Cancel服務的冪等性保障

通常認爲,服務的冪等性,是指針對同一個服務的屢次(n>1)請求和對它的單次(n=1)請求,兩者具備相同的反作用。

在TCC事務模型中,Confirm/Cancel業務可能會被重複調用,其緣由不少。好比,全局事務在提交/回滾時會調用各TCC服務的Confirm/Cancel業務邏輯。執行這些Confirm/Cancel業務時,可能會出現如網絡中斷的故障而使得全局事務不能完成。所以,故障恢復機制後續仍然會從新提交/回滾這些未完成的全局事務,這樣就會再次調用參與該全局事務的各TCC服務的Confirm/Cancel業務邏輯。

既然Confirm/Cancel業務可能會被屢次調用,就須要保障其冪等性。 那麼,應該由TCC事務框架來提供冪等性保障?仍是應該由業務系統自行來保障冪等性呢? 我的認爲,應該是由TCC事務框架來提供冪等性保障。若是僅僅只是極個別服務存在這個問題的話,那麼由業務系統來負責也是能夠的;然而,這是一類公共問題,毫無疑問,全部TCC服務的Confirm/Cancel業務存在冪等性問題。TCC服務的公共問題應該由TCC事務框架來解決;並且,考慮一下由業務系統來負責冪等性須要考慮的問題,就會發現,這無疑增大了業務系統的複雜度。

4. 服務部署

當咱們完成業務代碼的開發後,就須要進入部署階段。在部署過程當中,咱們將會引入持續集成、持續交付、持續部署,而且闡述如何在微服務中使用他們。

4.1 持續集成、持續部署、持續交付

在介紹這三個概念以前,咱們首先來了解下使用了這三個概念以後的軟件開發流程,以下圖所示:

 

首先是代碼的開發階段,當代碼完成開發後須要提交至代碼倉庫,此時須要對代碼進行編譯、打包,打包後的產物被稱爲「構建物」,如:對Web項目打包以後生成的war包、jar包就是一種構建物。此時的構建物雖然沒有語法錯誤,但其質量是沒法保證的,必須通過一系列嚴格的測試以後才能具備部署到生產環境的資格。咱們通常會給系統分配多套環境,如開發環境、測試環境、預發環境、生產環境。每套環境都有它測試標準,當構建物完成了一套環境的測試,並達到交付標準時,就會自動進入下一個環境。構建物依次會通過這四套環境,構建物每完成一套環境的驗證,就具有交付給下一套環境的資格。當完成預發環境的驗證後,就具有的上線的資格。

測試和交付過程是相互伴隨的,每一套環境都有各自的測試標準。如在開發環境中,當代碼提交後須要經過編譯、打包生成構建物,在編譯的過程當中會對代碼進行單元測試,若是有任何測試用例沒經過,整個構建流程就會被停止。此時開發人員須要當即修復問題,並從新提交代碼、從新編譯打包。

當單元測試經過以後,構建物就具有了進入測試環境的資格,此時它會被自動部署到測試環境,進行新一輪的測試。在測試環境中,通常須要完成接口測試和人工測試。接口測試由自動化腳本完成,這個過程完成後還須要人工進行功能性測試。人工測試完成後,須要手動觸發進入下一個階段。

此時構建物將會被部署到預發環境。預發環境是一種「類生產環境」,它和生產環境的服務器配置須要保持高度一致。在預發環境中,通常須要對構建物進行性能測試,瞭解其性能指標是否能知足上線的要求。當經過預發驗證後,構建物已經具有了上線的資格,此時它能夠隨時上線。

上述過程涵蓋了持續集成、持續交付、持續部署,那麼下面咱們就從理論角度來介紹這三個概念。

4.1.1 持續集成

「集成」指的是修改後/新增的代碼向代碼倉庫合併的過程,而「持續集成」指的是代碼高頻率合併。這樣有什麼好處呢?你們不妨想想,若是咱們集成代碼的頻率變高了,那麼每次集成的代碼量就會變少,因爲每次集成的時候都會進行單元測試,從而當出現問題的時候問題出現的範圍就被縮小的,這樣就能快速定位到出錯的地方,尋找問題就更容易了。此外,頻繁集成可以使問題儘早地暴露,這樣解決問題的成本也就越低。由於在軟件測試中有這樣一條定律,時間和bug修復的成本成正比,也就是時間越長,bug修復的成本也就越大。因此持續集成可以儘早發現問題,並可以及時修復問題,這對於軟件的質量是很是重要的。

4.1.2 持續部署

「持續部署」指的是當存在多套環境時,當構建物成完上一套環境的測試後,自動部署到下一套環境並進行一系列的測試,直到構建物知足上線的要求爲止。

4.1.3 持續交付

當系統經過了全部的測試以後,就具有了部署到生產環境的資格,這個過程也就被稱爲「交付」。「持續交付」指的是每一個版本的構建物都具備上線的資格,這就要求每當代碼庫中有新的版本後,都須要自動觸發構建、測試、部署、交付等一系列流程,當構建物在某個階段的測試未經過時,就須要開發人員當即解決這個問題,並從新構建,從而保證每一個版本的構建物都具有上線的資格,能夠隨時部署到生產環境中。

4.2 微服務與持續集成

當咱們瞭解了持續集成後,下面來介紹微服務如何與持續集成相整合。當咱們對系統進行了微服務化後,本來單一的系統被拆分紅多個課獨立運行的微服務。單服務系統的持續集成較爲簡單,代碼庫、構建和構建物之間都是一對一的關係。然而,當咱們將系統微服務化後,持續集成就變得複雜了。下面介紹兩種在微服務中使用持續集成的方法,分別是單庫多構建和多庫多構建,並依次介紹這兩種方式的優缺點及使用場景。

4.2.1 單庫多構建

「單庫」指的是單個代碼倉庫,即整個系統的多個模塊的代碼均由一個代碼倉庫維護。「多構建」指的是持續集成平臺中的構建項目會有多個,每一個構建都會生成一個構建物,以下如所示:

 

 

在這種持續集成的模式中,整個項目的全部代碼均在同一個代碼倉庫中維護。但在持續集成平臺中,每一項服務都有各自獨立的構建,從而持續集成平臺可以爲每一項服務產出各自的構建物。

這種持續集成的模式在微服務架構中顯然是不合理的。首先,一個系統的可能會有不少服務構成,若是將這些服務的代碼均在同一個代碼倉庫中維護,那麼一個程序員在開發服務A代碼的時候頗有可能會由於疏忽,修改了服務B的代碼,此時服務B構建以後就會存在安全隱患,若是這個問題在服務B上線前被發現,那麼還好,但無疑增長了額外的工做量;但若是這個問題及其隱諱,致使以前的測試用例沒有覆蓋到,從而服務B會帶着這個問題進入生產環境,這可能會給企業帶來巨大的損失。因此,在微服務架構中,儘可能選擇多庫多構建模式來實現持續集成,它將帶來更大的安全性。

雖然這種模式不合理,但它也有存在的必要性,當咱們在項目建設初期的時候,這種模式會給咱們帶來更多的便利性。由於項目在建設初期,服務之間的邊界每每是比較模糊的,並且須要通過一段時間的演化纔可以構建出穩定的邊界。因此若是在項目建設初期直接使用微服務架構,那麼服務邊界頻繁地調整會極大增長系統開發的複雜度,你要知道,在多個系統之間調整邊界比在單個系統的多個模塊之間調整邊界的成本要高不少。因此在項目建設初期,咱們可使用單服務結構,服務內部採用模塊做爲將來各個微服務的邊界,當系統演化出較爲清晰、穩定的邊界後再將系統拆分紅多個微服務。此時代碼在同一個代碼倉庫中維護是合理的,這也符合敏捷開發中快速迭代的理念。

4.2.2 多庫多構建

 

 

當系咱們的系統擁有了穩定、清晰的邊界後,就能夠將系統向微服務架構演進。與此同時,持續集成模式也能夠從單庫多構建向多庫多構建演進。

在多庫多構建模式中,每項服務都有各自獨立的代碼倉庫,代碼倉庫之間互不干擾。開發團隊只需關注屬於本身的某幾項服務的代碼倉庫便可。每一項服務都有各自獨立的構建。這種方式邏輯清晰,維護成本較低,並且能避免單庫多構建模式中出現的影響其餘服務的問題。

4.3 微服務構建物

持續集成平臺對源碼編譯、大包後生成的產物稱爲「構建物」。根據打包的粒度不一樣,能夠將構建物分爲以下三種:平臺構建物、操做系統構建物和鏡像構建物。

4.3.1 平臺構建物

平臺構建物指的是由某一特定平臺生成的構建物,好比JVM平臺生成的Jar包、War包,Python生成的egg等都屬於平臺構建物。但平臺構建物運行須要部署在特定的容器中,如war須要運行在Servlet容器中,而Servlet容器又依賴的JVM環境。因此若要部署平臺構建物,則須要先給它們提供好運行所需的環境。

4.3.2 操做系統構建物

操做系統構建物是將系統打包成一個操做系統可執行程序,,如CentOS的RPM包、Windows的MSI包等。這些安裝包能夠在操做系統上直接安裝運行。但和平臺構建物相同的是,操做系統構建物每每也須要依賴於其餘環境,因此也須要在部署以前搭建好安裝包所需的依賴。此外,配置操做系統構建物的複雜度較大,構建的成本較高,因此通常不使用這種方式,這裏僅做介紹。

4.3.3 鏡像構建物

平臺構建物和操做系統構建物都有一個共同的缺點就是須要安裝構建物運行的額外依賴,增長部署複雜度,而鏡像構建物能很好地解決這個問題。

咱們能夠把鏡像理解成一個小型操做系統,這個操做系統中包含了系統運行所需的全部依賴,並將系統也部署在這個「操做系統」中。這樣當持續集成平臺構建完這個鏡像後,就能夠直接運行它,無需任何依賴的安裝,從而極大簡化了構建的複雜度。可是,鏡像每每比較龐大,構建鏡像的過程也較長,從而當咱們將生成的鏡像從持續集成服務器發佈到部署服務器的時間將會很長,這無疑下降了部署的效率。不過好在Docker的出現解決了這一問題。持續集成平臺在構建過程當中並不須要生成一個鏡像,而只需生成一個鏡像的Dockerfile文件便可。Dockerfile文件用命令定義了鏡像所包含的內容,以及鏡像建立的過程。從而持續集成服務器只需將這個體積較小的鏡像文件發佈到部署服務器上便可。而後部署服務器會經過docker build命令基於這個Dockerfile文件建立鏡像,並建立該鏡像的容器,從而完成服務的部署。

相對於平臺構建物和操做系統構建物而言,鏡像構建物在部署時不須要安裝額外的環境依賴,它把環境依賴的配置都在持續集成平臺構建Dockerfile文件時完成,從而簡化了部署的過程。

相關文章
相關標籤/搜索