在2.2以后的版本中,我们调整了一些消息的流转流程,我们移除了数据库中的 Queue 表使用内存队列来代替,详情见:[Improve the implementation mechanism of queue mode](https://github.com/dotnetcore/CAP/issues/96)
CAP 是一个遵循 .NET Standard 标准库的C#库,用来处理分布式事务以及提供EventBus的功能,她具有轻量级,高性能,易使用等特点。
目前 CAP 使用的是 .NET Standard 1.6 的标准进行开发,目前最新预览版本已经支持 .NET Standard 2.0.
### 应用场景
CAP 的应用场景主要有以下两个:
* 1. 分布式事务中的最终一致性(异步确保)的方案。
分布式事务是在分布式系统中不可避免的一个硬性需求,而目前的分布式事务的解决方案也无外乎就那么几种,在了解 CAP 的分布式事务方案前,可以阅读以下 [这篇文章](http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency)。
CAP 没有采用两阶段提交(2PC)这种事务机制,而是采用的 本地消息表+MQ 这种经典的实现方式,这种方式又叫做 异步确保。
* 2. 具有高可用性的 EventBus。
CAP 实现了 EventBus 中的发布/订阅,它具有 EventBus 的所有功能。也就是说你可以像使用 EventBus 一样来使用 CAP,另外 CAP 的 EventBus 是具有高可用性的,这是什么意思呢?
CAP 借助于本地消息表来对 EventBus 中的消息进行了持久化,这样可以保证 EventBus 发出的消息是可靠的,当消息队列出现宕机或者连接失败的情况时,消息也不会丢失。
### Quick Start
***引用 NuGet 包**
使用一下命令来引用CAP的NuGet包:
```
PM> Install-Package DotNetCore.CAP
```
根据使用的不同类型的消息队列,来引入不同的扩展包:
```
PM> Install-Package DotNetCore.CAP.RabbitMQ
PM> Install-Package DotNetCore.CAP.Kafka
```
根据使用的不同类型的数据库,来引入不同的扩展包:
```
PM> Install-Package DotNetCore.CAP.SqlServer
PM> Install-Package DotNetCore.CAP.MySql
PM> Install-Package DotNetCore.CAP.PostgreSql
PM> Install-Package DotNetCore.CAP.MongoDB
```
***启动配置**
在 ASP.NET Core 程序中,你可以在 `Startup.cs` 文件 `ConfigureServices()` 中配置 CAP 使用到的服务:
In this overload method, `callbackName` is the callback name of the subscription method,when the consumption-side finished processing messages,CAP will return the processed result and also call the specified subscription method
### Transactions
Transaction plays a very import role in CAP, It is a main factor to ensure the reliability of messaging.
In the process of sending a message to the message queue without transaction we can not ensure that messages are sent to the message queue successfully after we finish dealing the business logic,or messages are send to the message queque successfully but our bussiness logic is failed.
There is a variety of reasons that causing failure,eg:connection errors,network errors,etc.
!!! note
Only by putting the business logic and logic in the Publish of CAP in the same transaction so that we can enssure both them to be success or fail
The following two blocks of code snippet demonstrate how to use transactions in EntityFramework and dapper when publishing messages.
When you set the `autoCommit: false`, you can put your business logic before or after the Publish logic,the only thing you need to do is to ensure that they are in the same transaction.
If you set the `autoCommit: true`, you need publish message `_capBus.Publish` at the last.
During the course,the message content will be serialized as json and stored in the message table.
The businsess logics in the subscription side should be keep idempotent.
You can view more details in this [ISSUE](https://github.com/dotnetcore/CAP/issues/29#issuecomment-451841287).
Use `CapSubscribe[""]` to decorate a method so that it can subscribe messages published by CAP.
```c#
[CapSubscribe("xxx.services.bar")]
publicvoidBarMessageProcessor()
{
}
```
You can also use multiple `CapSubscribe[""]` to decorate a method so that you can subscribe messages from different sources accordingly.
```c#
[CapSubscribe("xxx.services.bar")]
[CapSubscribe("xxx.services.foo")]
publicvoidBarAndFooMessageProcessor()
{
}
```
`xxx.services.bar` is the name of the message to be subscribed.And it has different name in different message queque Clients.for example,in kafka the name is called Topic Name and in RAbbitMQ it is called RouteKey.
In RabbitMQ you can use regular expression in RouteKey:
<blockquote>
<p><citetitle="Source Title">\*</cite> (Asterisk) stands for a single word.</p>
<p><citetitle="Source Title">#</cite> (hash sign) standards for zero or more words.</p>
<pclass="small">See the following picture(P for Publisher,X for Exchange,C for consumer and Q for Queue)</p>
<pclass="small">In this example, we're going to send messages which all describe animals. The messages will be sent with a routing key that consists of three words (two dots). The first word in the routing key will describe a celerity, second a colour and third a species: "<celerity>.<colour>.<species>".</p>
<pclass="small">We created three bindings: Q1 is bound with binding key "*.orange.*" and Q2 with "*.*.rabbit" and "lazy.#".</p>
<pclass="small">These bindings can be summarised as:</p>
<pclass="small">Q1 is interested in all the orange animals.Q2 wants to hear everything about rabbits, and everything about lazy animals.A message with a routing key set to "quick.orange.rabbit" will be delivered to both queues. Message "lazy.orange.elephant" also will go to both of them. On the other hand "quick.orange.fox" will only go to the first queue, and "lazy.brown.fox" only to the second. "lazy.pink.rabbit" will be delivered to the second queue only once, even though it matches two bindings. "quick.brown.fox" doesn't match any binding so it will be discarded.</p>
<pclass="small">What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost.</p>
<pclass="small">On the other hand "lazy.orange.male.rabbit", even though it has four words, will match the last binding and will be delivered to the second queue.</p>
</blockquote>
In CAP, we called a method decorated by `CapSubscribe[]` a **subscriber**, you can group different subscribers.
**Group** is a collection of subscribers,each group can have one or multiple consumers,but a subscriber can only belongs to a certain group(you can not put a subscriber into multiple groups).Messages subscribed by members in a certain group can only be consumed once.
If you do not specify any group when subscribing,CAP will put the subscriber into a default group named `cap.default.group`
the following is a demo shows how to use group when subscribing.
**① the subscription side has not started yet when publishing a message**
#### Kafka
In Kafka,published messages stored in the Persistent log files,so messages will not lost.when the subscription side started,it can still consume the message.
#### RabbitMQ
In RabbitMQ, the application will create Persistent Exchange and Queue at the **first start**, CAP will create a new consumer queue for each consumer group,**because the application started but the subscription side hasn's start yet so there has no queue,thus the message can not be persited,and the published messages will lost**
There are two ways to solve this `message lost` issue in RamitMQ:
* Before the deployment of your application,you can create durable Exchange and Queue in RabbitMQ by hand,the default names them are (cap.default.topic, cap.default.group).
* Run all instances in advance to ensure that both Exchange and Queue are initialized.
It is highly recommanded that users adopt the second way,because it is easier to achieve.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Sagas (also known in the literature as "process managers") are stateful services. You can think of them as state machines whose transitions are driven by messages.
<ahref="https://github.com/dotnetcore/cap/issues"><buttondata-md-color-primary="purple"type="submit"> Active Issues <iclass="fa fa-github fa-2x"></i></button></a>
## Submitting Changes
You can also contribute by submitting pull requests with code changes.
>
Pull requests let you tell others about changes you've pushed to a repository on GitHub. Once a pull request is opened, you can discuss and review the potential changes with collaborators and add follow-up commits before the changes are merged into the repository.
## Additional Resources
*[Filtering issues and pull requests](https://help.github.com/articles/filtering-issues-and-pull-requests/)
*[Using search to filter issues and pull requests](https://help.github.com/articles/using-search-to-filter-issues-and-pull-requests/)
CAP 是一个EventBus,同时也是一个在微服务或者SOA系统中解决分布式事务问题的一个框架。它有助于创建可扩展,可靠并且易于更改的微服务系统。
在微软的 [eShopOnContainer](https://github.com/dotnet-architecture/eShopOnContainers) 微服务示例项目中,推荐使用 CAP 作为生产环境可用的 EventBus。
!!! question "什么是 EventBus?"
An Eventbus is a mechanism that allows different components to communicate with each other without knowing about each other. A component can send an Event to the Eventbus without knowing who will pick it up or how many others will pick it up. Components can also listen to Events on an Eventbus, without knowing who sent the Events. That way, components can communicate without depending on each other. Also, it is very easy to substitute a component. As long as the new component understands the Events that are being sent and received, the other components will never know.
相对于其他的 Service Bus 或者 Event Bus, CAP 拥有自己的特色,它不要求使用者发送消息或者处理消息的时候实现或者继承任何接口,拥有非常高的灵活性。我们一直坚信约定大于配置,所以CAP使用起来非常简单,对于新手非常友好,并且拥有轻量级。
CAP 采用模块化设计,具有高度的可扩展性。你有许多选项可以选择,包括消息队列,存储,序列化方式等,系统的许多元素内容可以替换为自定义实现。
!!! faq "Any IM group(e.g Tencent QQ group) to learn and chat about CAP?"
None for that. Better than wasting much time in IM group, I hope developers could be capable of independent thinking more, and solve problems yourselves with referenced documents, even create issues or send emails when errors are remaining present.
!!! faq "Does it require certain different databases, one each for productor and resumer in CAP?"
Not requird differences necessary, a given advice is that using a special database for each program.
Otherwise, look at Q&A below.
!!! faq "How to use the same database for different applications?"
defining a prefix name of table in `ConfigureServices` method。
codes exsample:
```c#
public void ConfigureServices(IServiceCollection services)
{
services.AddCap(x =>
{
x.UseKafka("");
x.UseMySql(opt =>
{
opt.ConnectionString = "connection string";
opt.TableNamePrefix = "appone"; // different table name prefix here
});
});
}
```
!!! faq "Can CAP not use the database as event storage? I just want to sent the message"
Not yet.
The purpose of CAP is that ensure consistency principle right in microservice or SOA architechtrues. The solution is based on ACID features of database, there is no sense about a single client wapper of message queue without database.
!!! faq "If the consumer is abnormal, can I roll back the database executed sql that the producer has executed?"
Can't roll back, CAP is the ultimate consistency solution.
You can imagine your scenario is to call a third party payment. If you are doing a third-party payment operation, after calling Alipay's interface successfully, and your own code is wrong, will Alipay roll back? If you don't roll back, what should you do? The same is true here.
Transports move data from one place to another – between acquisition programs and pipelines, between pipelines and the entity database, and even between pipelines and external systems.
The CAP uses the CapOptions extension to implement the RabbitMQ configuration function. Therefore, the configuration of the RabbitMQ is used as follows:
```cs
services.AddCap(capOptions=>{
capOptions.UseRabbitMQ(rabbitMQOption=>{
// rabbitmq options.
});
});
```
`RabbitMQOptions` provides related RabbitMQ configuration:
NAME | DESCRIPTION | TYPE | DEFAULT
:---|:---|---|:------
HostName | Host Address | string | localhost
UserName | username | string | guest
Password | Password | string | guest
VirtualHost | Virtual Host | string | /
Port | Port number | int | -1
TopicExchangeName | CAP Default Exchange Name | string | cap.default.topic
RequestedConnectionTimeout | RabbitMQ Connection Timeout | int | 30,000 milliseconds
CAP uses Microsoft.Extensions.DependencyInjection for configuration injection.
## CAP Configs
You can use the following methods to configure some configuration items in the CAP, for example:
```cs
services.AddCap(capOptions=>{
capOptions.FailedCallback=//...
});
```
`CapOptions` provides the following configuration items::
NAME | DESCRIPTION | TYPE | DEFAULT
:---|:---|---|:------
DefaultGroup | Default consumer group to which the subscriber belongs | string | cap.queue+{assembly name}
SuccessedMessageExpiredAfter | Expiration date after successful message was deleted | int | 3600 seconds
FailedCallback|Callback function when the failed message is executed. See below for details | Action | NULL
FailedRetryInterval | Failed Retry Interval | int | 60 seconds
FailedRetryCount | Failed RetryCount | int | 50th
CapOptions provides a callback function for `FailedCallback` to handle failed messages. When the message fails to be sent multiple times, the CAP will mark the message state as `Failed`. The CAP has a special handler to handle this failed message. The failed message will be put back into the queue and sent to MQ. Prior to this, if `FailedCallback` has a value, this callback function will be called first to tell the client.
The type of FailedCallback is `Action<MessageType,string,string>`. The first parameter is the message type (send or receive), the second parameter is the name of the message, and the third parameter is the content of the message.
## RabbitMQ Configs
The CAP uses the CapOptions extension to implement the RabbitMQ configuration function. Therefore, the configuration of the RabbitMQ is used as follows:
```cs
services.AddCap(capOptions=>{
capOptions.UseRabbitMQ(rabbitMQOption=>{
// rabbitmq options.
});
});
```
`RabbitMQOptions` provides related RabbitMQ configuration:
NAME | DESCRIPTION | TYPE | DEFAULT
:---|:---|---|:------
HostName | Host Address | string | localhost
UserName | username | string | guest
Password | Password | string | guest
VirtualHost | Virtual Host | string | /
Port | Port number | int | -1
TopicExchangeName | CAP Default Exchange Name | string | cap.default.topic
RequestedConnectionTimeout | RabbitMQ Connection Timeout | int | 30,000 milliseconds
QueueMessageExpires | Automatic deletion of messages in queue | int | (10 days) ms
### Kafka Configs
CAP adopts Kafka's configuration function to expand CapOptions, so the configuration usage for Kafka is as follows:
```cs
services.AddCap(capOptions=>{
capOptions.UseKafka(kafkaOption=>{
// kafka options.
// kafkaOptions.MainConfig.Add("", "");
});
});
```
`KafkaOptions` provides Kafka-related configurations. Because Kafka has more configurations, the MainConfig dictionary provided here is used to support custom configurations. You can check here to get support information for configuration items.
If you are using Entityframework as a message persistence store, then you can customize some configuration when configuring the CAP EntityFramework configuration item.
```cs
services.AddCap(x=>
{
x.UseEntityFramework<AppDbContext>(efOption=>
{
// entityframework options.
});
});
```
Note that if you use the `UseEntityFramework` configuration item, then you do not need to reconfigure the following sections for several different database configurations. The CAP will automatically read the database configuration information used in DbContext.
NAME | DESCRIPTION | TYPE | DEFAULT
:---|:---|---|:------
Schema | Cap table schema | string | Cap (SQL Server)
Schema | Cap table schema | string | cap (PostgreSql)
TableNamePrefix | Cap table name prefix | string | cap (MySql)
### SqlServer Configs
Note that if you are using EntityFramewrok, you do not use this configuration item.
CAP adopts the configuration function of SqlServer for extending CapOptions. Therefore, the configuration usage of SqlServer is as follows:
With the popularity of microservices architecture, more and more people are trying to use microservices to architect their systems. In this we encounter problems such as distributed transactions. To solve these problems, I did not find simplicity and Easy to use solution, so I decided to create such a library to solve this problem.
The original CAP was to solve the transaction problems in the distributed system. She used asynchronous to ensure that this weak consistency transaction mechanism achieved the eventual consistency of the distributed transaction. For more information, see section 6.
Now in addition to solving distributed transaction problems, CAP's other important function is to use it as an EventBus. It has all of the features of EventBus and provides a more simplified way to handle publish/subscribe in EventBus.
## Persistence
The CAP relies on the local database for persistence of messages. The CAP uses this method to deal with situations in which all messages are lost due to environmental or network anomalies. The reliability of messages is the cornerstone of distributed transactions, so messages cannot be lost under any circumstances.
There are two types of persistence for messages:
**1 Persistence before the message enters the message queue**
Before the message enters the message queue, the CAP uses the local database table to persist the message. This ensures that the message is not lost when the message queue is abnormal or the network error occurs.
In order to ensure the reliability of this mechanism, CAP uses database transactions with the same business code to ensure that business operations and CAP messages are strongly consistent throughout the persistence process. That is to say, in the process of message persistence, the database of any abnormal situation will be rolled back.
**2 Persistence after messages enter the message queue**
After the message enters the message queue, the CAP starts the persistence function of the message queue. We need to explain how the message of the CAP in RabbitMQ and Kafka is persistent.
For message persistence in RabbitMQ, CAP uses a consumer queue with message persistence, but there may be exceptions to this and take part in 2.2.1.
Since Kafka is inherently designed to persist messages using files, Kafka ensures that messages are correctly persisted without loss after the message enters Kafka.
## Communication Data Streams
The flow of messages in the CAP is roughly as follows:
> "P" represents the sender of the message (producer). "C" stands for message consumer (subscriber).
**After version 2.2**
In the 2.2 and later versions, we adjusted the flow of some messages. We removed the Queue table in the database and used the memory queue instead. For details, see: [Improve the implementation mechanism of queue mode](https://github.com/dotnetcore/CAP/issues/96)
## Consistency
The CAP uses the ultimate consistency as a consistent solution. This solution follows the CAP theory. The following is the description of the CAP theory.
C (consistent) consistency refers to the atomicity of data. It is guaranteed by transactions in a classic database. When a transaction completes, the data will be in a consistent state regardless of success or rollback. In a distributed environment, consistency is Indicates whether the data of multiple nodes is consistent;
A (availability) service is always available, when the user sends a request, the service can return the result within a certain time;
P (Partition Tolerance) In distributed applications, the system may not operate due to some distributed reasons. The good partition tolerance makes the application a distributed system but it seems to be a functioning whole.
According to ["CAP" distributed theory](https://en.wikipedia.org/wiki/CAP_theorem), in a distributed system, we often reluctantly give up strong consensus support for availability and partition fault tolerance, and instead pursue Ultimate consistency. In most business scenarios, we can accept short-term inconsistencies.
!!! faq "Any IM group(e.g Tencent QQ group) to learn and chat about CAP?"
None for that. Better than wasting much time in IM group, I hope developers could be capable of independent thinking more, and solve problems yourselves with referenced documents, even create issues or send emails when errors are remaining present.
!!! faq "Does it require certain different databases, one each for productor and resumer in CAP?"
Not requird differences necessary, a given advice is that using a special database for each program.
Otherwise, look at Q&A below.
!!! faq "How to use the same database for different applications?"
defining a prefix name of table in `ConfigureServices` method。
codes exsample:
```c#
public void ConfigureServices(IServiceCollection services)
{
services.AddCap(x =>
{
x.UseKafka("");
x.UseMySql(opt =>
{
opt.ConnectionString = "connection string";
opt.TableNamePrefix = "appone"; // different table name prefix here
});
});
}
```
!!! faq "Can CAP not use the database as event storage? I just want to sent the message"
Not yet.
The purpose of CAP is that ensure consistency principle right in microservice or SOA architechtrues. The solution is based on ACID features of database, there is no sense about a single client wapper of message queue without database.
!!! faq "If the consumer is abnormal, can I roll back the database executed sql that the producer has executed?"
Can't roll back, CAP is the ultimate consistency solution.
You can imagine your scenario is to call a third party payment. If you are doing a third-party payment operation, after calling Alipay's interface successfully, and your own code is wrong, will Alipay roll back? If you don't roll back, what should you do? The same is true here.
#### 1. Distributed transaction alternative solution in micro-service base on eventually consistency
A distributed transaction is a very complex process with a lot of moving parts that can fail. Also, if these parts run on different machines or even in different data centers, the process of committing a transaction could become very long and unreliable.
This could seriously affect the user experience and overall system bandwidth. So one of the best ways to solve the problem of distributed transactions is to avoid them completely.
Usually, a microservice is designed in such way as to be independent and useful on its own. It should be able to solve some atomic business task.
If we could split our system in such microservices, there’s a good chance we wouldn’t need to implement transactions between them at all.
By far, one of the most feasible models of handling consistency across microservices is eventual consistency. This model doesn’t enforce distributed ACID transactions across microservices. Instead, it proposes to use some mechanisms of ensuring that the system would be eventually consistent at some point in the future.
CAP ia an alternative solution without transactions, it comply the eventually consistency and implement base on message queue.
#### 2. EventBus with Outbox pattern
CAP is an event bus that implements the Outbox pattern, Outbox is an infrastructure feature which simulates the reliability of distributed transactions without requiring use of the Distributed Transaction Coordinator(DTC).
The outbox feature can be used instead of the DTC to mimic the same level of consistency without using distributed transactions.
!!! Tip "CAP implements the Outbox Pattern described in the [eShop ebook](https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/subscribe-events#designing-atomicity-and-resiliency-when-publishing-to-the-event-bus)"
Users can get a ICapPublisher interface from the ASP.NET Core DI container to publish a message .It is initialized by configurations in the `ConfigureServices` and `configure` method in the Startup.cs file,just like the way to initialize a `MiddleWare` in ASP.NET Core.
## Message Table
After initialized, CAP will create two tables in the client side,they are `Cap.Published` and `Cap.Received`. Please noted that different databases may deal letter case differently,if you do not explicitly specify the Schema or the TableName Prefix before project startup,the default names are the ones mentioned above.
**Cap.Published**:Used to store messages(Published by the `ICapPublisher` service) that CAP published to the MQ(Message Queue)Client side
**Cap.Received**:Used to Store messages(subscribed by the `CapSubscribe[]`) subscribed by the MQ(message Queue) client side that CAP received.
Both `Published` and `Received` tables have a `StatusName` field,which is used to mark the status of the current message.Until now it has `Scheduled`,`Successed` and `Failed` statuses.
In the process of dealing with messages,CAP will change the status from `Scheduled` to `Successed`(or `Failed` ).if the final status is `Successed`,it means that the message is sent to MQ successfully,and `Failed` means the message is failed to sent to MQ.
Version later than 2.2, CAP will retry after 4 minutes if the status is `Scheduled` or `Failed`,the retry interval is default to 60 seconds.You can change it by modify `FailedRetryInterval` in `CapOptions`.
## Message format
CAP use JSON to transfer message,the following is CAP's messaging object model:
NAME | DESCRIPTION | TYPE
:---|:---|:---
Id | Message Id | int
Version | Message Version | string
Name | Name | string
Content | Content | string
Group | Group a message belongs to | string
Added |add time | DateTime
ExpiresAt | expire time | DateTime
Retries | retry times | int
StatusName | Status Name | string
>for `Cap.Received`,there is an extra `Group` filed to mark which group the mesage belongs to.
>for the `Content` property CAP will use a Messsage object to wrap all the contents.The following shows details of the Message Object:
NAME | DESCRIPTION | TYPE
:---|:---|:---
Id | Generated by CAP | string
Timestamp | message create time | string
Content | content | string
CallbackName | the subscriber which is used to call back | string
CAP use the same algorithms as MongoDB ObjectId's distributed Id generation algorithms.
## EventBus
EventBus adopt the publish-subscribe messaging style to communicate with different components,and there is no need to register it in component explicitly.
the diagram in the above link shows Eventbus's event flowchart,about EventBus,users can refer to other meterials to learn about it.
We say that CAP implement all the features in Eventbus,EventBus has two features:publish and subscribe,In CAP we implement them in an elegant way.Besides,CAP also has two very robust feature,they are message persistence and messaging reliability under any circumstances,But EventBus don't have such features.
In CAP,send a message can be regarded as an "Event",When CAP is used in an ASP.NET Core applicaiton,the application has the ablity to publish as well as receive messages.
## Retry
Retry plays a very important role in CAP's infrastructure,CAP will retry for Failed messages.CAP has the following retry strategies:
**1、 Retry on sending**
in the process of sending a message,when the Broker crashed or connection failed or exceptions are thrown,CAP will retry,it will retry 3 times for the first time,if still failed,then it will retry every 1 minute after 4 minutes,the retry the retry count +1,when the retry count come to 50,CAP will not retry any more.
>You can modify `FailedRetryCount` in `CapOptions` to change the default retry count.
As metioned above,when the retry count comes to a certain number,CAP will not retry anymore,this time,you can find out the fail reason in the Dashboard and they deal with it manually.
**2、 Retry on Consuming**
When consumer received messages,specified method in the consumer will be executed,if exceptions are thrown during this course,CAP will retry,the retry strategy is the same as above `Retry on sending`.
## Data clean out
table to store messages in database has an `ExpiresAt` field to mark the expiration time of the message. CAP will set `ExpiresAt` value as **1 hour** for `Successed` messages and **15days** for `Failed` messages.
To avoid performance slow down caused by a large amount of data,CAP will delete expired data every hour by default,the deletion rule is that `ExpiresAt` field's value isn't null and samller than current time.That is, `Failed` messages(it has been retried 50 times by default),if you do not deal with it manually,will also be deleted after 15 days as well,you have to pay attention to it.
For the processing of distributed transactions, this CAP library matches the "Asynchronous recovery events" scenario.
## Asynchronous recovery events
As known as the name "native message table", this is a classic solution, originally from EBay, and referenced links about it are at the end of this section. This is also one of the most popular solutions in the business development.
Compared to TCC or 2pc/3pc, this solution is the simplest one for distributed transactions, and is decentralized. In TCC or 2PC solutions, the common transaction handlers synchronize the state among different services with a transaction coordinator, but it's not much required in this CAP solution. In addition, the deeper references of other conditions these services have, the more management complexity and stability risk may be increased in 2PC/TCC. Imagine that if we have 9 services committed successfully of all 10 whitch relied heavily, though the last one execute fail, should we roll back transactions of those 9 service? In fact, the cost is still very high.
However, it's not mean that 2PC or TCC are at a disadvantage, each has its own suitability and matched scenarios, here won't introduce more.