分布式事務(wù)解決方案1.二階段提交(滿足強(qiáng)一致性)第一階段:投票階段****第二階段:事務(wù)提交階段****2.三階段提交****第一階段:can_commit****第二階段:pre_commit****第三階段:do_commit3.可靠消息最終一致性方案****1.本地事務(wù)與消息發(fā)送的原子性問題****2、事務(wù)參與方接收消息的可靠性3、消息重復(fù)消費(fèi)的問題3.2.解決方案3.2.1.本地消息表方案
分布式一致性解決方案
在分布式系統(tǒng)中,同時(shí)滿足“CAP定律”中的“一致性”、“可用性”和“分區(qū)容錯(cuò)性”三者是不可能的,這比現(xiàn)實(shí)中找對象需同時(shí)滿足“高、富、帥”或“白、富、美”更加困難。在互聯(lián)網(wǎng)領(lǐng)域的絕大多數(shù)的場景,都需要犧牲強(qiáng)一致性來換取系統(tǒng)的高可用性,系統(tǒng)往往只需要保證“最終一致性”,只要這個(gè)最終時(shí)間是在用戶可以接受的范圍內(nèi)即可。
分布式事務(wù)解決方案
1.二階段提交(滿足強(qiáng)一致性)
該協(xié)議將一個(gè)分布式的事務(wù)過程拆分成兩個(gè)階段:投票階段和事務(wù)提交階段。為了讓整個(gè)數(shù)據(jù)庫集群能夠正常的運(yùn)行,該協(xié)議指定了一個(gè)“協(xié)調(diào)者”單點(diǎn),用于協(xié)調(diào)整個(gè)數(shù)據(jù)庫集群的運(yùn)行,為了簡化描述,我們將數(shù)據(jù)庫里面的各個(gè)節(jié)點(diǎn)稱為“參與者”,三階段提交協(xié)議中同樣包含“協(xié)調(diào)者”和“參與者”這兩個(gè)定義。
第一階段:投票階段
該階段的主要目的在于打探數(shù)據(jù)庫集群中的各個(gè)參與者是否能夠正常的執(zhí)行事務(wù),具體步驟如下:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n10" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 協(xié)調(diào)者向所有的參與者發(fā)送事務(wù)執(zhí)行請求,并等待參與者反饋事務(wù)執(zhí)行結(jié)果。
2. 事務(wù)參與者收到請求之后,執(zhí)行事務(wù),但不提交,并記錄事務(wù)日志。
3. 參與者將自己事務(wù)執(zhí)行情況反饋給協(xié)調(diào)者,同時(shí)阻塞等待協(xié)調(diào)者的后續(xù)指令。</pre>
第二階段:事務(wù)提交階段
在第一階段協(xié)調(diào)者的詢盤之后,各個(gè)參與者會(huì)回復(fù)自己事務(wù)的執(zhí)行情況,這時(shí)候存在三種可能:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n13" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 所有的參與者回復(fù)能夠正常執(zhí)行事務(wù)
2. 一個(gè)或多個(gè)參與者回復(fù)事務(wù)執(zhí)行失敗
3. 協(xié)調(diào)者等待超時(shí)。</pre>
對于第一種情況,協(xié)調(diào)者將向所有的參與者發(fā)出提交事務(wù)的通知,具體步驟如下:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n15" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 協(xié)調(diào)者向各個(gè)參與者發(fā)送commit通知,請求提交事務(wù)。
2. 參與者收到事務(wù)提交通知之后,執(zhí)行commit操作,然后釋放占有的資源。
3. 參與者向協(xié)調(diào)者返回事務(wù)commit結(jié)果信息。</pre>
對于第二、三種情況,協(xié)調(diào)者均認(rèn)為參與者無法正常成功執(zhí)行事務(wù),為了整個(gè)集群數(shù)據(jù)的一致性,所以要向各個(gè)參與者發(fā)送事務(wù)回滾通知,具體步驟如下:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n17" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 協(xié)調(diào)者向各個(gè)參與者發(fā)送事務(wù)rollback通知,請求回滾事務(wù)。
2. 參與者收到事務(wù)回滾通知之后,執(zhí)行rollback操作,然后釋放占有的資源。
3. 參與者向協(xié)調(diào)者返回事務(wù)rollback結(jié)果信息。</pre>
兩階段提交協(xié)議解決的是分布式數(shù)據(jù)庫數(shù)據(jù)強(qiáng)一致性問題,其原理簡單,易于實(shí)現(xiàn),但是缺點(diǎn)也是顯而易見的,主要缺點(diǎn)如下:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n19" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">- 單點(diǎn)問題
協(xié)調(diào)者在整個(gè)兩階段提交過程中扮演著舉足輕重的作用,一旦協(xié)調(diào)者所在服務(wù)器宕機(jī),那么就會(huì)影響整個(gè)數(shù)據(jù)庫集群的正常運(yùn)行,比如在第二階段中,如果協(xié)調(diào)者因?yàn)楣收喜荒苷0l(fā)送事務(wù)提交或回滾通知,那么參與者們將一直處于阻塞狀態(tài),整個(gè)數(shù)據(jù)庫集群將無法提供服務(wù)。
同步阻塞
兩階段提交執(zhí)行過程中,所有的參與者都需要聽從協(xié)調(diào)者的統(tǒng)一調(diào)度,期間處于阻塞狀態(tài)而不能從事其他操作,這樣效率及其低下。數(shù)據(jù)不一致性
兩階段提交協(xié)議雖然為分布式數(shù)據(jù)強(qiáng)一致性所設(shè)計(jì),但仍然存在數(shù)據(jù)不一致性的可能,比如在第二階段中,假設(shè)協(xié)調(diào)者發(fā)出了事務(wù)commit的通知,但是因?yàn)榫W(wǎng)絡(luò)問題該通知僅被一部分參與者所收到并執(zhí)行了commit操作,其余的參與者則因?yàn)闆]有收到通知一直處于阻塞狀態(tài),這時(shí)候就產(chǎn)生了數(shù)據(jù)的不一致性。</pre>
2.三階段提交
針對兩階段提交存在的問題,三階段提交協(xié)議通過引入一個(gè)“預(yù)詢盤”階段,以及超時(shí)策略來減少整個(gè)集群的阻塞時(shí)間,提升系統(tǒng)性能。三階段提交的三個(gè)階段分別為:can_commit,pre_commit,do_commit。
第一階段:can_commit
該階段協(xié)調(diào)者會(huì)去詢問各個(gè)參與者是否能夠正常執(zhí)行事務(wù),參與者根據(jù)自身情況回復(fù)一個(gè)預(yù)估值,相對于真正的執(zhí)行事務(wù),這個(gè)過程是輕量的,具體步驟如下:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n24" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 協(xié)調(diào)者向各個(gè)參與者發(fā)送事務(wù)詢問通知,詢問是否可以執(zhí)行事務(wù)操作,并等待回復(fù)
2. 各個(gè)參與者依據(jù)自身狀況回復(fù)一個(gè)預(yù)估值,如果預(yù)估自己能夠正常執(zhí)行事務(wù)就返回確定信息,并進(jìn)入預(yù)備狀態(tài),否則返回否定信息</pre>
第二階段:pre_commit
本階段協(xié)調(diào)者會(huì)根據(jù)第一階段的詢盤結(jié)果采取相應(yīng)操作,詢盤結(jié)果主要有三種:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n27" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 所有的參與者都返回確定信息
2. 一個(gè)或多個(gè)參與者返回否定信息
3. 協(xié)調(diào)者等待超時(shí)</pre>
針對第一種情況,協(xié)調(diào)者會(huì)向所有參與者發(fā)送事務(wù)執(zhí)行請求,具體步驟如下:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n29" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 協(xié)調(diào)者向所有的事務(wù)參與者發(fā)送事務(wù)執(zhí)行通知
2. 參與者收到通知后,執(zhí)行事務(wù),但不提交
3. 參與者將事務(wù)執(zhí)行情況返回給客戶端</pre>
在上面的步驟中,如果參與者等待超時(shí),則會(huì)中斷事務(wù)。 針對第二、三種情況,協(xié)調(diào)者認(rèn)為事務(wù)無法正常執(zhí)行,于是向各個(gè)參與者發(fā)出abort通知,請求退出預(yù)備狀態(tài)。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n31" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 協(xié)調(diào)者向所有事務(wù)參與者發(fā)送abort通知
2. 參與者收到通知后,中斷事務(wù)</pre>
第三階段:do_commit
如果第二階段事務(wù)未中斷,那么本階段協(xié)調(diào)者將會(huì)依據(jù)事務(wù)執(zhí)行返回的結(jié)果來決定提交或回滾事務(wù),分為三種情況:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n34" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 所有的參與者都能正常執(zhí)行事務(wù)
2. 一個(gè)或多個(gè)參與者執(zhí)行事務(wù)失敗
3. 協(xié)調(diào)者等待超時(shí)</pre>
針對第一種情況,協(xié)調(diào)者向各個(gè)參與者發(fā)起事務(wù)提交請求,具體步驟如下:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n36" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 協(xié)調(diào)者向所有參與者發(fā)送事務(wù)commit通知
2. 所有參與者在收到通知之后執(zhí)行commit操作,并釋放占有的資源
3. 參與者向協(xié)調(diào)者反饋事務(wù)提交結(jié)果</pre>
針對第二、三種情況,協(xié)調(diào)者認(rèn)為事務(wù)無法正常執(zhí)行,于是向各個(gè)參與者發(fā)送事務(wù)回滾請求,具體步驟如下:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n38" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">1. 協(xié)調(diào)者向所有參與者發(fā)送事務(wù)rollback通知
2. 所有參與者在收到通知之后執(zhí)行rollback操作,并釋放占有的資源
3. 參與者向協(xié)調(diào)者反饋事務(wù)提交結(jié)果</pre>
在本階段如果因?yàn)閰f(xié)調(diào)者或網(wǎng)絡(luò)問題,導(dǎo)致參與者遲遲不能收到來自協(xié)調(diào)者的commit或rollback請求,那么參與者將不會(huì)如兩階段提交中那樣陷入阻塞,而是等待超時(shí)后繼續(xù)commit。相對于兩階段提交雖然降低了同步阻塞,但仍然無法避免數(shù)據(jù)的不一致性。
在分布式數(shù)據(jù)庫中,如果期望達(dá)到數(shù)據(jù)的強(qiáng)一致性,那么服務(wù)基本沒有可用性可言,這也是為什么許多分布式數(shù)據(jù)庫提供了跨庫事務(wù),但也只是個(gè)擺設(shè)的原因,在實(shí)際應(yīng)用中我們更多追求的是數(shù)據(jù)的弱一致性或最終一致性,為了強(qiáng)一致性而丟棄可用性是不可取的。
3.可靠消息最終一致性方案
可靠消息最終一致性方案是指當(dāng)事務(wù)發(fā)起方執(zhí)行完成本地事務(wù)后并發(fā)出一條消息,事務(wù)參與方(消息消費(fèi)者)一定能夠接收消息并處理事務(wù)成功,此方案強(qiáng)調(diào)的是只要消息發(fā)給事務(wù)參與方最終事務(wù)要達(dá)到一致。
此方案是利用消息中間件完成,如下圖:
事物發(fā)起方--->網(wǎng)絡(luò)--->消息中間件--->網(wǎng)絡(luò)--->事物消費(fèi)方
事務(wù)發(fā)起方(消息生產(chǎn)方)將消息發(fā)給消息中間件,事務(wù)參與方從消息中間件接收消息,事務(wù)發(fā)起方和消息中間件之間,事務(wù)參與方(消息消費(fèi)方)和消息中間件之間都是通過網(wǎng)絡(luò)通信,由于網(wǎng)絡(luò)通信的不確定性會(huì)導(dǎo)致分布式事務(wù)問題。
因此可靠消息最終一致性方案要解決以下幾個(gè)問題:
1.本地事務(wù)與消息發(fā)送的原子性問題
本地事務(wù)與消息發(fā)送的原子性問題即:事務(wù)發(fā)起方在本地事務(wù)執(zhí)行成功后消息必須發(fā)出去,否則就丟棄消息。即實(shí)現(xiàn)本地事務(wù)和消息發(fā)送的原子性,要么都成功,要么都失敗。本地事務(wù)與消息發(fā)送的原子性問題是實(shí)現(xiàn)可靠消息最終一致性方案的關(guān)鍵問題。
先來嘗試下這種操作,先發(fā)送消息,再操作數(shù)據(jù)庫:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n50" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">begin transaction;
//1.發(fā)送MQ
//2.數(shù)據(jù)庫操作
commit transation;</pre>
這種情況下無法保證數(shù)據(jù)庫操作與發(fā)送消息的一致性,因?yàn)榭赡馨l(fā)送消息成功,數(shù)據(jù)庫操作失敗。 你立馬想到第二種方案,先進(jìn)行數(shù)據(jù)庫操作,再發(fā)送消息:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n52" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">begin transaction;
//1.數(shù)據(jù)庫操作
//2.發(fā)送MQ
commit transation;</pre>
這種情況下貌似沒有問題,如果發(fā)送MQ消息失敗,就會(huì)拋出異常,導(dǎo)致數(shù)據(jù)庫事務(wù)回滾。但如果是超時(shí)異常,數(shù)據(jù)庫回滾,但MQ其實(shí)已經(jīng)正常發(fā)送了,同樣會(huì)導(dǎo)致不一致。
2、事務(wù)參與方接收消息的可靠性
事務(wù)參與方必須能夠從消息隊(duì)列接收到消息,如果接收消息失敗可以重復(fù)接收消息。
3、消息重復(fù)消費(fèi)的問題
由于網(wǎng)絡(luò)2的存在,若某一個(gè)消費(fèi)節(jié)點(diǎn)超時(shí)但是消費(fèi)成功,此時(shí)消息中間件會(huì)重復(fù)投遞此消息,就導(dǎo)致了消息的重復(fù)消費(fèi)。
要解決消息重復(fù)消費(fèi)的問題就要實(shí)現(xiàn)事務(wù)參與方的方法冪等性。
3.2.解決方案
上節(jié)討論了可靠消息最終一致性事務(wù)方案需要解決的問題,本節(jié)討論具體的解決方案。
3.2.1.本地消息表方案
本地消息表這個(gè)方案最初是eBay提出的,此方案的核心是通過本地事務(wù)保證數(shù)據(jù)業(yè)務(wù)操作和消息的一致性,然后通過定時(shí)任務(wù)將消息發(fā)送至消息中間件,待確認(rèn)消息發(fā)送給消費(fèi)方成功再將消息刪除。
下面以注冊送積分為例來說明:,
下例共有兩個(gè)微服務(wù)交互,用戶服務(wù)和積分服務(wù),用戶服務(wù)負(fù)責(zé)添加用戶,積分服務(wù)負(fù)責(zé)增加積分。

