概念:微服務就是一些可獨立運行、可協同作業的小的服務,微服務是現在特別流行的服務,微服務的字面意思是大家都很好理解,就是將系統拆分成很多小的獨立系統,每個系統我們看成一個服務,從存盤,快取,部署相對獨立,通過服務的小型化、原子化以及分布式架構的彈性伸縮和高可用性,可以實作業務之間的松耦合、業務的靈活調整組合以及系統的高可用性,為業務創新和業務持續提供了一個良好的基礎平臺,
從概念中我們可以提取三個關鍵詞:可獨立運行、可協同作業、小,這三個詞高度概括了微服務的核心特性,下面我們就對這三個詞作詳細解釋,
可獨立運行
微服務是一個個可以獨立開發、獨立部署、獨立運行的系統或者行程,
可協同作業
采用了微服務架構后,整個系統被拆分成多個微服務,這些服務之間往往不是完全獨立的,在業務上存在一定的耦合,即一個服務可能需要使用另一個服務所提供的功能,這就是所謂的“可協同作業”,與單服務應用不同的是,多個微服務之間的呼叫時通過RPC通信來實作,而非單服務的本地呼叫,所以通信的成本相對要高一些,但帶來的好處也是可觀的,
小而美
微服務的思想是,將一個擁有復雜功能的龐大系統,按照業務功能,拆分成多個相互獨立的子系統,這些子系統則被稱為“微服務”,每個微服務只承擔某一項職責,從而相對于單服務應用來說,微服務的體積是“小”的,小也就意味著每個服務承擔的職責變少,根據單一職責原則,我們在系統設計時,要盡量使得每一項服務只承擔一項職責,從而實作系統的“高內聚”,
微服務的優點
易于擴展
在單服務應用中,如果目前性能到達瓶頸,無法支撐目前的業務量,此時一般采用集群模式,即增加服務器集群的節點,并將這個單服務應用“復制”到所有的節點上,從而提升整體性能,然而這種擴展的粒度是比較粗糙的,如果只是系統中某一小部分存在性能問題,在單服務應用中,也要將整個應用進行擴展,這種方式簡單粗暴,無法對癥下藥,而當我們使用了微服務架構后,如果某一項服務的性能到達瓶頸,那么我們只需要增加該服務的節點數即可,其他服務無需變化,這種擴展更加具有針對性,能夠充分利用計算機硬體/軟體資源,而且只擴展單個服務影響的范圍較小,從而系統出錯的概率也就越低,
部署簡單
對于單服務應用而言,所有代碼均在一個專案中,從而導致任何微小的改變都需要將整個專案打包、發布、部署,而這一系列操作的代價是高昂的,長此以往,團隊為了降低發布的頻率,會使得每次發布都伴隨著大量的修改,修改越多也就意味著出錯的概率也越大, 當我們采用微服務架構以后,每個服務只承擔少數職責,從而每次只需要發布發生修改的系統,其他系統依然能夠正常運行,波及范圍較小,此外,相對于單服務應用而言,每個微服務系統修改的代碼相對較少,從而部署后出現錯誤的概率也相對較低,
技術異構性
對于單服務應用而言,一個系統的所有模塊均整合在一個專案中,所以這些模塊只能選擇相同的技術,但有些時候,單一技術沒辦法滿足不同的業務需求,如對于專案的演算法團隊而言,函式試編程語言可能更適合演算法的開發,而對于業務開發團隊而言,類似于Java的強型別語言具有更高的穩定性,然而在單服務應用中只能互相權衡,選擇同一種語言,而當我們使用微服務結構后,這個問題就能夠引刃而解,我們將一個完整的系統拆分成了多個獨立的服務,從而每個服務都可以根據各自不同的特點,選擇最為合適的技術體系,
當然,并不是所有的微服務系統都具備技術異構性,要實作技術異構性,必須保證所有服務都提供通用介面,我們知道,在微服務系統中,服務之間采用RPC介面通信,而實作RPC通信的方式有很多,有一些RPC通信方式與語言強耦合,如Java的RMI技術,它就要求通信的雙方都必須采用Java語言開發,當然,也有一些RPC通信方式與語言無關,如基于HTTP協議的REST,這種通信方式對通信雙方所采用的語言沒有做任何限制,只要通信程序中傳輸的資料遵循REST規范即可,當然,與語言無關也就意味著通信雙方沒有型別檢查,從而會提高出錯的概率,所以,究竟選擇與語言無關的RPC通信方式,還是選擇與語言強耦合的RPC通信方式,需要我們根據實際的業務場景合理地分析,
資料庫的服務化切分
什么是“分庫分表”?
隨著大資料時代的到來,業務系統的資料量日益增大,資料存盤能力逐漸成為影響系統性能的瓶頸,目前主流的關系型資料庫單表存盤上限為1000萬條記錄,而這一存盤能力顯然已經無法滿足大資料背景下的業務系統存盤要求了,隨著微服務架構、分布式存盤等概念的出現,資料存盤問題也漸漸迎來了轉機,而資料分片是目前解決海量資料持久化存盤與高效查詢的一種重要手段,資料分庫分表的程序在系統設計階段完成,要求系統設計人員根據系統預期的業務量,將未來可能出現瓶頸的資料庫、資料表按照一定規則拆分成多個庫、多張表,這些資料庫和資料表需要部署在不同的服務器上,從而將資料讀寫壓力分攤至集群中的各個節點,提升資料庫整體處理能力,避免出現讀寫瓶頸的現象,
目前資料分片的方式一共有兩種:離散分片和連續分片,
離散分片是按照資料的某一欄位哈希取模后進行分片存盤,只要哈希演算法選擇得當,資料就會均勻地分布在不同的分片中,從而將讀寫壓力平均分配給所有分片,整體上提升資料的讀寫能力,然而,離散存盤要求資料之間有較強的獨立性,但實際業務系統并非如此,不同分片之間的資料往往存在一定的關聯性,因此在某些場景下需要跨分片連接查詢,由于目前所有的關系型資料庫出于安全性考慮,均不支持跨庫連接,因此,跨庫操作需要由資料分庫分表中間件來完成,這極大影響資料的查詢效率,此外,當資料存盤能力出現瓶頸需要擴容時,離散分片規則需要將所有資料重新進行哈希取模運算,這無疑成為限制系統可擴展性的一個重要因素,雖然,一致性哈希能在一定程度上減少系統擴容時的資料遷移,但資料遷移問題仍然不可避免,對于一個已經上線運行的系統而言,系統停止對外服務進行資料遷移的代價太大,
第二種資料分片的方式即為連續分片,它能解決系統擴容時產生的資料遷移問題,這種方式要求資料按照時間或連續自增主鍵連續存盤,從而一段時間內的資料或相鄰主鍵的資料會被存盤在同一個分片中,當需要增加分片時,不會影響現有的分片,因此,連續分片能解決擴容所帶來的資料遷移問題,但是,資料的存盤時間和讀寫頻率往往呈正比,也就是大量的讀寫往往都集中在最新存盤的那一部分資料,這就會導致熱點問題,并不能起到分攤讀寫壓力的初衷,
資料庫擴展的幾種方式
資料庫擴展一共有四種分配方式,分別是:垂直分庫、垂直分表、水平分表、水平資料分片,每一種策略都有各自的適用場景,
垂直分庫
垂直分庫即是將一個完整的資料庫根據業務功能拆分成多個獨立的資料庫,這些資料庫可以運行在不同的服務器上,從而提升資料庫整體的資料讀寫性能,這種方式在微服務架構中非常常用,微服務架構的核心思想是將一個完整的應用按照業務功能拆分成多個可獨立運行的子系統,這些子系統稱為“微服務”,各個服務之間通過RPC介面通信,這樣的結構使得系統耦合度更低、更易于擴展,垂直分庫的理念與微服務的理念不謀而合,可以將原本完整的資料按照微服務拆分系統的方式,拆分成多個獨立的資料庫,使得每個微服務系統都有各自獨立的資料庫,從而可以避免單個資料庫節點壓力過大,影響系統的整體性能,如下圖所示,

