分布式事务,EventBus 解决方案:CAP【中文文档】
前言
很多同學(xué)想對(duì)CAP的機(jī)制以及用法等想有一個(gè)詳細(xì)的了解,所以花了將近兩周時(shí)間寫了這份中文的CAP文檔,對(duì) CAP 還不知道的同學(xué)可以先看一下 .NET Core 事件總線,分布式事務(wù)解決方案:CAP。
本文檔為 CAP 文獻(xiàn)(Wiki),本文獻(xiàn)同時(shí)提供中文和英文版本,英文版本目前還在翻譯中,會(huì)放到Github Wiki 中。
1、Getting Started
1.1 介紹
CAP 是一個(gè)遵循 .NET Standard 標(biāo)準(zhǔn)庫(kù)的C#庫(kù),用來(lái)處理分布式事務(wù)以及提供EventBus的功能,她具有輕量級(jí),高性能,易使用等特點(diǎn)。
目前 CAP 使用的是 .NET Standard 1.6 的標(biāo)準(zhǔn)進(jìn)行開(kāi)發(fā),目前最新預(yù)覽版本已經(jīng)支持 .NET Standard 2.0.
1.2 應(yīng)用場(chǎng)景
CAP 的應(yīng)用場(chǎng)景主要有以下兩個(gè):
分布式事務(wù)中的最終一致性(異步確保)的方案。
分布式事務(wù)是在分布式系統(tǒng)中不可避免的一個(gè)硬性需求,而目前的分布式事務(wù)的解決方案也無(wú)外乎就那么幾種,在了解 CAP 的分布式事務(wù)方案前,可以閱讀以下 這篇文章。
CAP 沒(méi)有采用兩階段提交(2PC)這種事務(wù)機(jī)制,而是采用的 本地消息表+MQ 這種經(jīng)典的實(shí)現(xiàn)方式,這種方式又叫做 異步確保。
具有高可用性的 EventBus。
CAP 實(shí)現(xiàn)了 EventBus 中的發(fā)布/訂閱,它具有 EventBus 的所有功能。也就是說(shuō)你可以像使用 EventBus 一樣來(lái)使用 CAP,另外 CAP 的 EventBus 是具有高可用性的,這是什么意思呢?
CAP 借助于本地消息表來(lái)對(duì) EventBus 中的消息進(jìn)行了持久化,這樣可以保證 EventBus 發(fā)出的消息是可靠的,當(dāng)消息隊(duì)列出現(xiàn)宕機(jī)或者連接失敗的情況時(shí),消息也不會(huì)丟失。
1.3 Quick Start
引用 NuGet 包
使用一下命令來(lái)引用CAP的NuGet包:
PM> Install-Package DotNetCore.CAP根據(jù)使用的不同類型的消息隊(duì)列,來(lái)引入不同的擴(kuò)展包:
PM> Install-Package DotNetCore.CAP.RabbitMQPM> Install-Package DotNetCore.CAP.Kafka根據(jù)使用的不同類型的數(shù)據(jù)庫(kù),來(lái)引入不同的擴(kuò)展包:
PM> Install-Package DotNetCore.CAP.SqlServerPM> Install-Package DotNetCore.CAP.MySql啟動(dòng)配置
在 ASP.NET Core 程序中,你可以在 Startup.cs 文件 ConfigureServices() 中配置 CAP 使用到的服務(wù):
public void ConfigureServices(IServiceCollection services){services.AddDbContext<AppDbContext>();services.AddCap(x =>{ ? ? ? ?// If your SqlServer is using EF for data operations, you need to add the following configuration:// Notice: You don't need to config x.UseSqlServer(""") again!x.UseEntityFramework<AppDbContext>(); ? ? ? ?// If you are using Dapper,you need to add the config:x.UseSqlServer("Your ConnectionStrings"); ? ? ? ?// If your Message Queue is using RabbitMQ you need to add the config:x.UseRabbitMQ("localhost"); ? ? ? ?// If your Message Queue is using Kafka you need to add the config:x.UseKafka("localhost");}); }在 Configure() 中配置啟動(dòng) CAP :
public void Configure(IApplicationBuilder app){app.UseCap(); }2、API接口
CAP 的 API 接口只有一個(gè),就是 ICapPublisher 接口,你可以從 DI 容器中獲取到該接口的實(shí)例進(jìn)行調(diào)用。
2.1 發(fā)布/發(fā)送
你可以使用 ICapPublisher 接口中的 Publish<T> 或者 PublishAsync<T> 方法來(lái)發(fā)送消息:
public class PublishController : Controller{ ? ?private readonly ICapPublisher _publisher; ? ?//在構(gòu)造函數(shù)中獲取接口實(shí)例public PublishController(ICapPublisher publisher) ? ?{_publisher = publisher;}[Route("~/checkAccount")] ? ?public async Task<IActionResult> PublishMessage() ? ?{ ? ? ? ?await _publisher.PublishAsync("xxx.services.account.check", new Person { Name = "Foo", Age = 11 }); ? ?return Ok();} }下面是PublishAsync這個(gè)接口的簽名:
PublishAsync<T>(string name,T object)
默認(rèn)情況下,在調(diào)用此方法的時(shí)候 CAP 將在內(nèi)部創(chuàng)建事務(wù),然后將消息寫入到 Cap.Published 這個(gè)消息表。
2.1.1 事務(wù)
事務(wù)在 CAP 具有重要作用,它是保證消息可靠性的一個(gè)基石。 在發(fā)送一條消息到消息隊(duì)列的過(guò)程中,如果不使用事務(wù),我們是沒(méi)有辦法保證我們的業(yè)務(wù)代碼在執(zhí)行成功后消息已經(jīng)成功的發(fā)送到了消息隊(duì)列,或者是消息成功的發(fā)送到了消息隊(duì)列,但是業(yè)務(wù)代碼確執(zhí)行失敗。
這里的失敗原因可能是多種多樣的,比如連接異常,網(wǎng)絡(luò)故障等等。
只有業(yè)務(wù)代碼和CAP的Publish代碼必須在同一個(gè)事務(wù)中,才能夠保證業(yè)務(wù)代碼和消息代碼同時(shí)成功或者失敗。
以下是兩種使用事務(wù)進(jìn)行Publish的代碼:
EntityFramework
你的業(yè)務(wù)代碼可以位于 Publish 之前或者之后,只需要保證在同一個(gè)事務(wù)。
當(dāng)CAP檢測(cè)到 Publish 是在EF事務(wù)區(qū)域內(nèi)的時(shí)候,將使用當(dāng)前的事務(wù)上下文進(jìn)行消息的存儲(chǔ)。
其中,發(fā)送的內(nèi)容會(huì)序列化為Json存儲(chǔ)到消息表中。
Dapper
在 Dapper 中,由于不能獲取到事務(wù)上下文,所以需要用戶手動(dòng)的傳遞事務(wù)上下文到CAP中。
2.2 訂閱/消費(fèi)
注意:消息端在方法實(shí)現(xiàn)的過(guò)程中需要實(shí)現(xiàn)冪等性。
使用 CapSubscribeAttribute 來(lái)訂閱 CAP 發(fā)布出去的消息。
[CapSubscribe("xxx.services.bar")]public void BarMessageProcessor(){}這里,你也可以使用多個(gè) CapSubscribe[""] 來(lái)同時(shí)訂閱多個(gè)不同的消息 :
[CapSubscribe("xxx.services.bar")] [CapSubscribe("xxx.services.foo")]public void BarAndFooMessageProcessor(){}其中,xxx.services.bar 為訂閱的消息名稱,內(nèi)部實(shí)現(xiàn)上,這個(gè)名稱在不同的消息隊(duì)列具有不同的代表。 在 Kafka 中,這個(gè)名稱即為 Topic Name。 在RabbitMQ 中,為 RouteKey。
RabbitMQ 中的 RouteKey 支持綁定鍵表達(dá)式寫法,有兩種主要的綁定鍵:
*(星號(hào))可以代替一個(gè)單詞.
# (井號(hào)) 可以代替0個(gè)或多個(gè)單詞.
比如在下面這個(gè)圖中(P為發(fā)送者,X為RabbitMQ中的Exchange,C為消費(fèi)者,Q為隊(duì)列)
在這個(gè)示例中,我們將發(fā)送一條關(guān)于動(dòng)物描述的消息,也就是說(shuō) Name(routeKey) 字段中的內(nèi)容包含 3 個(gè)單詞。第一個(gè)單詞是描述速度的(celerity),第二個(gè)單詞是描述顏色的(colour),第三個(gè)是描述哪種動(dòng)物的(species),它們組合起來(lái)類似:“ . . ”。
然后在使用 CapSubscribe 綁定的時(shí)候,Q1綁定為 CapSubscribe["*.orange.*"], Q2 綁定為 CapSubscribe["*.*.rabbit"] 和 [CapSubscribe["lazy.#]。
那么,當(dāng)發(fā)送一個(gè)名為 "quick.orange.rabbit" 消息的時(shí)候,這兩個(gè)隊(duì)列將會(huì)同時(shí)收到該消息。同樣名為 lazy.orange.elephant的消息也會(huì)被同時(shí)收到。另外,名為 "quick.orange.fox" 的消息將僅會(huì)被發(fā)送到Q1隊(duì)列,名為 "lazy.brown.fox" 的消息僅會(huì)被發(fā)送到Q2。"lazy.pink.rabbit" 僅會(huì)被發(fā)送到Q2一次,即使它被綁定了2次。"quick.brown.fox" 沒(méi)有匹配到任何綁定的隊(duì)列,所以它將會(huì)被丟棄。
另外一種情況,如果你違反約定,比如使用 4個(gè)單詞進(jìn)行組合,例如 "quick.orange.male.rabbit",那么它將匹配不到任何的隊(duì)列,消息將會(huì)被丟棄。
但是,假如你的消息名為 "lazy.orange.male.rabbit",那么他們將會(huì)被發(fā)送到Q2,因?yàn)?#(井號(hào))可以匹配 0 或者多個(gè)單詞。
在 CAP 中,我們把每一個(gè)擁有 CapSubscribe[]標(biāo)記的方法叫做訂閱者,你可以把訂閱者進(jìn)行分組。
組(Group),是訂閱者的一個(gè)集合,每一組可以有一個(gè)或者多個(gè)消費(fèi)者,但是一個(gè)訂閱者只能屬于某一個(gè)組。同一個(gè)組內(nèi)的訂閱者訂閱的消息只能被消費(fèi)一次。
如果你在訂閱的時(shí)候沒(méi)有指定組,CAP會(huì)將訂閱者設(shè)置到一個(gè)默認(rèn)的組 cap.default.group。
以下是使用組進(jìn)行訂閱的示例:
[CapSubscribe("xxx.services.foo", Group = "moduleA")]public void FooMessageProcessor(){}2.2.1 例外情況
這里有幾種情況可能需要知道:
① 消息發(fā)布的時(shí)候訂閱方還未啟動(dòng)
Kafka:
當(dāng) Kafka 中,發(fā)布的消息存儲(chǔ)于持久化的日志文件中,所以消息不會(huì)丟失,當(dāng)訂閱者所在的程序啟動(dòng)的時(shí)候會(huì)消費(fèi)掉這些消息。
RabbitMQ:
在 RabbitMQ 中,應(yīng)用程序首次啟動(dòng)會(huì)創(chuàng)建具有持久化的 Exchange 和 Queue,CAP 會(huì)針對(duì)每一個(gè)訂閱者Group會(huì)新建一個(gè)消費(fèi)者隊(duì)列,由于首次啟動(dòng)時(shí)候訂閱者未啟動(dòng)的所以是沒(méi)有隊(duì)列的,消息無(wú)法進(jìn)行持久化,這個(gè)時(shí)候生產(chǎn)者發(fā)的消息會(huì)丟失。
針對(duì)RabbitMQ的消息丟失的問(wèn)題,有兩種解決方式:
i. 部署應(yīng)用程序之前,在RabbitMQ中手動(dòng)創(chuàng)建具有durable特性的Exchange和Queue,默認(rèn)情況他們的名字分別是(cap.default.topic, cap.default.group)。
ii. 提前運(yùn)行一遍所有實(shí)例,讓Exchange和Queue初始化。
我們建議采用第 ii 種方案,因?yàn)楹苋菀鬃龅健?/p>
② 消息沒(méi)有任何訂閱者
如果你發(fā)送了一條個(gè)沒(méi)有被任何訂閱者訂閱的消息,那么此消息將會(huì)被丟棄。
3、配置
Cap 使用 Microsoft.Extensions.DependencyInjection 進(jìn)行配置的注入,你也可以依賴于 DI 從json文件中讀取配置。
3.1 Cap Options
你可以使用如下方式來(lái)配置 CAP 中的一些配置項(xiàng),例如
services.AddCap(capOptions => {capOptions.FailedCallback = //...});CapOptions 提供了一下配置項(xiàng):
| PollingDelay | 處理消息的線程默認(rèn)輪詢等待時(shí)間(秒) | int | 15 秒 |
| QueueProcessorCount | 啟動(dòng)隊(duì)列中消息的處理器個(gè)數(shù) | int | 2 |
| FailedMessageWaitingInterval | 輪詢失敗消息的間隔(秒) | int | 180 秒 |
| FailedCallback | 執(zhí)行失敗消息時(shí)的回調(diào)函數(shù),詳情見(jiàn)下文 | Action | NULL |
CapOptions 提供了 FailedCallback 為處理失敗的消息時(shí)的回調(diào)函數(shù)。當(dāng)消息多次發(fā)送失敗后,CAP會(huì)將消息狀態(tài)標(biāo)記為Failed,CAP有一個(gè)專門的處理者用來(lái)處理這種失敗的消息,針對(duì)失敗的消息會(huì)重新放入到隊(duì)列中發(fā)送到MQ,在這之前如果FailedCallback具有值,那么將首先調(diào)用此回調(diào)函數(shù)來(lái)告訴客戶端。
FailedCallback 的類型為 Action<MessageType,string,string>,第一個(gè)參數(shù)為消息類型(發(fā)送的還是接收到),第二個(gè)參數(shù)為消息的名稱(name),第三個(gè)參數(shù)為消息的內(nèi)容(content)。
3.2 RabbitMQ Options
CAP 采用的是針對(duì) CapOptions 進(jìn)行擴(kuò)展來(lái)實(shí)現(xiàn)RabbitMQ的配置功能,所以針對(duì) RabbitMQ 的配置用法如下:
services.AddCap(capOptions => {capOptions.UseRabbitMQ(rabbitMQOption=>{ ? ? ? ?// rabbitmq options.}); });RabbitMQOptions 提供了有關(guān)RabbitMQ相關(guān)的配置:
| HostName | 宿主地址 | string | localhost |
| UserName | 用戶名 | string | guest |
| Password | 密碼 | string | guest |
| VirtualHost | 虛擬主機(jī) | string | / |
| Port | 端口號(hào) | int | -1 |
| TopicExchangeName | CAP默認(rèn)Exchange名稱 | string | cap.default.topic |
| RequestedConnectionTimeout | RabbitMQ連接超時(shí)時(shí)間 | int | 30,000 毫秒 |
| SocketReadTimeout | RabbitMQ消息讀取超時(shí)時(shí)間 | int | 30,000 毫秒 |
| SocketWriteTimeout | RabbitMQ消息寫入超時(shí)時(shí)間 | int | 30,000 毫秒 |
| QueueMessageExpires | 隊(duì)列中消息自動(dòng)刪除時(shí)間 | int | (10天) 毫秒 |
3.3 Kafka Options
CAP 采用的是針對(duì) CapOptions 進(jìn)行擴(kuò)展來(lái)實(shí)現(xiàn) Kafka 的配置功能,所以針對(duì) Kafka 的配置用法如下:
services.AddCap(capOptions => {capOptions.UseKafka(kafkaOption=>{ ? ? ? ?// kafka options.// kafkaOptions.MainConfig.Add("", "");}); });KafkaOptions 提供了有關(guān) Kafka 相關(guān)的配置,由于Kafka的配置比較多,所以此處使用的是提供的 MainConfig 字典來(lái)支持進(jìn)行自定義配置,你可以查看這里來(lái)獲取對(duì)配置項(xiàng)的支持信息。
https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
3.4 SqlServer Options
如果你使用的是 EntityFramewrok,你用不到該配置項(xiàng)下的內(nèi)容。
CAP 采用的是針對(duì) CapOptions 進(jìn)行擴(kuò)展來(lái)實(shí)現(xiàn) SqlServer 的配置功能,所以針對(duì) SqlServer 的配置用法如下:
services.AddCap(capOptions => {capOptions.UseSqlServer(sqlserverOptions => { ? ? ? // sqlserverOptions.ConnectionString}); });| Schema | Cap表架構(gòu) | string | Cap |
| ConnectionString | 數(shù)據(jù)庫(kù)連接字符串 | string | null |
3.5 MySql Options
如果你使用的是 EntityFramewrok,你用不到該配置項(xiàng)下的內(nèi)容。
CAP 采用的是針對(duì) CapOptions 進(jìn)行擴(kuò)展來(lái)實(shí)現(xiàn) MySql 的配置功能,所以針對(duì) MySql 的配置用法如下:
services.AddCap(capOptions => {capOptions.UseMySql(mysqlOptions => { ? ? ? // mysqlOptions.ConnectionString}); });| TableNamePrefix | Cap表名前綴 | string | cap |
| ConnectionString | 數(shù)據(jù)庫(kù)連接字符串 | string | null |
4、設(shè)計(jì)原理
4.1 動(dòng)機(jī)
隨著微服務(wù)架構(gòu)的流行,越來(lái)越多的人在嘗試使用微服務(wù)來(lái)架構(gòu)他們的系統(tǒng),而在這其中我們會(huì)遇到例如分布式事務(wù)的問(wèn)題,為了解決這些問(wèn)題,我沒(méi)有發(fā)現(xiàn)簡(jiǎn)單并且易于使用的解決方案,所以我決定來(lái)打造這樣一個(gè)庫(kù)來(lái)解決這個(gè)問(wèn)題。
最初 CAP 是為了解決分布式系統(tǒng)中的事務(wù)問(wèn)題,她采用的是 異步確保 這種機(jī)制實(shí)現(xiàn)了分布式事務(wù)的最終一致性,更多這方面的信息可以查看第6節(jié)。
現(xiàn)在 CAP 除了解決分布式事務(wù)的問(wèn)題外,她另外一個(gè)重要的功能就是作為 EventBus 來(lái)使用,她具有 EventBus 的所有功能,并且提供了更加簡(jiǎn)化的方式來(lái)處理EventBus中的發(fā)布/訂閱。
4.2 持久化
CAP 依靠本地?cái)?shù)據(jù)庫(kù)實(shí)現(xiàn)消息的持久化,CAP 使用這種方式來(lái)應(yīng)對(duì)一切環(huán)境或者網(wǎng)絡(luò)異常導(dǎo)致消息丟失的情況,消息的可靠性是分布式事務(wù)的基石,所以在任何情況下消息都不能丟失。
對(duì)于消息的持久化分為兩種:
① 消息進(jìn)入消息隊(duì)列之前的持久化
在消息進(jìn)入到消息隊(duì)列之前,CAP使用本地?cái)?shù)據(jù)庫(kù)表對(duì)消息進(jìn)行持久化,這樣可以保證當(dāng)消息隊(duì)列出現(xiàn)異常或者網(wǎng)絡(luò)錯(cuò)誤時(shí)候消息是沒(méi)有丟失的。
為了保證這種機(jī)制的可靠性,CAP使用和業(yè)務(wù)代碼相同的數(shù)據(jù)庫(kù)事務(wù)來(lái)保證業(yè)務(wù)操作和CAP的消息在持久化的過(guò)程中是強(qiáng)一致的。也就是說(shuō)在進(jìn)行消息持久化的過(guò)程中,任何一方發(fā)生異常情況數(shù)據(jù)庫(kù)都會(huì)進(jìn)行回滾操作。
② 消息進(jìn)入到消息隊(duì)列之后的持久化
消息進(jìn)入到消息隊(duì)列之后,CAP會(huì)啟動(dòng)消息隊(duì)列的持久化功能,我們需要說(shuō)明一下在 RabbitMQ 和 Kafka 中CAP的消息是如何持久化的。
針對(duì)于 RabbitMQ 中的消息持久化,CAP 使用的是具有消息持久化功能的消費(fèi)者隊(duì)列,但是這里面可能有例外情況,參加 2.2.1 章節(jié)。
由于 Kafka 天生設(shè)計(jì)的就是使用文件進(jìn)行的消息持久化,在所以在消息進(jìn)入到Kafka之后,Kafka會(huì)保證消息能夠正確被持久化而不丟失。
4.3 通訊數(shù)據(jù)流
CAP 中消息的流轉(zhuǎn)過(guò)程大致如下:
“ P ” 代表消息發(fā)送者(生產(chǎn)者)。 “ C ” 代表消息消費(fèi)者(訂閱者)。
4.4 一致性
CAP 采用最終一致性作為的一致性方案,此方案是遵循 CAP 理論,以下是CAP理論的描述。
C(一致性)一致性是指數(shù)據(jù)的原子性,在經(jīng)典的數(shù)據(jù)庫(kù)中通過(guò)事務(wù)來(lái)保障,事務(wù)完成時(shí),無(wú)論成功或回滾,數(shù)據(jù)都會(huì)處于一致的狀態(tài),在分布式環(huán)境下,一致性是指多個(gè)節(jié)點(diǎn)數(shù)據(jù)是否一致;
A(可用性)服務(wù)一直保持可用的狀態(tài),當(dāng)用戶發(fā)出一個(gè)請(qǐng)求,服務(wù)能在一定的時(shí)間內(nèi)返回結(jié)果;
P(分區(qū)容忍性)在分布式應(yīng)用中,可能因?yàn)橐恍┓植际降脑驅(qū)е孪到y(tǒng)無(wú)法運(yùn)轉(zhuǎn),好的分區(qū)容忍性,使應(yīng)用雖然是一個(gè)分布式系統(tǒng),但是好像一個(gè)可以正常運(yùn)轉(zhuǎn)的整體
根據(jù) “CAP”分布式理論, 在一個(gè)分布式系統(tǒng)中,我們往往為了可用性和分區(qū)容錯(cuò)性,忍痛放棄強(qiáng)一致支持,轉(zhuǎn)而追求最終一致性。大部分業(yè)務(wù)場(chǎng)景下,我們是可以接受短暫的不一致的。
第 6 節(jié)將對(duì)此做進(jìn)一步介紹。
5、實(shí)現(xiàn)
CAP 封裝了在 ASP.NET Core 中的使用依賴注入來(lái)獲取 Publisher (ICapPublisher)的接口。而啟動(dòng)方式類似于 “中間件” 的形式,通過(guò)在 Startup.cs 配置 ConfigureServices 和 Configure 進(jìn)行啟動(dòng)。
5.1 消息表
當(dāng)系統(tǒng)引入CAP之后并首次啟動(dòng)后,CAP會(huì)在客戶端生成 3 個(gè)表,分別是 Cap.Published, Cap.Received, Cap.Queue。注意表名可能在不同的數(shù)據(jù)庫(kù)具有不同的大小寫區(qū)分,如果你在運(yùn)行項(xiàng)目的時(shí)候沒(méi)有顯式的指定數(shù)據(jù)庫(kù)生成架構(gòu)(SQL Server)或者表名前綴(MySql)的話,默認(rèn)情況下就是以上3個(gè)名字。
Cap.Published:這個(gè)表主要是用來(lái)存儲(chǔ) CAP 發(fā)送到MQ(Message Queue)的客戶端消息,也就是說(shuō)你使用 ICapPublisher 接口 Publish 的消息內(nèi)容。
Cap.Received:這個(gè)表主要是用來(lái)存儲(chǔ) CAP 接收到 MQ(Message Queue) 的客戶端訂閱的消息,也就是使用 CapSubscribe[] 訂閱的那些消息。
Cap.Queue: 這個(gè)表主要是CAP內(nèi)部用來(lái)處理發(fā)送和接收消息的一個(gè)臨時(shí)表,通常情況下,如果系統(tǒng)不出現(xiàn)問(wèn)題,這個(gè)表將是空的。
Published 和 Received 表具有 StatusName 字段,這個(gè)字段用來(lái)標(biāo)識(shí)當(dāng)前消息的狀態(tài)。目前共有 Scheduled,Enqueued,Processing,Successed,Failed 等幾個(gè)狀態(tài)。CAP 在處理消息的過(guò)程中會(huì)依次從 Scheduled 到 Successed 來(lái)改變這些消息狀態(tài)的值。如果是狀態(tài)值為 Successed,代表該消息已經(jīng)成功的發(fā)送到了 MQ 中。如果為 Failed 則代表消息發(fā)送失敗,消息發(fā)送失敗后 CAP 會(huì)對(duì)消息進(jìn)行重試,直到成功。
關(guān)于數(shù)據(jù)清理: CAP 默認(rèn)情況下會(huì)每隔一個(gè)小時(shí)將消息表的數(shù)據(jù)進(jìn)行清理刪除,避免數(shù)據(jù)量過(guò)多導(dǎo)致性能的降低。清理規(guī)則為 ExpiresAt 不為空并且小于當(dāng)前時(shí)間的數(shù)據(jù)。
5.2 消息格式
CAP 采用 JSON 格式進(jìn)行消息傳輸,以下是消息的對(duì)象模型:
| Id | 消息編號(hào) | int |
| Name | 消息名稱 | string |
| Content | 內(nèi)容 | string |
| Group | 所屬消費(fèi)組 | string |
| Added | 創(chuàng)建時(shí)間 | DateTime |
| ExpiresAt | 過(guò)期時(shí)間 | DateTime |
| Retries | 重試次數(shù) | int |
| StatusName | 狀態(tài) | string |
對(duì)于 Cap.Received 中的消息,會(huì)多一個(gè) Group 字段來(lái)標(biāo)記所屬的消費(fèi)者組。
5.3 EventBus
EventBus 采用 發(fā)布-訂閱 風(fēng)格進(jìn)行組件之間的通訊,它不需要顯式在組件中進(jìn)行注冊(cè)。
上圖是EventBus的一個(gè)Event的流程,關(guān)于 EventBus 的更多信息就不在這里介紹了...
在 CAP 中,為什么說(shuō) CAP 實(shí)現(xiàn)了 EventBus 中的全部特性,因?yàn)?EventBus 具有的兩個(gè)大功能就是發(fā)布和訂閱, 在 CAP 中 使用了另外一種優(yōu)雅的方式來(lái)實(shí)現(xiàn)的,另外一個(gè) CAP 提供的強(qiáng)大功能就是消息的持久化,以及在任何異常情況下消息的可靠性,這是EventBus不具有的功能。
frameborder="0" scrolling="no" style="border-width: medium; width: 686px; height: 323px;">
CAP 里面發(fā)送一個(gè)消息可以看做是一個(gè) “Event”,一個(gè)使用了CAP的ASP.NET Core 應(yīng)用程序既可以進(jìn)行發(fā)送也可以進(jìn)行訂閱接收。
5.4 重試
重試在實(shí)現(xiàn)分布式事務(wù)中具有重要作用,CAP 中會(huì)針對(duì)發(fā)送失敗或者執(zhí)行失敗的消息進(jìn)行重試。在整個(gè) CAP 的設(shè)計(jì)過(guò)程中有以下幾處采用的重試策略。
① 消息發(fā)送重試
在消息發(fā)送過(guò)程中,當(dāng)出現(xiàn) Broker 宕機(jī)或者連接失敗的情況亦或者出現(xiàn)異常的情況下,這個(gè)時(shí)候 CAP 會(huì)對(duì)發(fā)送的重試,重試策略為默認(rèn) 15 次失敗重試,當(dāng)15次過(guò)后仍然失敗時(shí),CAP會(huì)將此消息狀態(tài)標(biāo)記為失敗。
② 消息消費(fèi)重試
當(dāng) Consumer 接收到消息時(shí),會(huì)執(zhí)行消費(fèi)者方法,在執(zhí)行消費(fèi)者方法出現(xiàn)異常時(shí),會(huì)進(jìn)行重試。這個(gè)重試策略和 ① 是相同的。
③ 失敗消息重試
CAP 會(huì)定期針對(duì) ① 和 ② 中狀態(tài)為“失敗的”消息進(jìn)行重試,CAP會(huì)對(duì)他們進(jìn)行重新“入隊(duì)(Enqueue)”,入隊(duì)時(shí)會(huì)將消息中的重試次數(shù)標(biāo)記為0,狀態(tài)置為 Enqueued。
6、分布式事務(wù)
針對(duì)于分布式事務(wù)的處理,CAP 采用的是“異步確保”這種方案。
6.1 異步確保
異步確保這種方案又叫做本地消息表,這是一種經(jīng)典的方案,方案最初來(lái)源于 eBay,參考資料見(jiàn)段末鏈接。這種方案目前也是企業(yè)中使用最多的方案之一。
相對(duì)于 TCC 或者 2PC/3PC 來(lái)說(shuō),這個(gè)方案對(duì)于分布式事務(wù)來(lái)說(shuō)是最簡(jiǎn)單的,而且它是去中心化的。在TCC 或者 2PC 的方案中,必須具有事務(wù)協(xié)調(diào)器來(lái)處理每個(gè)不同服務(wù)之間的狀態(tài),而此種方案不需要事務(wù)協(xié)調(diào)器。
另外 2PC/TCC 這種方案如果服務(wù)依賴過(guò)多,會(huì)帶來(lái)管理復(fù)雜性增加和穩(wěn)定性風(fēng)險(xiǎn)增大的問(wèn)題。試想如果我們強(qiáng)依賴 10 個(gè)服務(wù),9 個(gè)都執(zhí)行成功了,最后一個(gè)執(zhí)行失敗了,那么是不是前面 9 個(gè)都要回滾掉?這個(gè)成本還是非常高的。
但是,并不是說(shuō) 2PC 或者 TCC 這種方案不好,因?yàn)槊恳环N方案都有其相對(duì)優(yōu)勢(shì)的使用場(chǎng)景和優(yōu)缺點(diǎn),這里就不做過(guò)多介紹了。
中文:http://www.cnblogs.com/savorboard/p/base-an-acid-alternative.html
英文:http://queue.acm.org/detail.cfm?id=1394128
7、FAQ
暫無(wú)
class="video_iframe" data-vidtype="2" allowfullscreen frameborder="0" data-ratio="1.7647058823529411" data-w="480" scrolling="no" data-src="https://v.qq.com/iframe/preview.html?vid=s0530mhp7py&width=500&height=375&auto=0">
相關(guān)文章
谷歌發(fā)布的首款基于HTTP/2和protobuf的RPC框架:GRPC
擁抱.NET Core,跨平臺(tái)的輕量級(jí)RPC:Rabbit.Rpc
基于DotNet Core的RPC框架(一) DotBPE.RPC快速開(kāi)始
基于.NET CORE微服務(wù)框架 -surging的介紹和簡(jiǎn)單示例 (開(kāi)源)
剝析surging的架構(gòu)思想
基于.NET CORE微服務(wù)框架 -談?wù)剆urging的服務(wù)容錯(cuò)降級(jí)
我眼中的ASP.NET Core之微服務(wù)
.NET Core 事件總線,分布式事務(wù)解決方案:CAP
CAP 介紹及使用【視頻】
原文地址:http://www.cnblogs.com/savorboard/p/cap-document.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的分布式事务,EventBus 解决方案:CAP【中文文档】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ASP.NET Core 源码学习之 O
- 下一篇: IdentityServer4 实现自定