日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

浅谈“微服务”

發(fā)布時間:2023/12/10 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浅谈“微服务” 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
  • 微服務概述

    1.1 易于擴展

    1.2 部署簡單

    1.3 技術異構性

  • 數(shù)據(jù)庫的服務化切分

    2.1 什么是“分庫分表”?

    2.2 數(shù)據(jù)庫擴展的幾種方式

    2.3 分庫分表的幾種方式

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

    2.5 現(xiàn)有分庫分表中間件的橫向?qū)Ρ?/p>

  • 微服務架構中的分布式事務

    3.1 什么是事務?

    3.2 事務的四大特性 ACID

    3.3 事務的隔離級別

    3.4 什么是分布式事務?

    3.5 CAP理論

    3.6 BASE理論

    3.7 酸堿平衡

    3.8 分布式事務協(xié)議

    3.9 分布式事務的解決方案

  • 服務部署

    4.1 持續(xù)集成、持續(xù)部署、持續(xù)交付

    4.2 微服務與持續(xù)集成

    4.3 微服務構建物


  • 1. 什么是微服務?

    我們首先給出微服務的定義,然后再對該定義給出詳細的解釋。

    微服務就是一些可獨立運行、可協(xié)同工作的小的服務。

    從概念中我們可以提取三個關鍵詞:可獨立運行、可協(xié)同工作、小。這三個詞高度概括了微服務的核心特性。下面我們就對這三個詞作詳細解釋。

  • 可獨立運行

    微服務是一個個可以獨立開發(fā)、獨立部署、獨立運行的系統(tǒng)或者進程。

  • 可協(xié)同工作

    采用了微服務架構后,整個系統(tǒng)被拆分成多個微服務,這些服務之間往往不是完全獨立的,在業(yè)務上存在一定的耦合,即一個服務可能需要使用另一個服務所提供的功能。這就是所謂的“可協(xié)同工作”。與單服務應用不同的是,多個微服務之間的調(diào)用時通過RPC通信來實現(xiàn),而非單服務的本地調(diào)用,所以通信的成本相對要高一些,但帶來的好處也是可觀的。

  • 小而美

    微服務的思想是,將一個擁有復雜功能的龐大系統(tǒng),按照業(yè)務功能,拆分成多個相互獨立的子系統(tǒng),這些子系統(tǒng)則被稱為“微服務”。每個微服務只承擔某一項職責,從而相對于單服務應用來說,微服務的體積是“小”的。小也就意味著每個服務承擔的職責變少,根據(jù)單一職責原則,我們在系統(tǒng)設計時,要盡量使得每一項服務只承擔一項職責,從而實現(xiàn)系統(tǒng)的“高內(nèi)聚”。

  • 2. 微服務的優(yōu)點

    1. 易于擴展

    在單服務應用中,如果目前性能到達瓶頸,無法支撐目前的業(yè)務量,此時一般采用集群模式,即增加服務器集群的節(jié)點,并將這個單服務應用“復制”到所有的節(jié)點上,從而提升整體性能。然而這種擴展的粒度是比較粗糙的。如果只是系統(tǒng)中某一小部分存在性能問題,在單服務應用中,也要將整個應用進行擴展,這種方式簡單粗暴,無法對癥下藥。而當我們使用了微服務架構后,如果某一項服務的性能到達瓶頸,那么我們只需要增加該服務的節(jié)點數(shù)即可,其他服務無需變化。這種擴展更加具有針對性,能夠充分利用計算機硬件/軟件資源。而且只擴展單個服務影響的范圍較小,從而系統(tǒng)出錯的概率也就越低。

    2. 部署簡單

    對于單服務應用而言,所有代碼均在一個項目中,從而導致任何微小的改變都需要將整個項目打包、發(fā)布、部署,而這一系列操作的代價是高昂的。長此以往,團隊為了降低發(fā)布的頻率,會使得每次發(fā)布都伴隨著大量的修改,修改越多也就意味著出錯的概率也越大。 當我們采用微服務架構以后,每個服務只承擔少數(shù)職責,從而每次只需要發(fā)布發(fā)生修改的系統(tǒng),其他系統(tǒng)依然能夠正常運行,波及范圍較小。此外,相對于單服務應用而言,每個微服務系統(tǒng)修改的代碼相對較少,從而部署后出現(xiàn)錯誤的概率也相對較低。

    3. 技術異構性

    對于單服務應用而言,一個系統(tǒng)的所有模塊均整合在一個項目中,所以這些模塊只能選擇相同的技術。但有些時候,單一技術沒辦法滿足不同的業(yè)務需求。如對于項目的算法團隊而言,函數(shù)試編程語言可能更適合算法的開發(fā),而對于業(yè)務開發(fā)團隊而言,類似于Java的強類型語言具有更高的穩(wěn)定性。然而在單服務應用中只能互相權衡,選擇同一種語言,而當我們使用微服務結構后,這個問題就能夠引刃而解。我們將一個完整的系統(tǒng)拆分成了多個獨立的服務,從而每個服務都可以根據(jù)各自不同的特點,選擇最為合適的技術體系。

    當然,并不是所有的微服務系統(tǒng)都具備技術異構性,要實現(xiàn)技術異構性,必須保證所有服務都提供通用接口。我們知道,在微服務系統(tǒng)中,服務之間采用RPC接口通信,而實現(xiàn)RPC通信的方式有很多。有一些RPC通信方式與語言強耦合,如Java的RMI技術,它就要求通信的雙方都必須采用Java語言開發(fā)。當然,也有一些RPC通信方式與語言無關,如基于HTTP協(xié)議的REST。這種通信方式對通信雙方所采用的語言沒有做任何限制,只要通信過程中傳輸?shù)臄?shù)據(jù)遵循REST規(guī)范即可。當然,與語言無關也就意味著通信雙方?jīng)]有類型檢查,從而會提高出錯的概率。所以,究竟選擇與語言無關的RPC通信方式,還是選擇與語言強耦合的RPC通信方式,需要我們根據(jù)實際的業(yè)務場景合理地分析。

    2. 數(shù)據(jù)庫的服務化切分

    2.1 什么是“分庫分表”?

    隨著大數(shù)據(jù)時代的到來,業(yè)務系統(tǒng)的數(shù)據(jù)量日益增大,數(shù)據(jù)存儲能力逐漸成為影響系統(tǒng)性能的瓶頸。目前主流的關系型數(shù)據(jù)庫單表存儲上限為1000萬條記錄,而這一存儲能力顯然已經(jīng)無法滿足大數(shù)據(jù)背景下的業(yè)務系統(tǒng)存儲要求了。隨著微服務架構、分布式存儲等概念的出現(xiàn),數(shù)據(jù)存儲問題也漸漸迎來了轉(zhuǎn)機。而數(shù)據(jù)分片是目前解決海量數(shù)據(jù)持久化存儲與高效查詢的一種重要手段。數(shù)據(jù)分庫分表的過程在系統(tǒng)設計階段完成,要求系統(tǒng)設計人員根據(jù)系統(tǒng)預期的業(yè)務量,將未來可能出現(xiàn)瓶頸的數(shù)據(jù)庫、數(shù)據(jù)表按照一定規(guī)則拆分成多個庫、多張表。這些數(shù)據(jù)庫和數(shù)據(jù)表需要部署在不同的服務器上,從而將數(shù)據(jù)讀寫壓力分攤至集群中的各個節(jié)點,提升數(shù)據(jù)庫整體處理能力,避免出現(xiàn)讀寫瓶頸的現(xiàn)象。

    目前數(shù)據(jù)分片的方式一共有兩種:離散分片和連續(xù)分片。

    離散分片是按照數(shù)據(jù)的某一字段哈希取模后進行分片存儲。只要哈希算法選擇得當,數(shù)據(jù)就會均勻地分布在不同的分片中,從而將讀寫壓力平均分配給所有分片,整體上提升數(shù)據(jù)的讀寫能力。然而,離散存儲要求數(shù)據(jù)之間有較強的獨立性,但實際業(yè)務系統(tǒng)并非如此,不同分片之間的數(shù)據(jù)往往存在一定的關聯(lián)性,因此在某些場景下需要跨分片連接查詢。由于目前所有的關系型數(shù)據(jù)庫出于安全性考慮,均不支持跨庫連接。因此,跨庫操作需要由數(shù)據(jù)分庫分表中間件來完成,這極大影響數(shù)據(jù)的查詢效率。此外,當數(shù)據(jù)存儲能力出現(xiàn)瓶頸需要擴容時,離散分片規(guī)則需要將所有數(shù)據(jù)重新進行哈希取模運算,這無疑成為限制系統(tǒng)可擴展性的一個重要因素。雖然,一致性哈希能在一定程度上減少系統(tǒng)擴容時的數(shù)據(jù)遷移,但數(shù)據(jù)遷移問題仍然不可避免。對于一個已經(jīng)上線運行的系統(tǒng)而言,系統(tǒng)停止對外服務進行數(shù)據(jù)遷移的代價太大。

    第二種數(shù)據(jù)分片的方式即為連續(xù)分片,它能解決系統(tǒng)擴容時產(chǎn)生的數(shù)據(jù)遷移問題。這種方式要求數(shù)據(jù)按照時間或連續(xù)自增主鍵連續(xù)存儲。從而一段時間內(nèi)的數(shù)據(jù)或相鄰主鍵的數(shù)據(jù)會被存儲在同一個分片中。當需要增加分片時,不會影響現(xiàn)有的分片。因此,連續(xù)分片能解決擴容所帶來的數(shù)據(jù)遷移問題。但是,數(shù)據(jù)的存儲時間和讀寫頻率往往呈正比,也就是大量的讀寫往往都集中在最新存儲的那一部分數(shù)據(jù),這就會導致熱點問題,并不能起到分攤讀寫壓力的初衷。

    2.2 數(shù)據(jù)庫擴展的幾種方式

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

  • 垂直分庫

    垂直分庫即是將一個完整的數(shù)據(jù)庫根據(jù)業(yè)務功能拆分成多個獨立的數(shù)據(jù)庫,這些數(shù)據(jù)庫可以運行在不同的服務器上,從而提升數(shù)據(jù)庫整體的數(shù)據(jù)讀寫性能。這種方式在微服務架構中非常常用。微服務架構的核心思想是將一個完整的應用按照業(yè)務功能拆分成多個可獨立運行的子系統(tǒng),這些子系統(tǒng)稱為“微服務”,各個服務之間通過RPC接口通信,這樣的結構使得系統(tǒng)耦合度更低、更易于擴展。垂直分庫的理念與微服務的理念不謀而合,可以將原本完整的數(shù)據(jù)按照微服務拆分系統(tǒng)的方式,拆分成多個獨立的數(shù)據(jù)庫,使得每個微服務系統(tǒng)都有各自獨立的數(shù)據(jù)庫,從而可以避免單個數(shù)據(jù)庫節(jié)點壓力過大,影響系統(tǒng)的整體性能,如下圖所示。

    ?

  • 垂直分表

    垂直分表如果一張表的字段非常多,那么很有可能會引起數(shù)據(jù)的跨頁存儲,這會造成數(shù)據(jù)庫額外的性能開銷,而垂直分表可以解決這個問題。垂直分表就是將一張表中不常用的字段拆分到另一張表中,從而保證第一章表中的字段較少,避免出現(xiàn)數(shù)據(jù)庫跨頁存儲的問題,從而提升查詢效率。而另一張表中的數(shù)據(jù)通過外鍵與第一張表進行關聯(lián),如下圖所示。

    ?

  • 水平分表

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

    ?

  • 水平分庫分表

    水平數(shù)據(jù)分片與數(shù)據(jù)分片區(qū)別在于:水平數(shù)據(jù)分片首先將數(shù)據(jù)表進行水平拆分,然后按照某一分片規(guī)則存儲在多臺數(shù)據(jù)庫服務器上。從而將單庫的壓力分攤到了多庫上,從而避免因為數(shù)據(jù)庫硬件資源有限導致的數(shù)據(jù)庫性能瓶頸,如下圖所示。

    ?

  • 2.3 分庫分表的幾種方式

    目前常用的數(shù)據(jù)分片策略有兩種,分別是連續(xù)分片和離散分片。

  • 離散分片

    離散分片是指將數(shù)據(jù)打散之后均勻地存儲在邏輯表的各個分片中,從而使的對同一張邏輯表的數(shù)據(jù)讀取操作均勻地落在不同庫的不同表上,從而提高讀寫速度。離散分片一般以哈希取模的方式實現(xiàn)。比如:一張邏輯表有4個分片,那么在讀寫數(shù)據(jù)的時候,中間件首先會取得分片字段的哈希值,然后再模以4,從而計算出該條記錄所在的分片。在這種方法中,只要哈希算法選的好,那么數(shù)據(jù)分片將會比較均勻,從而數(shù)據(jù)讀寫就會比較均勻地落在各個分片上,從而就有較高的讀寫效率。但是,這種方式也存在一個最大的缺陷——數(shù)據(jù)庫擴容成本較高。采用這種方式,如果需要再增加分片,原先的分片算法將失效,并且所有記錄都需要重新計算所在分片的位置。對于一個已經(jīng)上線的系統(tǒng)來說,行級別的數(shù)據(jù)遷移成本相當高,而且由于數(shù)據(jù)遷移期間系統(tǒng)仍在運行,仍有新數(shù)據(jù)產(chǎn)生,從而無法保證遷移過程數(shù)據(jù)的一致性。如果為了避免這個問題而停機遷移,那必然會對業(yè)務造成巨大影響。當然,如果為了避免數(shù)據(jù)遷移,在一開始的時候就分片較多的分片,那需要承擔較高的費用,這對于中小公司來說是無法承受的。

  • 連續(xù)分片

    連續(xù)分片指的是按照某一種分片規(guī)則,將某一個區(qū)間內(nèi)的數(shù)據(jù)存儲在同一個分片上。比如按照時間分片,每個月生成一張物理表。那么在讀寫數(shù)據(jù)時,直接根據(jù)當前時間就可以找到數(shù)據(jù)所在的分片。再比如可以按照記錄ID分片,這種分片方式要求ID需要連續(xù)遞增。由于Mysql數(shù)據(jù)庫單表支持最大的記錄數(shù)約為1000萬,因此我們可以根據(jù)記錄的ID,使得每個分片存儲1000萬條記錄,當目前的記錄數(shù)即將到達存儲上限時,我們只需增加分片即可,原有的數(shù)據(jù)無需遷移。連續(xù)分片的一個最大好處就是方便擴容,因為它不需要任何的數(shù)據(jù)遷移。但是,連續(xù)分片有個最大的缺點就是熱點問題。連續(xù)分片使得新插入的數(shù)據(jù)集中在同一個分片上,而往往新插入的數(shù)據(jù)讀寫頻率較高,因此,讀寫操作都會集中在最新的分片上,從而無法體現(xiàn)數(shù)據(jù)分片的優(yōu)勢。

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

  • 跨庫操作

    在關系型數(shù)據(jù)庫中,多張表之間往往存在關聯(lián),我們在開發(fā)過程中需要使用JOIN操作進行多表連接。但是當我們使用了分庫分表模式后,由于數(shù)據(jù)庫廠商處于安全考慮,不允許跨庫JOIN操作,從而如果需要連接的兩張表被分到不同的庫中后,就無法使用SQL提供的JOIN關鍵字來實現(xiàn)表連接,我們可能需要在業(yè)務系統(tǒng)層面,通過多次SQL查詢,完成數(shù)據(jù)的組裝和拼接。這一方面會增加業(yè)務系統(tǒng)的復雜度,另一方面會增加業(yè)務系統(tǒng)的負載。 因此,當我們使用分庫分表模式時,需要根據(jù)具體的業(yè)務場景,合理地設置分片策略、設置分片字段,這將會在本文的后續(xù)章節(jié)中介紹。

  • 分布式事務

    我們知道,數(shù)據(jù)庫提供了事務的功能,以保證數(shù)據(jù)一致性。然而,這種事務只是針對單數(shù)據(jù)庫而言的,數(shù)據(jù)庫廠商并未提供跨庫事務。因此,當我們使用了分庫分表之后,就需要我們在業(yè)務系統(tǒng)層面實現(xiàn)分布式事務。關于分布式事務的詳細內(nèi)容,可以參考筆者的另一篇文章《常用的分布式事務解決方案》。

  • 2.5 現(xiàn)有分庫分表中間件的橫向?qū)Ρ?/h2>
    • Cobar實現(xiàn)數(shù)據(jù)庫的透明分庫,讓開發(fā)人員能夠在無感知的情況下操縱數(shù)據(jù)庫集群,從而簡化數(shù)據(jù)庫的編程模型。然而Cobar僅實現(xiàn)了分庫功能,并未實現(xiàn)分表功能。分庫可以解決單庫IO、CPU、內(nèi)存的瓶頸,但無法解決單表數(shù)據(jù)量過大的問題。此外,Cobar是一個獨立運行的系統(tǒng),它處在應用系統(tǒng)與數(shù)據(jù)庫系統(tǒng)之間,因此增加了額外的部署復雜度,增加了運維成本。

    • 為了解決上述問題,Cobar還推出了一個Cobar-Client項目,它只是一個安裝在應用程序的Jar包,并不是一個獨立運行的系統(tǒng),一定程度上降低了系統(tǒng)的復雜度。但和Cobar一樣,仍然只支持分庫,并不支持分表,也不支持讀寫分離。

    • MyCat是基于Cobar二次開發(fā)的數(shù)據(jù)庫中間件,和Cobar相比,它增加了讀寫分離的功能,并修復了Cobar的一些bug。但是,MyCat和Cobar一樣,都是一套需要獨立部署的系統(tǒng),因此會增加部署的復雜度,提高了后期系統(tǒng)運維的成本。

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

    眾所周知,數(shù)據(jù)庫能實現(xiàn)本地事務,也就是在同一個數(shù)據(jù)庫中,你可以允許一組操作要么全都正確執(zhí)行,要么全都不執(zhí)行。這里特別強調(diào)了本地事務,也就是目前的數(shù)據(jù)庫只能支持同一個數(shù)據(jù)庫中的事務。但現(xiàn)在的系統(tǒng)往往采用微服務架構,業(yè)務系統(tǒng)擁有獨立的數(shù)據(jù)庫,因此就出現(xiàn)了跨多個數(shù)據(jù)庫的事務需求,這種事務即為“分布式事務”。那么在目前數(shù)據(jù)庫不支持跨庫事務的情況下,我們應該如何實現(xiàn)分布式事務呢?本文首先會為大家梳理分布式事務的基本概念和理論基礎,然后介紹幾種目前常用的分布式事務解決方案。廢話不多說,那就開始吧~

    3.1 什么是事務?

    事務由一組操作構成,我們希望這組操作能夠全部正確執(zhí)行,如果這一組操作中的任意一個步驟發(fā)生錯誤,那么就需要回滾之前已經(jīng)完成的操作。也就是同一個事務中的所有操作,要么全都正確執(zhí)行,要么全都不要執(zhí)行。

    3.2 事務的四大特性 ACID

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

    • 原子性

      原子性要求,事務是一個不可分割的執(zhí)行單元,事務中的所有操作要么全都執(zhí)行,要么全都不執(zhí)行。

    • 一致性

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

    • 隔離性

      事務的執(zhí)行是相互獨立的,它們不會相互干擾,一個事務不會看到另一個正在運行過程中的事務的數(shù)據(jù)。

    • 持久性

      持久性要求,一個事務完成之后,事務的執(zhí)行結果必須是持久化保存的。即使數(shù)據(jù)庫發(fā)生崩潰,在數(shù)據(jù)庫恢復后事務提交的結果仍然不會丟失。

    注意:事務只能保證數(shù)據(jù)庫的高可靠性,即數(shù)據(jù)庫本身發(fā)生問題后,事務提交后的數(shù)據(jù)仍然能恢復;而如果不是數(shù)據(jù)庫本身的故障,如硬盤損壞了,那么事務提交的數(shù)據(jù)可能就丟失了。這屬于『高可用性』的范疇。因此,事務只能保證數(shù)據(jù)庫的『高可靠性』,而『高可用性』需要整個系統(tǒng)共同配合實現(xiàn)。

    3.3 事務的隔離級別

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

    在事務的四大特性ACID中,要求的隔離性是一種嚴格意義上的隔離,也就是多個事務是串行執(zhí)行的,彼此之間不會受到任何干擾。這確實能夠完全保證數(shù)據(jù)的安全性,但在實際業(yè)務系統(tǒng)中,這種方式性能不高。因此,數(shù)據(jù)庫定義了四種隔離級別,隔離級別和數(shù)據(jù)庫的性能是呈反比的,隔離級別越低,數(shù)據(jù)庫性能越高,而隔離級別越高,數(shù)據(jù)庫性能越差。

    3.3.1 事務并發(fā)執(zhí)行會出現(xiàn)的問題

    我們先來看一下在不同的隔離級別下,數(shù)據(jù)庫可能會出現(xiàn)的問題:

  • 更新丟失

    當有兩個并發(fā)執(zhí)行的事務,更新同一行數(shù)據(jù),那么有可能一個事務會把另一個事務的更新覆蓋掉。 當數(shù)據(jù)庫沒有加任何鎖操作的情況下會發(fā)生。

  • 臟讀

    一個事務讀到另一個尚未提交的事務中的數(shù)據(jù)。 該數(shù)據(jù)可能會被回滾從而失效。 如果第一個事務拿著失效的數(shù)據(jù)去處理那就發(fā)生錯誤了。

  • 不可重復讀

    不可重復度的含義:一個事務對同一行數(shù)據(jù)讀了兩次,卻得到了不同的結果。它具體分為如下兩種情況:

    • 虛讀:在事務1兩次讀取同一記錄的過程中,事務2對該記錄進行了修改,從而事務1第二次讀到了不一樣的記錄。
    • 幻讀:事務1在兩次查詢的過程中,事務2對該表進行了插入、刪除操作,從而事務1第二次查詢的結果發(fā)生了變化。
  • 不可重復讀 與 臟讀 的區(qū)別? 臟讀讀到的是尚未提交的數(shù)據(jù),而不可重復讀讀到的是已經(jīng)提交的數(shù)據(jù),只不過在兩次讀的過程中數(shù)據(jù)被另一個事務改過了。

    3.3.2 數(shù)據(jù)庫的四種隔離級別

    數(shù)據(jù)庫一共有如下四種隔離級別:

  • Read uncommitted 讀未提交

    在該級別下,一個事務對一行數(shù)據(jù)修改的過程中,不允許另一個事務對該行數(shù)據(jù)進行修改,但允許另一個事務對該行數(shù)據(jù)讀。 因此本級別下,不會出現(xiàn)更新丟失,但會出現(xiàn)臟讀、不可重復讀。

  • Read committed 讀提交

    在該級別下,未提交的寫事務不允許其他事務訪問該行,因此不會出現(xiàn)臟讀;但是讀取數(shù)據(jù)的事務允許其他事務的訪問該行數(shù)據(jù),因此會出現(xiàn)不可重復讀的情況。

  • Repeatable read 重復讀

    在該級別下,讀事務禁止寫事務,但允許讀事務,因此不會出現(xiàn)同一事務兩次讀到不同的數(shù)據(jù)的情況(不可重復讀),且寫事務禁止其他一切事務。

  • Serializable 序列化

    該級別要求所有事務都必須串行執(zhí)行,因此能避免一切因并發(fā)引起的問題,但效率很低。

  • 隔離級別越高,越能保證數(shù)據(jù)的完整性和一致性,但是對并發(fā)性能的影響也越大。對于多數(shù)應用程序,可以優(yōu)先考慮把數(shù)據(jù)庫系統(tǒng)的隔離級別設為Read Committed。它能夠避免臟讀取,而且具有較好的并發(fā)性能。盡管它會導致不可重復讀、幻讀和第二類丟失更新這些并發(fā)問題,在可能出現(xiàn)這類問題的個別場合,可以由應用程序采用悲觀鎖或樂觀鎖來控制。

    3.4 什么是分布式事務?

    到此為止,所介紹的事務都是基于單數(shù)據(jù)庫的本地事務,目前的數(shù)據(jù)庫僅支持單庫事務,并不支持跨庫事務。而隨著微服務架構的普及,一個大型業(yè)務系統(tǒng)往往由若干個子系統(tǒng)構成,這些子系統(tǒng)又擁有各自獨立的數(shù)據(jù)庫。往往一個業(yè)務流程需要由多個子系統(tǒng)共同完成,而且這些操作可能需要在一個事務中完成。在微服務系統(tǒng)中,這些業(yè)務場景是普遍存在的。此時,我們就需要在數(shù)據(jù)庫之上通過某種手段,實現(xiàn)支持跨數(shù)據(jù)庫的事務支持,這也就是大家常說的“分布式事務”。

    這里舉一個分布式事務的典型例子——用戶下單過程。 當我們的系統(tǒng)采用了微服務架構后,一個電商系統(tǒng)往往被拆分成如下幾個子系統(tǒng):商品系統(tǒng)、訂單系統(tǒng)、支付系統(tǒng)、積分系統(tǒng)等。整個下單的過程如下:

  • 用戶通過商品系統(tǒng)瀏覽商品,他看中了某一項商品,便點擊下單
  • 此時訂單系統(tǒng)會生成一條訂單
  • 訂單創(chuàng)建成功后,支付系統(tǒng)提供支付功能
  • 當支付完成后,由積分系統(tǒng)為該用戶增加積分
  • 上述步驟2、3、4需要在一個事務中完成。對于傳統(tǒng)單體應用而言,實現(xiàn)事務非常簡單,只需將這三個步驟放在一個方法A中,再用Spring的@Transactional注解標識該方法即可。Spring通過數(shù)據(jù)庫的事務支持,保證這些步驟要么全都執(zhí)行完成,要么全都不執(zhí)行。但在這個微服務架構中,這三個步驟涉及三個系統(tǒng),涉及三個數(shù)據(jù)庫,此時我們必須在數(shù)據(jù)庫和應用系統(tǒng)之間,通過某項黑科技,實現(xiàn)分布式事務的支持。

    3.5 CAP理論

    CAP理論說的是:在一個分布式系統(tǒng)中,最多只能滿足C、A、P中的兩個需求。

    CAP的含義:

    • C:Consistency 一致性

      同一數(shù)據(jù)的多個副本是否實時相同。

    • A:Availability 可用性

      可用性:一定時間內(nèi) & 系統(tǒng)返回一個明確的結果 則稱為該系統(tǒng)可用。

    • P:Partition tolerance 分區(qū)容錯性

      將同一服務分布在多個系統(tǒng)中,從而保證某一個系統(tǒng)宕機,仍然有其他系統(tǒng)提供相同的服務。

    CAP理論告訴我們,在分布式系統(tǒng)中,C、A、P三個條件中我們最多只能選擇兩個。那么問題來了,究竟選擇哪兩個條件較為合適呢?

    對于一個業(yè)務系統(tǒng)來說,可用性和分區(qū)容錯性是必須要滿足的兩個條件,并且這兩者是相輔相成的。業(yè)務系統(tǒng)之所以使用分布式系統(tǒng),主要原因有兩個:

    • 提升整體性能

      當業(yè)務量猛增,單個服務器已經(jīng)無法滿足我們的業(yè)務需求的時候,就需要使用分布式系統(tǒng),使用多個節(jié)點提供相同的功能,從而整體上提升系統(tǒng)的性能,這就是使用分布式系統(tǒng)的第一個原因。

    • 實現(xiàn)分區(qū)容錯性

      單一節(jié)點 或 多個節(jié)點處于相同的網(wǎng)絡環(huán)境下,那么會存在一定的風險,萬一該機房斷電、該地區(qū)發(fā)生自然災害,那么業(yè)務系統(tǒng)就全面癱瘓了。為了防止這一問題,采用分布式系統(tǒng),將多個子系統(tǒng)分布在不同的地域、不同的機房中,從而保證系統(tǒng)高可用性。

    這說明分區(qū)容錯性是分布式系統(tǒng)的根本,如果分區(qū)容錯性不能滿足,那使用分布式系統(tǒng)將失去意義。

    此外,可用性對業(yè)務系統(tǒng)也尤為重要。在大談用戶體驗的今天,如果業(yè)務系統(tǒng)時常出現(xiàn)“系統(tǒng)異常”、響應時間過長等情況,這使得用戶對系統(tǒng)的好感度大打折扣,在互聯(lián)網(wǎng)行業(yè)競爭激烈的今天,相同領域的競爭者不甚枚舉,系統(tǒng)的間歇性不可用會立馬導致用戶流向競爭對手。因此,我們只能通過犧牲一致性來換取系統(tǒng)的可用性分區(qū)容錯性。這也就是下面要介紹的BASE理論。

    3.6 BASE理論

    CAP理論告訴我們一個悲慘但不得不接受的事實——我們只能在C、A、P中選擇兩個條件。而對于業(yè)務系統(tǒng)而言,我們往往選擇犧牲一致性來換取系統(tǒng)的可用性和分區(qū)容錯性。不過這里要指出的是,所謂的“犧牲一致性”并不是完全放棄數(shù)據(jù)一致性,而是犧牲強一致性換取弱一致性。下面來介紹下BASE理論。

    • BA:Basic Available 基本可用
      • 整個系統(tǒng)在某些不可抗力的情況下,仍然能夠保證“可用性”,即一定時間內(nèi)仍然能夠返回一個明確的結果。只不過“基本可用”和“高可用”的區(qū)別是:
        • “一定時間”可以適當延長 當舉行大促時,響應時間可以適當延長
        • 給部分用戶返回一個降級頁面 給部分用戶直接返回一個降級頁面,從而緩解服務器壓力。但要注意,返回降級頁面仍然是返回明確結果。
    • S:Soft State:柔性狀態(tài) 同一數(shù)據(jù)的不同副本的狀態(tài),可以不需要實時一致。
    • E:Eventual Consisstency:最終一致性 同一數(shù)據(jù)的不同副本的狀態(tài),可以不需要實時一致,但一定要保證經(jīng)過一定時間后仍然是一致的。

    3.7 酸堿平衡

    ACID能夠保證事務的強一致性,即數(shù)據(jù)是實時一致的。這在本地事務中是沒有問題的,在分布式事務中,強一致性會極大影響分布式系統(tǒng)的性能,因此分布式系統(tǒng)中遵循BASE理論即可。但分布式系統(tǒng)的不同業(yè)務場景對一致性的要求也不同。如交易場景下,就要求強一致性,此時就需要遵循ACID理論,而在注冊成功后發(fā)送短信驗證碼等場景下,并不需要實時一致,因此遵循BASE理論即可。因此要根據(jù)具體業(yè)務場景,在ACID和BASE之間尋求平衡。

    3.8 分布式事務協(xié)議

    下面介紹幾種實現(xiàn)分布式事務的協(xié)議。

    3.8.1 兩階段提交協(xié)議 2PC

    分布式系統(tǒng)的一個難點是如何保證架構下多個節(jié)點在進行事務性操作的時候保持一致性。為實現(xiàn)這個目的,二階段提交算法的成立基于以下假設:

    • 該分布式系統(tǒng)中,存在一個節(jié)點作為協(xié)調(diào)者(Coordinator),其他節(jié)點作為參與者(Cohorts)。且節(jié)點之間可以進行網(wǎng)絡通信。
    • 所有節(jié)點都采用預寫式日志,且日志被寫入后即被保持在可靠的存儲設備上,即使節(jié)點損壞不會導致日志數(shù)據(jù)的消失。
    • 所有節(jié)點不會永久性損壞,即使損壞后仍然可以恢復。

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

  • 協(xié)調(diào)者節(jié)點向所有參與者節(jié)點詢問是否可以執(zhí)行提交操作(vote),并開始等待各參與者節(jié)點的響應。
  • 參與者節(jié)點執(zhí)行詢問發(fā)起為止的所有事務操作,并將Undo信息和Redo信息寫入日志。(注意:若成功這里其實每個參與者已經(jīng)執(zhí)行了事務操作)
  • 各參與者節(jié)點響應協(xié)調(diào)者節(jié)點發(fā)起的詢問。如果參與者節(jié)點的事務操作實際執(zhí)行成功,則它返回一個"同意"消息;如果參與者節(jié)點的事務操作實際執(zhí)行失敗,則它返回一個"中止"消息。
  • 2. 第二階段(提交執(zhí)行階段):

    當協(xié)調(diào)者節(jié)點從所有參與者節(jié)點獲得的相應消息都為"同意"時:

  • 協(xié)調(diào)者節(jié)點向所有參與者節(jié)點發(fā)出"正式提交(commit)"的請求。
  • 參與者節(jié)點正式完成操作,并釋放在整個事務期間內(nèi)占用的資源。
  • 參與者節(jié)點向協(xié)調(diào)者節(jié)點發(fā)送"完成"消息。
  • 協(xié)調(diào)者節(jié)點受到所有參與者節(jié)點反饋的"完成"消息后,完成事務。
  • 如果任一參與者節(jié)點在第一階段返回的響應消息為"中止",或者 協(xié)調(diào)者節(jié)點在第一階段的詢問超時之前無法獲取所有參與者節(jié)點的響應消息時:

  • 協(xié)調(diào)者節(jié)點向所有參與者節(jié)點發(fā)出"回滾操作(rollback)"的請求。
  • 參與者節(jié)點利用之前寫入的Undo信息執(zhí)行回滾,并釋放在整個事務期間內(nèi)占用的資源。
  • 參與者節(jié)點向協(xié)調(diào)者節(jié)點發(fā)送"回滾完成"消息。
  • 協(xié)調(diào)者節(jié)點受到所有參與者節(jié)點反饋的"回滾完成"消息后,取消事務。
  • 不管最后結果如何,第二階段都會結束當前事務。

    二階段提交看起來確實能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點的:

  • 執(zhí)行過程中,所有參與節(jié)點都是事務阻塞型的。當參與者占有公共資源時,其他第三方節(jié)點訪問公共資源不得不處于阻塞狀態(tài)。
  • 參與者發(fā)生故障。協(xié)調(diào)者需要給每個參與者額外指定超時機制,超時后整個事務失敗。(沒有多少容錯機制)
  • 協(xié)調(diào)者發(fā)生故障。參與者會一直阻塞下去。需要額外的備機進行容錯。(這個可以依賴后面要講的Paxos協(xié)議實現(xiàn)HA)
  • 二階段無法解決的問題:協(xié)調(diào)者再發(fā)出commit消息之后宕機,而唯一接收到這條消息的參與者同時也宕機了。那么即使協(xié)調(diào)者通過選舉協(xié)議產(chǎn)生了新的協(xié)調(diào)者,這條事務的狀態(tài)也是不確定的,沒人知道事務是否被已經(jīng)提交。
  • 為此,Dale Skeen和Michael Stonebraker在“A Formal Model of Crash Recovery in a Distributed System”中提出了三階段提交協(xié)議(3PC)。

    3.8.2 三階段提交協(xié)議 3PC

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

    • 引入超時機制。同時在協(xié)調(diào)者和參與者中都引入超時機制。
    • 在第一階段和第二階段中插入一個準備階段。保證了在最后提交階段之前各參與節(jié)點的狀態(tài)是一致的。

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

    1. CanCommit階段

    3PC的CanCommit階段其實和2PC的準備階段很像。協(xié)調(diào)者向參與者發(fā)送commit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。

  • 事務詢問

    協(xié)調(diào)者向參與者發(fā)送CanCommit請求。詢問是否可以執(zhí)行事務提交操作。然后開始等待參與者的響應。

  • 響應反饋

    參與者接到CanCommit請求之后,正常情況下,如果其自身認為可以順利執(zhí)行事務,則返回Yes響應,并進入預備狀態(tài)。否則反饋No

  • 2. PreCommit階段

    協(xié)調(diào)者根據(jù)參與者的反應情況來決定是否可以記性事務的PreCommit操作。根據(jù)響應情況,有以下兩種可能。 假如協(xié)調(diào)者從所有的參與者獲得的反饋都是Yes響應,那么就會執(zhí)行事務的預執(zhí)行。

  • 發(fā)送預提交請求

    協(xié)調(diào)者向參與者發(fā)送PreCommit請求,并進入Prepared階段。

  • 事務預提交

    參與者接收到PreCommit請求后,會執(zhí)行事務操作,并將undo和redo信息記錄到事務日志中。

  • 響應反饋

    如果參與者成功的執(zhí)行了事務操作,則返回ACK響應,同時開始等待最終指令。

  • 假如有任何一個參與者向協(xié)調(diào)者發(fā)送了No響應,或者等待超時之后,協(xié)調(diào)者都沒有接到參與者的響應,那么就執(zhí)行事務的中斷。

  • 發(fā)送中斷請求

    協(xié)調(diào)者向所有參與者發(fā)送abort請求。

  • 中斷事務

    參與者收到來自協(xié)調(diào)者的abort請求之后(或超時之后,仍未收到協(xié)調(diào)者的請求),執(zhí)行事務的中斷。

  • 3. doCommit階段 該階段進行真正的事務提交,也可以分為以下兩種情況。

    該階段進行真正的事務提交,也可以分為以下兩種情況。

    3.1 執(zhí)行提交

  • 發(fā)送提交請求

    協(xié)調(diào)接收到參與者發(fā)送的ACK響應,那么他將從預提交狀態(tài)進入到提交狀態(tài)。并向所有參與者發(fā)送doCommit請求。

  • 事務提交

    參與者接收到doCommit請求之后,執(zhí)行正式的事務提交。并在完成事務提交之后釋放所有事務資源。

  • 響應反饋

    事務提交完之后,向協(xié)調(diào)者發(fā)送Ack響應。

  • 完成事務

    協(xié)調(diào)者接收到所有參與者的ack響應之后,完成事務。

  • 3.2 中斷事務 協(xié)調(diào)者沒有接收到參與者發(fā)送的ACK響應(可能是接受者發(fā)送的不是ACK響應,也可能響應超時),那么就會執(zhí)行中斷事務。

  • 發(fā)送中斷請求

    協(xié)調(diào)者向所有參與者發(fā)送abort請求

  • 事務回滾

    參與者接收到abort請求之后,利用其在階段二記錄的undo信息來執(zhí)行事務的回滾操作,并在完成回滾之后釋放所有的事務資源。

  • 反饋結果

    參與者完成事務回滾之后,向協(xié)調(diào)者發(fā)送ACK消息

  • 中斷事務

    協(xié)調(diào)者接收到參與者反饋的ACK消息之后,執(zhí)行事務的中斷。

  • 3.9 分布式事務的解決方案

    分布式事務的解決方案有如下幾種:

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

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

    全局事務基于DTP模型實現(xiàn)。DTP是由X/Open組織提出的一種分布式事務模型——X/Open Distributed Transaction Processing Reference Model。它規(guī)定了要實現(xiàn)分布式事務,需要三種角色:

    • AP:Application 應用系統(tǒng)

      它就是我們開發(fā)的業(yè)務系統(tǒng),在我們開發(fā)的過程中,可以使用資源管理器提供的事務接口來實現(xiàn)分布式事務。

    • TM:Transaction Manager 事務管理器

      • 分布式事務的實現(xiàn)由事務管理器來完成,它會提供分布式事務的操作接口供我們的業(yè)務系統(tǒng)調(diào)用。這些接口稱為TX接口。
      • 事務管理器還管理著所有的資源管理器,通過它們提供的XA接口來同一調(diào)度這些資源管理器,以實現(xiàn)分布式事務。
      • DTP只是一套實現(xiàn)分布式事務的規(guī)范,并沒有定義具體如何實現(xiàn)分布式事務,TM可以采用2PC、3PC、Paxos等協(xié)議實現(xiàn)分布式事務。
    • RM:Resource Manager 資源管理器

      • 能夠提供數(shù)據(jù)服務的對象都可以是資源管理器,比如:數(shù)據(jù)庫、消息中間件、緩存等。大部分場景下,數(shù)據(jù)庫即為分布式事務中的資源管理器。
      • 資源管理器能夠提供單數(shù)據(jù)庫的事務能力,它們通過XA接口,將本數(shù)據(jù)庫的提交、回滾等能力提供給事務管理器調(diào)用,以幫助事務管理器實現(xiàn)分布式的事務管理。
      • XA是DTP模型定義的接口,用于向事務管理器提供該資源管理器(該數(shù)據(jù)庫)的提交、回滾等能力。
      • DTP只是一套實現(xiàn)分布式事務的規(guī)范,RM具體的實現(xiàn)是由數(shù)據(jù)庫廠商來完成的。
  • 有沒有基于DTP模型的分布式事務中間件?
  • DTP模型有啥優(yōu)缺點?
  • 3.9.2 方案2:基于可靠消息服務的分布式事務

    這種實現(xiàn)分布式事務的方式需要通過消息中間件來實現(xiàn)。假設有A和B兩個系統(tǒng),分別可以處理任務A和任務B。此時系統(tǒng)A中存在一個業(yè)務流程,需要將任務A和任務B在同一個事務中處理。下面來介紹基于消息中間件來實現(xiàn)這種分布式事務。

    ?

    ?

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

    上述過程可以得出如下幾個結論:

  • 消息中間件扮演者分布式事務協(xié)調(diào)者的角色。
  • 系統(tǒng)A完成任務A后,到任務B執(zhí)行完成之間,會存在一定的時間差。在這個時間差內(nèi),整個系統(tǒng)處于數(shù)據(jù)不一致的狀態(tài),但這短暫的不一致性是可以接受的,因為經(jīng)過短暫的時間后,系統(tǒng)又可以保持數(shù)據(jù)一致性,滿足BASE理論。
  • 上述過程中,如果任務A處理失敗,那么需要進入回滾流程,如下圖所示:

    ?

    • 若系統(tǒng)A在處理任務A時失敗,那么就會向消息中間件發(fā)送Rollback請求。和發(fā)送Commit請求一樣,系統(tǒng)A發(fā)完之后便可以認為回滾已經(jīng)完成,它便可以去做其他的事情。
    • 消息中間件收到回滾請求后,直接將該消息丟棄,而不投遞給系統(tǒng)B,從而不會觸發(fā)系統(tǒng)B的任務B。

    此時系統(tǒng)又處于一致性狀態(tài),因為任務A和任務B都沒有執(zhí)行。

    上面所介紹的Commit和Rollback都屬于理想情況,但在實際系統(tǒng)中,Commit和Rollback指令都有可能在傳輸途中丟失。那么當出現(xiàn)這種情況的時候,消息中間件是如何保證數(shù)據(jù)一致性呢?——答案就是超時詢問機制。

    ?

    ?

    系統(tǒng)A除了實現(xiàn)正常的業(yè)務流程外,還需提供一個事務詢問的接口,供消息中間件調(diào)用。當消息中間件收到一條事務型消息后便開始計時,如果到了超時時間也沒收到系統(tǒng)A發(fā)來的Commit或Rollback指令的話,就會主動調(diào)用系統(tǒng)A提供的事務詢問接口詢問該系統(tǒng)目前的狀態(tài)。該接口會返回三種結果:

    • 提交

      若獲得的狀態(tài)是“提交”,則將該消息投遞給系統(tǒng)B。

    • 回滾

      若獲得的狀態(tài)是“回滾”,則直接將條消息丟棄。

    • 處理中

      若獲得的狀態(tài)是“處理中”,則繼續(xù)等待。

    消息中間件的超時詢問機制能夠防止上游系統(tǒng)因在傳輸過程中丟失Commit/Rollback指令而導致的系統(tǒng)不一致情況,而且能降低上游系統(tǒng)的阻塞時間,上游系統(tǒng)只要發(fā)出Commit/Rollback指令后便可以處理其他任務,無需等待確認應答。而Commit/Rollback指令丟失的情況通過超時詢問機制來彌補,這樣大大降低上游系統(tǒng)的阻塞時間,提升系統(tǒng)的并發(fā)度。

    下面來說一說消息投遞過程的可靠性保證。 當上游系統(tǒng)執(zhí)行完任務并向消息中間件提交了Commit指令后,便可以處理其他任務了,此時它可以認為事務已經(jīng)完成,接下來消息中間件**一定會保證消息被下游系統(tǒng)成功消費掉!**那么這是怎么做到的呢?這由消息中間件的投遞流程來保證。

    消息中間件向下游系統(tǒng)投遞完消息后便進入阻塞等待狀態(tài),下游系統(tǒng)便立即進行任務的處理,任務處理完成后便向消息中間件返回應答。消息中間件收到確認應答后便認為該事務處理完畢!

    如果消息在投遞過程中丟失,或消息的確認應答在返回途中丟失,那么消息中間件在等待確認應答超時之后就會重新投遞,直到下游消費者返回消費成功響應為止。當然,一般消息中間件可以設置消息重試的次數(shù)和時間間隔,比如:當?shù)谝淮瓮哆f失敗后,每隔五分鐘重試一次,一共重試3次。如果重試3次之后仍然投遞失敗,那么這條消息就需要人工干預。

    ?

    ?

    ?

    有的同學可能要問:消息投遞失敗后為什么不回滾消息,而是不斷嘗試重新投遞?

    這就涉及到整套分布式事務系統(tǒng)的實現(xiàn)成本問題。 我們知道,當系統(tǒng)A將向消息中間件發(fā)送Commit指令后,它便去做別的事情了。如果此時消息投遞失敗,需要回滾的話,就需要讓系統(tǒng)A事先提供回滾接口,這無疑增加了額外的開發(fā)成本,業(yè)務系統(tǒng)的復雜度也將提高。對于一個業(yè)務系統(tǒng)的設計目標是,在保證性能的前提下,最大限度地降低系統(tǒng)復雜度,從而能夠降低系統(tǒng)的運維成本。

    不知大家是否發(fā)現(xiàn),上游系統(tǒng)A向消息中間件提交Commit/Rollback消息采用的是異步方式,也就是當上游系統(tǒng)提交完消息后便可以去做別的事情,接下來提交、回滾就完全交給消息中間件來完成,并且完全信任消息中間件,認為它一定能正確地完成事務的提交或回滾。然而,消息中間件向下游系統(tǒng)投遞消息的過程是同步的。也就是消息中間件將消息投遞給下游系統(tǒng)后,它會阻塞等待,等下游系統(tǒng)成功處理完任務返回確認應答后才取消阻塞等待。為什么這兩者在設計上是不一致的呢?

    首先,上游系統(tǒng)和消息中間件之間采用異步通信是為了提高系統(tǒng)并發(fā)度。業(yè)務系統(tǒng)直接和用戶打交道,用戶體驗尤為重要,因此這種異步通信方式能夠極大程度地降低用戶等待時間。此外,異步通信相對于同步通信而言,沒有了長時間的阻塞等待,因此系統(tǒng)的并發(fā)性也大大增加。但異步通信可能會引起Commit/Rollback指令丟失的問題,這就由消息中間件的超時詢問機制來彌補。

    那么,消息中間件和下游系統(tǒng)之間為什么要采用同步通信呢?

    異步能提升系統(tǒng)性能,但隨之會增加系統(tǒng)復雜度;而同步雖然降低系統(tǒng)并發(fā)度,但實現(xiàn)成本較低。因此,在對并發(fā)度要求不是很高的情況下,或者服務器資源較為充裕的情況下,我們可以選擇同步來降低系統(tǒng)的復雜度。 我們知道,消息中間件是一個獨立于業(yè)務系統(tǒng)的第三方中間件,它不和任何業(yè)務系統(tǒng)產(chǎn)生直接的耦合,它也不和用戶產(chǎn)生直接的關聯(lián),它一般部署在獨立的服務器集群上,具有良好的可擴展性,所以不必太過于擔心它的性能,如果處理速度無法滿足我們的要求,可以增加機器來解決。而且,即使消息中間件處理速度有一定的延遲那也是可以接受的,因為前面所介紹的BASE理論就告訴我們了,我們追求的是最終一致性,而非實時一致性,因此消息中間件產(chǎn)生的時延導致事務短暫的不一致是可以接受的。

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

    最大努力通知也被稱為定期校對,其實在方案二中已經(jīng)包含,這里再單獨介紹,主要是為了知識體系的完整性。這種方案也需要消息中間件的參與,其過程如下:

    ?

    ?

    • 上游系統(tǒng)在完成任務后,向消息中間件同步地發(fā)送一條消息,確保消息中間件成功持久化這條消息,然后上游系統(tǒng)可以去做別的事情了;
    • 消息中間件收到消息后負責將該消息同步投遞給相應的下游系統(tǒng),并觸發(fā)下游系統(tǒng)的任務執(zhí)行;
    • 當下游系統(tǒng)處理成功后,向消息中間件反饋確認應答,消息中間件便可以將該條消息刪除,從而該事務完成。

    上面是一個理想化的過程,但在實際場景中,往往會出現(xiàn)如下幾種意外情況:

  • 消息中間件向下游系統(tǒng)投遞消息失敗
  • 上游系統(tǒng)向消息中間件發(fā)送消息失敗
  • 對于第一種情況,消息中間件具有重試機制,我們可以在消息中間件中設置消息的重試次數(shù)和重試時間間隔,對于網(wǎng)絡不穩(wěn)定導致的消息投遞失敗的情況,往往重試幾次后消息便可以成功投遞,如果超過了重試的上限仍然投遞失敗,那么消息中間件不再投遞該消息,而是記錄在失敗消息表中,消息中間件需要提供失敗消息的查詢接口,下游系統(tǒng)會定期查詢失敗消息,并將其消費,這就是所謂的“定期校對”。

    如果重復投遞和定期校對都不能解決問題,往往是因為下游系統(tǒng)出現(xiàn)了嚴重的錯誤,此時就需要人工干預。

    對于第二種情況,需要在上游系統(tǒng)中建立消息重發(fā)機制。可以在上游系統(tǒng)建立一張本地消息表,并將 任務處理過程向本地消息表中插入消息 這兩個步驟放在一個本地事務中完成。如果向本地消息表插入消息失敗,那么就會觸發(fā)回滾,之前的任務處理結果就會被取消。如果這量步都執(zhí)行成功,那么該本地事務就完成了。接下來會有一個專門的消息發(fā)送者不斷地發(fā)送本地消息表中的消息,如果發(fā)送失敗它會返回重試。當然,也要給消息發(fā)送者設置重試的上限,一般而言,達到重試上限仍然發(fā)送失敗,那就意味著消息中間件出現(xiàn)嚴重的問題,此時也只有人工干預才能解決問題。

    對于不支持事務型消息的消息中間件,如果要實現(xiàn)分布式事務的話,就可以采用這種方式。它能夠通過重試機制+定期校對實現(xiàn)分布式事務,但相比于第二種方案,它達到數(shù)據(jù)一致性的周期較長,而且還需要在上游系統(tǒng)中實現(xiàn)消息重試發(fā)布機制,以確保消息成功發(fā)布給消息中間件,這無疑增加了業(yè)務系統(tǒng)的開發(fā)成本,使得業(yè)務系統(tǒng)不夠純粹,并且這些額外的業(yè)務邏輯無疑會占用業(yè)務系統(tǒng)的硬件資源,從而影響性能。

    因此,盡量選擇支持事務型消息的消息中間件來實現(xiàn)分布式事務,如RocketMQ。

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

    TCC即為Try Confirm Cancel,它屬于補償型分布式事務。顧名思義,TCC實現(xiàn)分布式事務一共有三個步驟:

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

    下面以一個轉(zhuǎn)賬的例子來解釋下TCC實現(xiàn)分布式事務的過程。

    假設用戶A用他的賬戶余額給用戶B發(fā)一個100元的紅包,并且余額系統(tǒng)和紅包系統(tǒng)是兩個獨立的系統(tǒng)。

    • Try

      • 創(chuàng)建一條轉(zhuǎn)賬流水,并將流水的狀態(tài)設為交易中
      • 將用戶A的賬戶中扣除100元(預留業(yè)務資源)
      • Try成功之后,便進入Confirm階段
      • Try過程發(fā)生任何異常,均進入Cancel階段
    • Confirm

      • 向B用戶的紅包賬戶中增加100元
      • 將流水的狀態(tài)設為交易已完成
      • Confirm過程發(fā)生任何異常,均進入Cancel階段
      • Confirm過程執(zhí)行成功,則該事務結束
    • Cancel

      • 將用戶A的賬戶增加100元
      • 將流水的狀態(tài)設為交易失敗

    在傳統(tǒng)事務機制中,業(yè)務邏輯的執(zhí)行和事務的處理,是在不同的階段由不同的部件來完成的:業(yè)務邏輯部分訪問資源實現(xiàn)數(shù)據(jù)存儲,其處理是由業(yè)務系統(tǒng)負責;事務處理部分通過協(xié)調(diào)資源管理器以實現(xiàn)事務管理,其處理由事務管理器來負責。二者沒有太多交互的地方,所以,傳統(tǒng)事務管理器的事務處理邏輯,僅需要著眼于事務完成(commit/rollback)階段,而不必關注業(yè)務執(zhí)行階段。

    TCC全局事務必須基于RM本地事務來實現(xiàn)全局事務

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

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

    ?

    ?

    假設圖中的服務B沒有基于RM本地事務(以RDBS為例,可通過設置auto-commit為true來模擬),那么一旦[B:Try]操作中途執(zhí)行失敗,TCC事務框架后續(xù)決定回滾全局事務時,該[B:Cancel]則需要判斷[B:Try]中哪些操作已經(jīng)寫到DB、哪些操作還沒有寫到DB:假設[B:Try]業(yè)務有5個寫庫操作,[B:Cancel]業(yè)務則需要逐個判斷這5個操作是否生效,并將生效的操作執(zhí)行反向操作。

    不幸的是,由于[B:Cancel]業(yè)務也有n(0<=n<=5)個反向的寫庫操作,此時一旦[B:Cancel]也中途出錯,則后續(xù)的[B:Cancel]執(zhí)行任務更加繁重。因為,相比第一次[B:Cancel]操作,后續(xù)的[B:Cancel]操作還需要判斷先前的[B:Cancel]操作的n(0<=n<=5)個寫庫中哪幾個已經(jīng)執(zhí)行、哪幾個還沒有執(zhí)行,這就涉及到了冪等性問題。而對冪等性的保障,又很可能還需要涉及額外的寫庫操作,該寫庫操作又會因為沒有RM本地事務的支持而存在類似問題。。。可想而知,如果不基于RM本地事務,TCC事務框架是無法有效的管理TCC全局事務的。

    反之,基于RM本地事務的TCC事務,這種情況則會很容易處理:[B:Try]操作中途執(zhí)行失敗,TCC事務框架將其參與RM本地事務直接rollback即可。后續(xù)TCC事務框架決定回滾全局事務時,在知道“[B:Try]操作涉及的RM本地事務已經(jīng)rollback”的情況下,根本無需執(zhí)行[B:Cancel]操作。

    換句話說,基于RM本地事務實現(xiàn)TCC事務框架時,一個TCC型服務的cancel業(yè)務要么執(zhí)行,要么不執(zhí)行,不需要考慮部分執(zhí)行的情況。

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

    一般認為,服務的冪等性,是指針對同一個服務的多次(n>1)請求和對它的單次(n=1)請求,二者具有相同的副作用。

    在TCC事務模型中,Confirm/Cancel業(yè)務可能會被重復調(diào)用,其原因很多。比如,全局事務在提交/回滾時會調(diào)用各TCC服務的Confirm/Cancel業(yè)務邏輯。執(zhí)行這些Confirm/Cancel業(yè)務時,可能會出現(xiàn)如網(wǎng)絡中斷的故障而使得全局事務不能完成。因此,故障恢復機制后續(xù)仍然會重新提交/回滾這些未完成的全局事務,這樣就會再次調(diào)用參與該全局事務的各TCC服務的Confirm/Cancel業(yè)務邏輯。

    既然Confirm/Cancel業(yè)務可能會被多次調(diào)用,就需要保障其冪等性。 那么,應該由TCC事務框架來提供冪等性保障?還是應該由業(yè)務系統(tǒng)自行來保障冪等性呢? 個人認為,應該是由TCC事務框架來提供冪等性保障。如果僅僅只是極個別服務存在這個問題的話,那么由業(yè)務系統(tǒng)來負責也是可以的;然而,這是一類公共問題,毫無疑問,所有TCC服務的Confirm/Cancel業(yè)務存在冪等性問題。TCC服務的公共問題應該由TCC事務框架來解決;而且,考慮一下由業(yè)務系統(tǒng)來負責冪等性需要考慮的問題,就會發(fā)現(xiàn),這無疑增大了業(yè)務系統(tǒng)的復雜度。

    4. 服務部署

    當我們完成業(yè)務代碼的開發(fā)后,就需要進入部署階段。在部署過程中,我們將會引入持續(xù)集成、持續(xù)交付、持續(xù)部署,并且闡述如何在微服務中使用他們。

    4.1 持續(xù)集成、持續(xù)部署、持續(xù)交付

    在介紹這三個概念之前,我們首先來了解下使用了這三個概念之后的軟件開發(fā)流程,如下圖所示:

    ?

    首先是代碼的開發(fā)階段,當代碼完成開發(fā)后需要提交至代碼倉庫,此時需要對代碼進行編譯、打包,打包后的產(chǎn)物被稱為“構建物”,如:對Web項目打包之后生成的war包、jar包就是一種構建物。此時的構建物雖然沒有語法錯誤,但其質(zhì)量是無法保證的,必須經(jīng)過一系列嚴格的測試之后才能具有部署到生產(chǎn)環(huán)境的資格。我們一般會給系統(tǒng)分配多套環(huán)境,如開發(fā)環(huán)境、測試環(huán)境、預發(fā)環(huán)境、生產(chǎn)環(huán)境。每套環(huán)境都有它測試標準,當構建物完成了一套環(huán)境的測試,并達到交付標準時,就會自動進入下一個環(huán)境。構建物依次會經(jīng)過這四套環(huán)境,構建物每完成一套環(huán)境的驗證,就具備交付給下一套環(huán)境的資格。當完成預發(fā)環(huán)境的驗證后,就具備的上線的資格。

    測試和交付過程是相互伴隨的,每一套環(huán)境都有各自的測試標準。如在開發(fā)環(huán)境中,當代碼提交后需要通過編譯、打包生成構建物,在編譯的過程中會對代碼進行單元測試,如果有任何測試用例沒通過,整個構建流程就會被中止。此時開發(fā)人員需要立即修復問題,并重新提交代碼、重新編譯打包。

    當單元測試通過之后,構建物就具備了進入測試環(huán)境的資格,此時它會被自動部署到測試環(huán)境,進行新一輪的測試。在測試環(huán)境中,一般需要完成接口測試和人工測試。接口測試由自動化腳本完成,這個過程完成后還需要人工進行功能性測試。人工測試完成后,需要手動觸發(fā)進入下一個階段。

    此時構建物將會被部署到預發(fā)環(huán)境。預發(fā)環(huán)境是一種“類生產(chǎn)環(huán)境”,它和生產(chǎn)環(huán)境的服務器配置需要保持高度一致。在預發(fā)環(huán)境中,一般需要對構建物進行性能測試,了解其性能指標是否能滿足上線的要求。當通過預發(fā)驗證后,構建物已經(jīng)具備了上線的資格,此時它可以隨時上線。

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

    4.1.1 持續(xù)集成

    “集成”指的是修改后/新增的代碼向代碼倉庫合并的過程,而“持續(xù)集成”指的是代碼高頻率合并。這樣有什么好處呢?大家不妨想一想,如果我們集成代碼的頻率變高了,那么每次集成的代碼量就會變少,由于每次集成的時候都會進行單元測試,從而當出現(xiàn)問題的時候問題出現(xiàn)的范圍就被縮小的,這樣就能快速定位到出錯的地方,尋找問題就更容易了。此外,頻繁集成能夠使問題盡早地暴露,這樣解決問題的成本也就越低。因為在軟件測試中有這樣一條定律,時間和bug修復的成本成正比,也就是時間越長,bug修復的成本也就越大。所以持續(xù)集成能夠盡早發(fā)現(xiàn)問題,并能夠及時修復問題,這對于軟件的質(zhì)量是非常重要的。

    4.1.2 持續(xù)部署

    “持續(xù)部署”指的是當存在多套環(huán)境時,當構建物成完上一套環(huán)境的測試后,自動部署到下一套環(huán)境并進行一系列的測試,直到構建物滿足上線的要求為止。

    4.1.3 持續(xù)交付

    當系統(tǒng)通過了所有的測試之后,就具備了部署到生產(chǎn)環(huán)境的資格,這個過程也就被稱為“交付”。“持續(xù)交付”指的是每個版本的構建物都具有上線的資格,這就要求每當代碼庫中有新的版本后,都需要自動觸發(fā)構建、測試、部署、交付等一系列流程,當構建物在某個階段的測試未通過時,就需要開發(fā)人員立即解決這個問題,并重新構建,從而保證每個版本的構建物都具備上線的資格,可以隨時部署到生產(chǎn)環(huán)境中。

    4.2 微服務與持續(xù)集成

    當我們了解了持續(xù)集成后,下面來介紹微服務如何與持續(xù)集成相整合。當我們對系統(tǒng)進行了微服務化后,原本單一的系統(tǒng)被拆分成多個課獨立運行的微服務。單服務系統(tǒng)的持續(xù)集成較為簡單,代碼庫、構建和構建物之間都是一對一的關系。然而,當我們將系統(tǒng)微服務化后,持續(xù)集成就變得復雜了。下面介紹兩種在微服務中使用持續(xù)集成的方法,分別是單庫多構建和多庫多構建,并依次介紹這兩種方式的優(yōu)缺點及使用場景。

    4.2.1 單庫多構建

    “單庫”指的是單個代碼倉庫,即整個系統(tǒng)的多個模塊的代碼均由一個代碼倉庫維護。“多構建”指的是持續(xù)集成平臺中的構建項目會有多個,每個構建都會生成一個構建物,如下如所示:

    ?

    ?

    在這種持續(xù)集成的模式中,整個項目的所有代碼均在同一個代碼倉庫中維護。但在持續(xù)集成平臺中,每一項服務都有各自獨立的構建,從而持續(xù)集成平臺能夠為每一項服務產(chǎn)出各自的構建物。

    這種持續(xù)集成的模式在微服務架構中顯然是不合理的。首先,一個系統(tǒng)的可能會有很多服務構成,如果將這些服務的代碼均在同一個代碼倉庫中維護,那么一個程序員在開發(fā)服務A代碼的時候很有可能會因為疏忽,修改了服務B的代碼,此時服務B構建之后就會存在安全隱患,如果這個問題在服務B上線前被發(fā)現(xiàn),那么還好,但無疑增加了額外的工作量;但如果這個問題及其隱諱,導致之前的測試用例沒有覆蓋到,從而服務B會帶著這個問題進入生產(chǎn)環(huán)境,這可能會給企業(yè)帶來巨大的損失。所以,在微服務架構中,盡量選擇多庫多構建模式來實現(xiàn)持續(xù)集成,它將帶來更大的安全性。

    雖然這種模式不合理,但它也有存在的必要性,當我們在項目建設初期的時候,這種模式會給我們帶來更多的便利性。因為項目在建設初期,服務之間的邊界往往是比較模糊的,而且需要經(jīng)過一段時間的演化才能夠構建出穩(wěn)定的邊界。所以如果在項目建設初期直接使用微服務架構,那么服務邊界頻繁地調(diào)整會極大增加系統(tǒng)開發(fā)的復雜度,你要知道,在多個系統(tǒng)之間調(diào)整邊界比在單個系統(tǒng)的多個模塊之間調(diào)整邊界的成本要高很多。所以在項目建設初期,我們可以使用單服務結構,服務內(nèi)部采用模塊作為未來各個微服務的邊界,當系統(tǒng)演化出較為清晰、穩(wěn)定的邊界后再將系統(tǒng)拆分成多個微服務。此時代碼在同一個代碼倉庫中維護是合理的,這也符合敏捷開發(fā)中快速迭代的理念。

    4.2.2 多庫多構建

    ?

    ?

    當系我們的系統(tǒng)擁有了穩(wěn)定、清晰的邊界后,就可以將系統(tǒng)向微服務架構演進。與此同時,持續(xù)集成模式也可以從單庫多構建向多庫多構建演進。

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

    4.3 微服務構建物

    持續(xù)集成平臺對源碼編譯、大包后生成的產(chǎn)物稱為“構建物”。根據(jù)打包的粒度不同,可以將構建物分為如下三種:平臺構建物、操作系統(tǒng)構建物和鏡像構建物。

    4.3.1 平臺構建物

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

    4.3.2 操作系統(tǒng)構建物

    操作系統(tǒng)構建物是將系統(tǒng)打包成一個操作系統(tǒng)可執(zhí)行程序,,如CentOS的RPM包、Windows的MSI包等。這些安裝包可以在操作系統(tǒng)上直接安裝運行。但和平臺構建物相同的是,操作系統(tǒng)構建物往往也需要依賴于其他環(huán)境,所以也需要在部署之前搭建好安裝包所需的依賴。此外,配置操作系統(tǒng)構建物的復雜度較大,構建的成本較高,所以一般不使用這種方式,這里僅作介紹。

    4.3.3 鏡像構建物

    平臺構建物和操作系統(tǒng)構建物都有一個共同的缺點就是需要安裝構建物運行的額外依賴,增加部署復雜度,而鏡像構建物能很好地解決這個問題。

    我們可以把鏡像理解成一個小型操作系統(tǒng),這個操作系統(tǒng)中包含了系統(tǒng)運行所需的所有依賴,并將系統(tǒng)也部署在這個“操作系統(tǒng)”中。這樣當持續(xù)集成平臺構建完這個鏡像后,就可以直接運行它,無需任何依賴的安裝,從而極大簡化了構建的復雜度。但是,鏡像往往比較龐大,構建鏡像的過程也較長,從而當我們將生成的鏡像從持續(xù)集成服務器發(fā)布到部署服務器的時間將會很長,這無疑降低了部署的效率。不過好在Docker的出現(xiàn)解決了這一問題。持續(xù)集成平臺在構建過程中并不需要生成一個鏡像,而只需生成一個鏡像的Dockerfile文件即可。Dockerfile文件用命令定義了鏡像所包含的內(nèi)容,以及鏡像創(chuàng)建的過程。從而持續(xù)集成服務器只需將這個體積較小的鏡像文件發(fā)布到部署服務器上即可。然后部署服務器會通過docker build命令基于這個Dockerfile文件創(chuàng)建鏡像,并創(chuàng)建該鏡像的容器,從而完成服務的部署。

    相對于平臺構建物和操作系統(tǒng)構建物而言,鏡像構建物在部署時不需要安裝額外的環(huán)境依賴,它把環(huán)境依賴的配置都在持續(xù)集成平臺構建Dockerfile文件時完成,從而簡化了部署的過程。

    轉(zhuǎn)載于:https://www.cnblogs.com/weilingfeng/p/11165620.html

    創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

    總結

    以上是生活随笔為你收集整理的浅谈“微服务”的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。