垂直分表
垂直分表如果一張表的欄位非常多,那么很有可能會引起資料的跨頁存盤,這會造成資料庫額外的性能開銷,而垂直分表可以解決這個問題,垂直分表就是將一張表中不常用的欄位拆分到另一張表中,從而保證第一章表中的欄位較少,避免出現資料庫跨頁存盤的問題,從而提升查詢效率,而另一張表中的資料通過外鍵與第一張表進行關聯,如下圖所示,

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

水平分庫分表
水平資料分片與資料分片區別在于:水平資料分片首先將資料表進行水平拆分,然后按照某一分片規則存盤在多臺資料庫服務器上,從而將單庫的壓力分攤到了多庫上,從而避免因為資料庫硬體資源有限導致的資料庫性能瓶頸,如下圖所示,
分庫分表的幾種方式
目前常用的資料分片策略有兩種,分別是連續分片和離散分片,
離散分片
離散分片是指將資料打散之后均勻地存盤在邏輯表的各個分片中,從而使的對同一張邏輯表的資料讀取操作均勻地落在不同庫的不同表上,從而提高讀寫速度,離散分片一般以哈希取模的方式實作,比如:一張邏輯表有4個分片,那么在讀寫資料的時候,中間件首先會取得分片欄位的哈希值,然后再模以4,從而計算出該條記錄所在的分片,在這種方法中,只要哈希演算法選的好,那么資料分片將會比較均勻,從而資料讀寫就會比較均勻地落在各個分片上,從而就有較高的讀寫效率,但是,這種方式也存在一個最大的缺陷——資料庫擴容成本較高,采用這種方式,如果需要再增加分片,原先的分片演算法將失效,并且所有記錄都需要重新計算所在分片的位置,對于一個已經上線的系統來說,行級別的資料遷移成本相當高,而且由于資料遷移期間系統仍在運行,仍有新資料產生,從而無法保證遷移程序資料的一致性,如果為了避免這個問題而停機遷移,那必然會對業務造成巨大影響,當然,如果為了避免資料遷移,在一開始的時候就分片較多的分片,那需要承擔較高的費用,這對于中小公司來說是無法承受的,
連續分片
連續分片指的是按照某一種分片規則,將某一個區間內的資料存盤在同一個分片上,比如按照時間分片,每個月生成一張物理表,那么在讀寫資料時,直接根據當前時間就可以找到資料所在的分片,再比如可以按照記錄ID分片,這種分片方式要求ID需要連續遞增,由于Mysql資料庫單表支持最大的記錄數約為1000萬,因此我們可以根據記錄的ID,使得每個分片存盤1000萬條記錄,當目前的記錄數即將到達存盤上限時,我們只需增加分片即可,原有的資料無需遷移,連續分片的一個最大好處就是方便擴容,因為它不需要任何的資料遷移,但是,連續分片有個最大的缺點就是熱點問題,連續分片使得新插入的資料集中在同一個分片上,而往往新插入的資料讀寫頻率較高,因此,讀寫操作都會集中在最新的分片上,從而無法體現資料分片的優勢,
引入分庫分表中間件后面臨的問題
跨庫操作
在關系型資料庫中,多張表之間往往存在關聯,我們在開發程序中需要使用JOIN操作進行多表連接,但是當我們使用了分庫分表模式后,由于資料庫廠商處于安全考慮,不允許跨庫JOIN操作,從而如果需要連接的兩張表被分到不同的庫中后,就無法使用SQL提供的JOIN關鍵字來實作表連接,我們可能需要在業務系統層面,通過多次SQL查詢,完成資料的組裝和拼接,這一方面會增加業務系統的復雜度,另一方面會增加業務系統的負載, 因此,當我們使用分庫分表模式時,需要根據具體的業務場景,合理地設定分片策略、設定分片欄位,這將會在本文的后續章節中介紹,
分布式事務
我們知道,資料庫提供了事務的功能,以保證資料一致性,然而,這種事務只是針對單資料庫而言的,資料庫廠商并未提供跨庫事務,因此,當我們使用了分庫分表之后,就需要我們在業務系統層面實作分布式事務,關于分布式事務的詳細內容,可以參考筆者的另一篇文章,
現有分庫分表中間件的橫向對比
Cobar實作資料庫的透明分庫,讓開發人員能夠在無感知的情況下操縱資料庫集群,從而簡化資料庫的編程模型,然而Cobar僅實作了分庫功能,并未實作分表功能,分庫可以解決單庫IO、CPU、記憶體的瓶頸,但無法解決單表資料量過大的問題,此外,Cobar是一個獨立運行的系統,它處在應用系統與資料庫系統之間,因此增加了額外的部署復雜度,增加了運維成本,
為了解決上述問題,Cobar還推出了一個Cobar-Client專案,它只是一個安裝在應用程式的Jar包,并不是一個獨立運行的系統,一定程度上降低了系統的復雜度,但和Cobar一樣,仍然只支持分庫,并不支持分表,也不支持讀寫分離,
MyCat是基于Cobar二次開發的資料庫中間件,和Cobar相比,它增加了讀寫分離的功能,并修復了Cobar的一些bug,但是,MyCat和Cobar一樣,都是一套需要獨立部署的系統,因此會增加部署的復雜度,提高了后期系統運維的成本,
微服務架構中的分布式事務
眾所周知,資料庫能實作 本地事務 ,也就是在 同一個資料庫中 ,你可以允許一組操作要么全都正確執行,要么全都不執行,這里特別強調了 本地事務 ,也就是目前的資料庫只能支持同一個資料庫中的事務,但現在的系統往往采用微服務架構,業務系統擁有獨立的資料庫,因此就出現了跨多個資料庫的事務需求,這種事務即為“分布式事務”,那么在目前資料庫不支持跨庫事務的情況下,我們應該如何實作分布式事務呢?本文首先會為大家梳理分布式事務的基本概念和理論基礎,然后介紹幾種目前常用的分布式事務解決方案,廢話不多說,那就開始吧~
什么是事務?
事務由一組操作構成,我們希望這組操作能夠全部正確執行,如果這一組操作中的任意一個步驟發生錯誤,那么就需要回滾之前已經完成的操作,也就是同一個事務中的所有操作,要么全都正確執行,要么全都不要執行,
事務的四大特性 ACID
說到事務,就不得不提一下事務著名的四大特性,
原子性
原子性要求,事務是一個不可分割的執行單元,事務中的所有操作要么全都執行,要么全都不執行,
一致性
一致性要求,事務在開始前和結束后,資料庫的完整性約束沒有被破壞,
隔離性
事務的執行是相互獨立的,它們不會相互干擾,一個事務不會看到另一個正在運行程序中的事務的資料,
持久性
持久性要求,一個事務完成之后,事務的執行結果必須是持久化保存的,即使資料庫發生崩潰,在資料庫恢復后事務提交的結果仍然不會丟失,
注意:事務只能保證資料庫的 高可靠性 ,即資料庫本身發生問題后,事務提交后的資料仍然能恢復;而如果不是資料庫本身的故障,如硬碟損壞了,那么事務提交的資料可能就丟失了,這屬于『 高可用性 』的范疇,因此,事務只能保證資料庫的『高可靠性』,而『高可用性』需要整個系統共同配合實作,
事務的隔離級別
這里擴展一下,對事務的 隔離性 做一個詳細的解釋,
在事務的四大特性ACID中,要求的隔離性是一種嚴格意義上的隔離,也就是多個事務是串行執行的,彼此之間不會受到任何干擾,這確實能夠完全保證資料的安全性,但在實際業務系統中,這種方式性能不高,因此,資料庫定義了四種隔離級別,隔離級別和資料庫的性能是呈反比的,隔離級別越低,資料庫性能越高,而隔離級別越高,資料庫性能越差,
事務并發執行會出現的問題
我們先來看一下在不同的隔離級別下,資料庫可能會出現的問題:
更新丟失
當有兩個并發執行的事務,更新同一行資料,那么有可能一個事務會把另一個事務的更新覆寫掉, 當資料庫沒有加任何鎖操作的情況下會發生,
臟讀
一個事務讀到另一個尚未提交的事務中的資料, 該資料可能會被回滾從而失效, 如果第一個事務拿著失效的資料去處理那就發生錯誤了,
不可重復讀
不可重復度的含義:一個事務對同一行資料讀了兩次,卻得到了不同的結果,它具體分為如下兩種情況:
虛讀:在事務1兩次讀取同一記錄的程序中,事務2對該記錄進行了修改,從而事務1第二次讀到了不一樣的記錄,
幻讀:事務1在兩次查詢的程序中,事務2對該表進行了插入、洗掉操作,從而事務1第二次查詢的結果發生了變化,
不可重復讀 與 臟讀 的區別? 臟讀讀到的是尚未提交的資料,而不可重復讀讀到的是已經提交的資料,只不過在兩次讀的程序中資料被另一個事務改過了,
資料庫的四種隔離級別
資料庫一共有如下四種隔離級別:
Read uncommitted 讀未提交
在該級別下,一個事務對一行資料修改的程序中,不允許另一個事務對該行資料進行修改,但允許另一個事務對該行資料讀, 因此本級別下,不會出現更新丟失,但會出現臟讀、不可重復讀,
Read committed 讀提交
在該級別下,未提交的寫事務不允許其他事務訪問該行,因此不會出現臟讀;但是讀取資料的事務允許其他事務的訪問該行資料,因此會出現不可重復讀的情況,
Repeatable read 重復讀
在該級別下,讀事務禁止寫事務,但允許讀事務,因此不會出現同一事務兩次讀到不同的資料的情況(不可重復讀),且寫事務禁止其他一切事務,
Serializable 序列化
該級別要求所有事務都必須串行執行,因此能避免一切因并發引起的問題,但效率很低,
隔離級別越高,越能保證資料的完整性和一致性,但是對并發性能的影響也越大,對于多數應用程式,可以優先考慮把資料庫系統的隔離級別設為Read Committed,它能夠避免臟讀取,而且具有較好的并發性能,盡管它會導致不可重復讀、幻讀和第二類丟失更新這些并發問題,在可能出現這類問題的個別場合,可以由應用程式采用悲觀鎖或樂觀鎖來控制,
什么是分布式事務?
到此為止,所介紹的事務都是基于單資料庫的本地事務,目前的資料庫僅支持單庫事務,并不支持跨庫事務,而隨著微服務架構的普及,一個大型業務系統往往由若干個子系統構成,這些子系統又擁有各自獨立的資料庫,往往一個業務流程需要由多個子系統共同完成,而且這些操作可能需要在一個事務中完成,在微服務系統中,這些業務場景是普遍存在的,此時,我們就需要在資料庫之上通過某種手段,實作支持跨資料庫的事務支持,這也就是大家常說的“分布式事務”,
想要了解更多微服務,分布式知識的可以加群:645830685,群中會不定期的分享軟體測驗資源,測驗面試題以及行業資訊,大家可以在群中積極交流技術,
這里舉一個分布式事務的典型例子——用戶下單程序, 當我們的系統采用了微服務架構后,一個電商系統往往被拆分成如下幾個子系統:商品系統、訂單系統、支付系統、積分系統等,整個下單的程序如下:
用戶通過商品系統瀏覽商品,他看中了某一項商品,便點擊下單
此時訂單系統會生成一條訂單
訂單創建成功后,支付系統提供支付功能
當支付完成后,由積分系統為該用戶增加積分
上述步驟2、3、4需要在一個事務中完成,對于傳統單體應用而言,實作事務非常簡單,只需將這三個步驟放在一個方法A中,再用Spring的@Transactional注解標識該方法即可,Spring通過資料庫的事務支持,保證這些步驟要么全都執行完成,要么全都不執行,但在這個微服務架構中,這三個步驟涉及三個系統,涉及三個資料庫,此時我們必須在資料庫和應用系統之間,通過某項黑科技,實作分布式事務的支持,
CAP理論
CAP理論說的是:在一個分布式系統中,最多只能滿足C、A、P中的兩個需求,
CAP的含義:
C:Consistency 一致性
同一資料的多個副本是否實時相同,
A:Availability 可用性
可用性:一定時間內 & 系統回傳一個明確的結果 則稱為該系統可用,
P:Partition tolerance 磁區容錯性
將同一服務分布在多個系統中,從而保證某一個系統宕機,仍然有其他系統提供相同的服務,
CAP理論告訴我們,在分布式系統中,C、A、P三個條件中我們最多只能選擇兩個,那么問題來了,究竟選擇哪兩個條件較為合適呢?
對于一個業務系統來說,可用性和磁區容錯性是必須要滿足的兩個條件,并且這兩者是相輔相成的,業務系統之所以使用分布式系統,主要原因有兩個:
提升整體性能
當業務量猛增,單個服務器已經無法滿足我們的業務需求的時候,就需要使用分布式系統,使用多個節點提供相同的功能,從而整體上提升系統的性能,這就是使用分布式系統的第一個原因,
實作磁區容錯性
單一節點 或 多個節點處于相同的網路環境下,那么會存在一定的風險,萬一該機房斷電、該地區發生自然災害,那么業務系統就全面癱瘓了,為了防止這一問題,采用分布式系統,將多個子系統分布在不同的地域、不同的機房中,從而保證系統高可用性,
這說明磁區容錯性是分布式系統的根本,如果磁區容錯性不能滿足,那使用分布式系統將失去意義,
此外,可用性對業務系統也尤為重要,在大談用戶體驗的今天,如果業務系統時常出現“系統例外”、回應時間過長等情況,這使得用戶對系統的好感度大打折扣,在互聯網行業競爭激烈的今天,相同領域的競爭者不甚列舉,系統的間歇性不可用會立馬導致用戶流向競爭對手,因此,我們只能通過犧牲一致性來換取系統的 可用性 和 磁區容錯性 ,這也就是下面要介紹的BASE理論,
BASE理論
CAP理論告訴我們一個悲慘但不得不接受的事實——我們只能在C、A、P中選擇兩個條件,而對于業務系統而言,我們往往選擇犧牲一致性來換取系統的可用性和磁區容錯性,不過這里要指出的是,所謂的“犧牲一致性”并不是完全放棄資料一致性,而是犧牲 強一致性 換取 弱一致性 ,下面來介紹下BASE理論,
BA:Basic Available 基本可用
整個系統在某些不可抗力的情況下,仍然能夠保證“可用性”,即一定時間內仍然能夠回傳一個明確的結果,只不過“基本可用”和“高可用”的區別是:
“一定時間”可以適當延長 當舉行大促時,回應時間可以適當延長
給部分用戶回傳一個降級頁面 給部分用戶直接回傳一個降級頁面,從而緩解服務器壓力,但要注意,回傳降級頁面仍然是回傳明確結果,
S:Soft State:柔性狀態 同一資料的不同副本的狀態,可以不需要實時一致,
E:Eventual Consisstency:最終一致性 同一資料的不同副本的狀態,可以不需要實時一致,但一定要保證經過一定時間后仍然是一致的,
酸堿平衡
ACID能夠保證事務的強一致性,即資料是實時一致的,這在本地事務中是沒有問題的,在分布式事務中,強一致性會極大影響分布式系統的性能,因此分布式系統中遵循BASE理論即可,但分布式系統的不同業務場景對一致性的要求也不同,如交易場景下,就要求強一致性,此時就需要遵循ACID理論,而在注冊成功后發送短信驗證碼等場景下,并不需要實時一致,因此遵循BASE理論即可,因此要根據具體業務場景,在ACID和BASE之間尋求平衡,
分布式事務協議
下面介紹幾種實作分布式事務的協議,
兩階段提交協議 2PC
分布式系統的一個難點是如何保證架構下多個節點在進行事務性操作的時候保持一致性,為實作這個目的,二階段提交演算法的成立基于以下假設:
該分布式系統中,存在一個節點作為協調者(Coordinator),其他節點作為參與者(Cohorts),且節點之間可以進行網路通信,
所有節點都采用預寫式日志,且日志被寫入后即被保持在可靠的存盤設備上,即使節點損壞不會導致日志資料的消失,
所有節點不會永久性損壞,即使損壞后仍然可以恢復,
- 第一階段(投票階段):
協調者節點向所有參與者節點詢問是否可以執行提交操作(vote),并開始等待各參與者節點的回應,
參與者節點執行詢問發起為止的所有事務操作,并將Undo資訊和Redo資訊寫入日志,(注意:若成功這里其實每個參與者已經執行了事務操作)
各參與者節點回應協調者節點發起的詢問,如果參與者節點的事務操作實際執行成功,則它回傳一個"同意"訊息;如果參與者節點的事務操作實際執行失敗,則它回傳一個"中止"訊息,
- 第二階段(提交執行階段):
當協調者節點從所有參與者節點獲得的相應訊息都為"同意"時:
協調者節點向所有參與者節點發出"正式提交(commit)"的請求,
參與者節點正式完成操作,并釋放在整個事務期間內占用的資源,
參與者節點向協調者節點發送"完成"訊息,
協調者節點受到所有參與者節點反饋的"完成"訊息后,完成事務,
如果任一參與者節點在第一階段回傳的回應訊息為"中止",或者 協調者節點在第一階段的詢問超時之前無法獲取所有參與者節點的回應訊息時:
協調者節點向所有參與者節點發出"回滾操作(rollback)"的請求,
參與者節點利用之前寫入的Undo資訊執行回滾,并釋放在整個事務期間內占用的資源,
參與者節點向協調者節點發送"回滾完成"訊息,
協調者節點受到所有參與者節點反饋的"回滾完成"訊息后,取消事務,
不管最后結果如何,第二階段都會結束當前事務,
二階段提交看起來確實能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點的:
執行程序中,所有參與節點都是事務阻塞型的,當參與者占有公共資源時,其他第三方節點訪問公共資源不得不處于阻塞狀態,
參與者發生故障,協調者需要給每個參與者額外指定超時機制,超時后整個事務失敗,(沒有多少容錯機制)
協調者發生故障,參與者會一直阻塞下去,需要額外的備機進行容錯,(這個可以依賴后面要講的Paxos協議實作HA)
二階段無法解決的問題:協調者再發出commit訊息之后宕機,而唯一接收到這條訊息的參與者同時也宕機了,那么即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,沒人知道事務是否被已經提交,
為此,Dale Skeen和Michael Stonebraker在“A Formal Model of Crash Recovery in a Distributed System”中提出了三階段提交協議(3PC),
三階段提交協議 3PC
與兩階段提交不同的是,三階段提交有兩個改動點,
引入超時機制,同時在協調者和參與者中都引入超時機制,
在第一階段和第二階段中插入一個準備階段,保證了在最后提交階段之前各參與節點的狀態是一致的,
也就是說,除了引入超時機制之外,3PC把2PC的準備階段再次一分為二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段,
- CanCommit階段
3PC的CanCommit階段其實和2PC的準備階段很像,協調者向參與者發送commit請求,參與者如果可以提交就回傳Yes回應,否則回傳No回應,
事務詢問
協調者向參與者發送CanCommit請求,詢問是否可以執行事務提交操作,然后開始等待參與者的回應,
回應反饋
參與者接到CanCommit請求之后,正常情況下,如果其自身認為可以順利執行事務,則回傳Yes回應,并進入預備狀態,否則反饋No
- PreCommit階段
協調者根據參與者的反應情況來決定是否可以記性事務的PreCommit操作,根據回應情況,有以下兩種可能, 假如協調者從所有的參與者獲得的反饋都是Yes回應,那么就會執行事務的預執行,
發送預提交請求
協調者向參與者發送PreCommit請求,并進入Prepared階段,
事務預提交
參與者接收到PreCommit請求后,會執行事務操作,并將undo和redo資訊記錄到事務日志中,
回應反饋
如果參與者成功的執行了事務操作,則回傳ACK回應,同時開始等待最終指令,
假如有任何一個參與者向協調者發送了No回應,或者等待超時之后,協調者都沒有接到參與者的回應,那么就執行事務的中斷,
發送中斷請求
協調者向所有參與者發送abort請求,
中斷事務
參與者收到來自協調者的abort請求之后(或超時之后,仍未收到協調者的請求),執行事務的中斷,
- doCommit階段該階段進行真正的事務提交,也可以分為以下兩種情況,
該階段進行真正的事務提交,也可以分為以下兩種情況,
執行提交
發送提交請求
協調接收到參與者發送的ACK回應,那么他將從預提交狀態進入到提交狀態,并向所有參與者發送doCommit請求,
事務提交
參與者接收到doCommit請求之后,執行正式的事務提交,并在完成事務提交之后釋放所有事務資源,
回應反饋
事務提交完之后,向協調者發送Ack回應,
完成事務
協調者接收到所有參與者的ack回應之后,完成事務,
中斷事務協調者沒有接收到參與者發送的ACK回應(可能是接受者發送的不是ACK回應,也可能回應超時),那么就會執行中斷事務,
發送中斷請求
協調者向所有參與者發送abort請求
事務回滾
參與者接收到abort請求之后,利用其在階段二記錄的undo資訊來執行事務的回滾操作,并在完成回滾之后釋放所有的事務資源,
反饋結果
參與者完成事務回滾之后,向協調者發送ACK訊息
中斷事務
協調者接收到參與者反饋的ACK訊息之后,執行事務的中斷,
分布式事務的解決方案
分布式事務的解決方案有如下幾種:
全域訊息
基于可靠訊息服務的分布式事務
TCC
最大努力通知
方案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具體的實作是由資料庫廠商來完成的,
有沒有基于DTP模型的分布式事務中間件?
DTP模型有啥優缺點?
方案2:基于可靠訊息服務的分布式事務
這種實作分布式事務的方式需要通過訊息中間件來實作,假設有A和B兩個系統,分別可以處理任務A和任務B,此時系統A中存在一個業務流程,需要將任務A和任務B在同一個事務中處理,下面來介紹基于訊息中間件來實作這種分布式事務,
在系統A處理任務A前,首先向訊息中間件發送一條訊息
訊息中間件收到后將該條訊息持久化,但并不投遞,此時下游系統B仍然不知道該條訊息的存在,
訊息中間件持久化成功后,便向系統A回傳一個確認應答;
系統A收到確認應答后,則可以開始處理任務A;
任務A處理完成后,向訊息中間件發送Commit請求,該請求發送完成后,對系統A而言,該事務的處理程序就結束了,此時它可以處理別的任務了, 但commit訊息可能會在傳輸途中丟失,從而訊息中間件并不會向系統B投遞這條訊息,從而系統就會出現不一致性,這個問題由訊息中間件的事務回查機制完成,下文會介紹,
訊息中間件收到Commit指令后,便向系統B投遞該訊息,從而觸發任務B的執行;
當任務B執行完成后,系統B向訊息中間件回傳一個確認應答,告訴訊息中間件該訊息已經成功消費,此時,這個分布式事務完成,
上述程序可以得出如下幾個結論:
訊息中間件扮演者分布式事務協調者的角色,
系統A完成任務A后,到任務B執行完成之間,會存在一定的時間差,在這個時間差內,整個系統處于資料不一致的狀態,但這短暫的不一致性是可以接受的,因為經過短暫的時間后,系統又可以保持資料一致性,滿足BASE理論,
上述程序中,如果任務A處理失敗,那么需要進入回滾流程,如下圖所示:

若系統A在處理任務A時失敗,那么就會向訊息中間件發送Rollback請求,和發送Commit請求一樣,系統A發完之后便可以認為回滾已經完成,它便可以去做其他的事情,
訊息中間件收到回滾請求后,直接將該訊息丟棄,而不投遞給系統B,從而不會觸發系統B的任務B,
此時系統又處于一致性狀態,因為任務A和任務B都沒有執行,
上面所介紹的Commit和Rollback都屬于理想情況,但在實際系統中,Commit和Rollback指令都有可能在傳輸途中丟失,那么當出現這種情況的時候,訊息中間件是如何保證資料一致性呢?——答案就是超時詢問機制,

系統A除了實作正常的業務流程外,還需提供一個事務詢問的介面,供訊息中間件呼叫,當訊息中間件收到一條事務型訊息后便開始計時,如果到了超時時間也沒收到系統A發來的Commit或Rollback指令的話,就會主動呼叫系統A提供的事務詢問介面詢問該系統目前的狀態,該介面會回傳三種結果:
提交
若獲得的狀態是“提交”,則將該訊息投遞給系統B,
回滾
若獲得的狀態是“回滾”,則直接將條訊息丟棄,
處理中
若獲得的狀態是“處理中”,則繼續等待,
訊息中間件的超時詢問機制能夠防止上游系統因在傳輸程序中丟失Commit/Rollback指令而導致的系統不一致情況,而且能降低上游系統的阻塞時間,上游系統只要發出Commit/Rollback指令后便可以處理其他任務,無需等待確認應答,而Commit/Rollback指令丟失的情況通過超時詢問機制來彌補,這樣大大降低上游系統的阻塞時間,提升系統的并發度,
下面來說一說訊息投遞程序的可靠性保證, 當上游系統執行完任務并向訊息中間件提交了Commit指令后,便可以處理其他任務了,此時它可以認為事務已經完成,接下來訊息中間件**一定會保證訊息被下游系統成功消費掉!**那么這是怎么做到的呢?這由訊息中間件的投遞流程來保證,
訊息中間件向下游系統投遞完訊息后便進入阻塞等待狀態,下游系統便立即進行任務的處理,任務處理完成后便向訊息中間件回傳應答,訊息中間件收到確認應答后便認為該事務處理完畢!
如果訊息在投遞程序中丟失,或訊息的確認應答在回傳途中丟失,那么訊息中間件在等待確認應答超時之后就會重新投遞,直到下游消費者回傳消費成功回應為止,當然,一般訊息中間件可以設定訊息重試的次數和時間間隔,比如:當第一次投遞失敗后,每隔五分鐘重試一次,一共重試3次,如果重試3次之后仍然投遞失敗,那么這條訊息就需要人工干預,
有的同學可能要問:訊息投遞失敗后為什么不回滾訊息,而是不斷嘗試重新投遞?
這就涉及到整套分布式事務系統的實作成本問題, 我們知道,當系統A將向訊息中間件發送Commit指令后,它便去做別的事情了,如果此時訊息投遞失敗,需要回滾的話,就需要讓系統A事先提供回滾介面,這無疑增加了額外的開發成本,業務系統的復雜度也將提高,對于一個業務系統的設計目標是,在保證性能的前提下,最大限度地降低系統復雜度,從而能夠降低系統的運維成本,
不知大家是否發現,上游系統A向訊息中間件提交Commit/Rollback訊息采用的是異步方式,也就是當上游系統提交完訊息后便可以去做別的事情,接下來提交、回滾就完全交給訊息中間件來完成,并且完全信任訊息中間件,認為它一定能正確地完成事務的提交或回滾,然而,訊息中間件向下游系統投遞訊息的程序是同步的,也就是訊息中間件將訊息投遞給下游系統后,它會阻塞等待,等下游系統成功處理完任務回傳確認應答后才取消阻塞等待,為什么這兩者在設計上是不一致的呢?
想要了解更多微服務知識的可以加群:642830685,群中會不定期的更新測驗資源,測驗面試題以及測驗行業資訊,大家可以在群中積極交流技術,
首先,上游系統和訊息中間件之間采用異步通信是為了提高系統并發度,業務系統直接和用戶打交道,用戶體驗尤為重要,因此這種異步通信方式能夠極大程度地降低用戶等待時間,此外,異步通信相對于同步通信而言,沒有了長時間的阻塞等待,因此系統的并發性也大大增加,但異步通信可能會引起Commit/Rollback指令丟失的問題,這就由訊息中間件的超時詢問機制來彌補,
那么,訊息中間件和下游系統之間為什么要采用同步通信呢?
異步能提升系統性能,但隨之會增加系統復雜度;而同步雖然降低系統并發度,但實作成本較低,因此,在對并發度要求不是很高的情況下,或者服務器資源較為充裕的情況下,我們可以選擇同步來降低系統的復雜度, 我們知道,訊息中間件是一個獨立于業務系統的第三方中間件,它不和任何業務系統產生直接的耦合,它也不和用戶產生直接的關聯,它一般部署在獨立的服務器集群上,具有良好的可擴展性,所以不必太過于擔心它的性能,如果處理速度無法滿足我們的要求,可以增加機器來解決,而且,即使訊息中間件處理速度有一定的延遲那也是可以接受的,因為前面所介紹的BASE理論就告訴我們了,我們追求的是最終一致性,而非實時一致性,因此訊息中間件產生的時延導致事務短暫的不一致是可以接受的,
方案3:最大努力通知(定期校對)
最大努力通知也被稱為定期校對,其實在方案二中已經包含,這里再單獨介紹,主要是為了知識體系的完整性,這種方案也需要訊息中間件的參與,其程序如下:
上游系統在完成任務后,向訊息中間件同步地發送一條訊息,確保訊息中間件成功持久化這條訊息,然后上游系統可以去做別的事情了;
訊息中間件收到訊息后負責將該訊息同步投遞給相應的下游系統,并觸發下游系統的任務執行;
當下游系統處理成功后,向訊息中間件反饋確認應答,訊息中間件便可以將該條訊息洗掉,從而該事務完成,
上面是一個理想化的程序,但在實際場景中,往往會出現如下幾種意外情況:
訊息中間件向下游系統投遞訊息失敗
上游系統向訊息中間件發送訊息失敗
對于第一種情況,訊息中間件具有重試機制,我們可以在訊息中間件中設定訊息的重試次數和重試時間間隔,對于網路不穩定導致的訊息投遞失敗的情況,往往重試幾次后訊息便可以成功投遞,如果超過了重試的上限仍然投遞失敗,那么訊息中間件不再投遞該訊息,而是記錄在失敗訊息表中,訊息中間件需要提供失敗訊息的查詢介面,下游系統會定期查詢失敗訊息,并將其消費,這就是所謂的“定期校對”,
如果重復投遞和定期校對都不能解決問題,往往是因為下游系統出現了嚴重的錯誤,此時就需要人工干預,
對于第二種情況,需要在上游系統中建立訊息重發機制,可以在上游系統建立一張本地訊息表,并將 任務處理程序 和 向本地訊息表中插入訊息 這兩個步驟放在一個本地事務中完成,如果向本地訊息表插入訊息失敗,那么就會觸發回滾,之前的任務處理結果就會被取消,如果這量步都執行成功,那么該本地事務就完成了,接下來會有一個專門的訊息發送者不斷地發送本地訊息表中的訊息,如果發送失敗它會回傳重試,當然,也要給訊息發送者設定重試的上限,一般而言,達到重試上限仍然發送失敗,那就意味著訊息中間件出現嚴重的問題,此時也只有人工干預才能解決問題,
對于不支持事務型訊息的訊息中間件,如果要實作分布式事務的話,就可以采用這種方式,它能夠通過 重試機制 + 定期校對 實作分布式事務,但相比于第二種方案,它達到資料一致性的周期較長,而且還需要在上游系統中實作訊息重試發布機制,以確保訊息成功發布給訊息中間件,這無疑增加了業務系統的開發成本,使得業務系統不夠純粹,并且這些額外的業務邏輯無疑會占用業務系統的硬體資源,從而影響性能,
因此,盡量選擇支持事務型訊息的訊息中間件來實作分布式事務,如RocketMQ,
方案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,