交互流程如下:
1、用戶注冊
用戶服務(wù)在本地事務(wù)新增用戶和增加 ”積分消息日志“。(用戶表和消息表通過本地事務(wù)保證一致)
下邊是偽代碼
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n70" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">begin transaction;
//1.新增用戶
//2.存儲(chǔ)積分消息日志
commit transation;</pre>
這種情況下,本地?cái)?shù)據(jù)庫操作與存儲(chǔ)積分消息日志處于同一個(gè)事務(wù)中,本地?cái)?shù)據(jù)庫操作與記錄消息日志操作具備原子性。
2、定時(shí)任務(wù)掃描日志
如何保證將消息發(fā)送給消息隊(duì)列呢?
經(jīng)過第一步消息已經(jīng)寫到消息日志表中,可以啟動(dòng)獨(dú)立的線程,定時(shí)對消息日志表中的消息進(jìn)行掃描并發(fā)送至消息中間件,在消息中間件反饋發(fā)送成功后刪除該消息日志,否則等待定時(shí)任務(wù)下一周期重試。
3、消費(fèi)消息
如何保證消費(fèi)者一定能消費(fèi)到消息呢?
這里可以使用MQ的ack(即消息確認(rèn))機(jī)制,消費(fèi)者監(jiān)聽MQ,如果消費(fèi)者接收到消息并且業(yè)務(wù)處理完成后向MQ發(fā)送ack(即消息確認(rèn)),此時(shí)說明消費(fèi)者正常消費(fèi)消息完成,MQ將不再向消費(fèi)者推送消息,否則消費(fèi)者會(huì)不斷重試向消費(fèi)者來發(fā)送消息。
積分服務(wù)接收到”增加積分“消息,開始增加積分,積分增加成功后向消息中間件回應(yīng)ack,否則消息中間件將重復(fù)投遞此消息。
由于消息會(huì)重復(fù)投遞,積分服務(wù)的”增加積分“功能需要實(shí)現(xiàn)冪等性。