假設圖中的服務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.1 持續集成、持續部署、持續交付
在介紹這三個概念之前,我們首先來了解下使用了這三個概念之后的軟體開發流程,如下圖所示:
首先是代碼的開發階段,當代碼完成開發后需要提交至代碼倉庫,此時需要對代碼進行編譯、打包,打包后的產物被稱為“構建物”,如:對Web專案打包之后生成的war包、jar包就是一種構建物,此時的構建物雖然沒有語法錯誤,但其質量是無法保證的,必須經過一系列嚴格的測驗之后才能具有部署到生產環境的資格,我們一般會給系統分配多套環境,如開發環境、測驗環境、預發環境、生產環境,每套環境都有它測驗標準,當構建物完成了一套環境的測驗,并達到交付標準時,就會自動進入下一個環境,構建物依次會經過這四套環境,構建物每完成一套環境的驗證,就具備交付給下一套環境的資格,當完成預發環境的驗證后,就具備的上線的資格,
測驗和交付程序是相互伴隨的,每一套環境都有各自的測驗標準,如在開發環境中,當代碼提交后需要通過編譯、打包生成構建物,在編譯的程序中會對代碼進行單元測驗,如果有任何測驗用例沒通過,整個構建流程就會被中止,此時開發人員需要立即修復問題,并重新提交代碼、重新編譯打包,
當單元測驗通過之后,構建物就具備了進入測驗環境的資格,此時它會被自動部署到測驗環境,進行新一輪的測驗,在測驗環境中,一般需要完成介面測驗和人工測驗,介面測驗由自動化腳本完成,這個程序完成后還需要人工進行功能性測驗,人工測驗完成后,需要手動觸發進入下一個階段,
此時構建物將會被部署到預發環境,預發環境是一種“類生產環境”,它和生產環境的服務器配置需要保持高度一致,在預發環境中,一般需要對構建物進行性能測驗,了解其性能指標是否能滿足上線的要求,當通過預發驗證后,構建物已經具備了上線的資格,此時它可以隨時上線,
上述程序涵蓋了持續集成、持續交付、持續部署,那么下面我們就從理論角度來介紹這三個概念,
持續集成
“集成”指的是修改后/新增的代碼向代碼倉庫合并的程序,而“持續集成”指的是代碼高頻率合并,這樣有什么好處呢?大家不妨想一想,如果我們集成代碼的頻率變高了,那么每次集成的代碼量就會變少,由于每次集成的時候都會進行單元測驗,從而當出現問題的時候問題出現的范圍就被縮小的,這樣就能快速定位到出錯的地方,尋找問題就更容易了,此外,頻繁集成能夠使問題盡早地暴露,這樣解決問題的成本也就越低,因為在軟體測驗中有這樣一條定律,時間和bug修復的成本成正比,也就是時間越長,bug修復的成本也就越大,所以持續集成能夠盡早發現問題,并能夠及時修復問題,這對于軟體的質量是非常重要的,
持續部署
“持續部署”指的是當存在多套環境時,當構建物成完上一套環境的測驗后,自動部署到下一套環境并進行一系列的測驗,直到構建物滿足上線的要求為止,
持續交付
當系統通過了所有的測驗之后,就具備了部署到生產環境的資格,這個程序也就被稱為“交付”,“持續交付”指的是每個版本的構建物都具有上線的資格,這就要求每當代碼庫中有新的版本后,都需要自動觸發構建、測驗、部署、交付等一系列流程,當構建物在某個階段的測驗未通過時,就需要開發人員立即解決這個問題,并重新構建,從而保證每個版本的構建物都具備上線的資格,可以隨時部署到生產環境中,
微服務與持續集成
當我們了解了持續集成后,下面來介紹微服務如何與持續集成相整合,當我們對系統進行了微服務化后,原本單一的系統被拆分成多個課獨立運行的微服務,單服務系統的持續集成較為簡單,代碼庫、構建和構建物之間都是一對一的關系,然而,當我們將系統微服務化后,持續集成就變得復雜了,下面介紹兩種在微服務中使用持續集成的方法,分別是單庫多構建和多庫多構建,并依次介紹這兩種方式的優缺點及使用場景,
單庫多構建
“單庫”指的是單個代碼倉庫,即整個系統的多個模塊的代碼均由一個代碼倉庫維護,“多構建”指的是持續集成平臺中的構建專案會有多個,每個構建都會生成一個構建物,如下如所示:

在這種持續集成的模式中,整個專案的所有代碼均在同一個代碼倉庫中維護,但在持續集成平臺中,每一項服務都有各自獨立的構建,從而持續集成平臺能夠為每一項服務產出各自的構建物,
這種持續集成的模式在微服務架構中顯然是不合理的,首先,一個系統的可能會有很多服務構成,如果將這些服務的代碼均在同一個代碼倉庫中維護,那么一個程式員在開發服務A代碼的時候很有可能會因為疏忽,修改了服務B的代碼,此時服務B構建之后就會存在安全隱患,如果這個問題在服務B上線前被發現,那么還好,但無疑增加了額外的作業量;但如果這個問題及其隱諱,導致之前的測驗用例沒有覆寫到,從而服務B會帶著這個問題進入生產環境,這可能會給企業帶來巨大的損失,所以,在微服務架構中,盡量選擇多庫多構建模式來實作持續集成,它將帶來更大的安全性,
雖然這種模式不合理,但它也有存在的必要性,當我們在專案建設初期的時候,這種模式會給我們帶來更多的便利性,因為專案在建設初期,服務之間的邊界往往是比較模糊的,而且需要經過一段時間的演化才能夠構建出穩定的邊界,所以如果在專案建設初期直接使用微服務架構,那么服務邊界頻繁地調整會極大增加系統開發的復雜度,你要知道,在多個系統之間調整邊界比在單個系統的多個模塊之間調整邊界的成本要高很多,所以在專案建設初期,我們可以使用單服務結構,服務內部采用模塊作為未來各個微服務的邊界,當系統演化出較為清晰、穩定的邊界后再將系統拆分成多個微服務,此時代碼在同一個代碼倉庫中維護是合理的,這也符合敏捷開發中快速迭代的理念,
多庫多構建

當系我們的系統擁有了穩定、清晰的邊界后,就可以將系統向微服務架構演進,與此同時,持續集成模式也可以從單庫多構建向多庫多構建演進,
想要了解更多微服務知識點的,可以加我的群:537775426,我會在群里把這些知識點全部羅列清楚,供大家免費學習下載,我也會在群里分享我從業多年的一些經驗,只希望我以前踩過的坑后面的人可以避過,也可以提前為以后打下伏筆,以下是我整理的關于微服務的知識點,具體資源在共享區:

微服務體系
在多庫多構建模式中,每項服務都有各自獨立的代碼倉庫,代碼倉庫之間互不干擾,開發團隊只需關注屬于自己的某幾項服務的代碼倉庫即可,每一項服務都有各自獨立的構建,這種方式邏輯清晰,維護成本較低,而且能避免單庫多構建模式中出現的影響其他服務的問題,
微服務構建物
持續集成平臺對原始碼編譯、大包后生成的產物稱為“構建物”,根據打包的粒度不同,可以將構建物分為如下三種:平臺構建物、作業系統構建物和鏡像構建物,
平臺構建物
平臺構建物指的是由某一特定平臺生成的構建物,比如JVM平臺生成的Jar包、War包,Python生成的egg等都屬于平臺構建物,但平臺構建物運行需要部署在特定的容器中,如war需要運行在Servlet容器中,而Servlet容器又依賴的JVM環境,所以若要部署平臺構建物,則需要先給它們提供好運行所需的環境,
作業系統構建物
作業系統構建物是將系統打包成一個作業系統可執行程式,,如CentOS的RPM包、Windows的MSI包等,這些安裝包可以在作業系統上直接安裝運行,但和平臺構建物相同的是,作業系統構建物往往也需要依賴于其他環境,所以也需要在部署之前搭建好安裝包所需的依賴,此外,配置作業系統構建物的復雜度較大,構建的成本較高,所以一般不使用這種方式,這里僅作介紹,
鏡像構建物
平臺構建物和作業系統構建物都有一個共同的缺點就是需要安裝構建物運行的額外依賴,增加部署復雜度,而鏡像構建物能很好地解決這個問題,
我們可以把鏡像理解成一個小型作業系統,這個作業系統中包含了系統運行所需的所有依賴,并將系統也部署在這個“作業系統”中,這樣當持續集成平臺構建完這個鏡像后,就可以直接運行它,無需任何依賴的安裝,從而極大簡化了構建的復雜度,但是,鏡像往往比較龐大,構建鏡像的程序也較長,從而當我們將生成的鏡像從持續集成服務器發布到部署服務器的時間將會很長,這無疑降低了部署的效率,不過好在Docker的出現解決了這一問題,持續集成平臺在構建程序中并不需要生成一個鏡像,而只需生成一個鏡像的Dockerfile檔案即可,Dockerfile檔案用命令定義了鏡像所包含的內容,以及鏡像創建的程序,從而持續集成服務器只需將這個體積較小的鏡像檔案發布到部署服務器上即可,然后部署服務器會通過docker build命令基于這個Dockerfile檔案創建鏡像,并創建該鏡像的容器,從而完成服務的部署,
相對于平臺構建物和作業系統構建物而言,鏡像構建物在部署時不需要安裝額外的環境依賴,它把環境依賴的配置都在持續集成平臺構建Dockerfile檔案時完成,從而簡化了部署的程序,
總結:
在這里推薦一個軟體測驗交流群,QQ:642830685,群中會不定期的分享軟體測驗資源,測驗面試題以及行業資訊,大家可以在群中積極交流問題,另外還有技術大佬為你答疑解惑,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/251452.html
標籤:其他
上一篇:我把我面試的全程序都和你講了,你再說面試不知道有啥,我就真的難受了!
下一篇:一條好的測驗用例,應該是這樣的。
