diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..4e13de572 --- /dev/null +++ b/404.html @@ -0,0 +1,1904 @@ + + + +
+ + + + + + + + + + + + + + + + + +MIT License
+Copyright © 2016 - 2023 Savorboard
+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 NONINFRINGEMENT. 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.
+ + + + + + + + + + + + +Breaking Changes
+Removed DefaultAuthenticationScheme
, UseChallengeOnAuth
, DefaultChallengeScheme
and AuthorizationPolicy
options of DotNetCore.CAP.Dashboard.
+Now CAP dashboard auth/authz mechanism to leverage the "ASP.NET Core" way of doing it, see #1428.
Features:
+FallbackWindowLookbackSeconds
option to configure the retry processor to pick up the backtrack time window for Scheduled or Failed status messages. (#1455) Thanks @apatoziBug Fixed:
+Features:
+Bug Fixed:
+Features:
+EnableConsumerPrefetch
and UseDispatchingPerGroup
will work together without interference. (#1399)Bug Fixed:
+Breaking Changes
+ProducerThreadCount
configuration option. Now automatically send task managed by the .NET thread pool. (#1380)Features:
+Bug Fixed:
+Features:
+AutoDeleteOnIdle
option for Azure Service Bus. (#1350) Thanks @StevenDevooghtBug Fixed:
+FailedRetryInterval
. (#1359) Thanks @li-zheng-haoUseDispatchingPerGroup
option. (#1356) Thanks @sampsonye @li-zheng-haoFeatures:
+Bug Fixed:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Bug Fixed:
+Breaking Changes:
+SubscribeFilter
method to asynchronous.IConsumerClient
interface OnMessage
and OnLog
is from event to delegate.Features:
+Others:
+Bug Fixed:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Bug Fixed:
+Features:
+Features:
+Bug Fixed:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features: +* Support record the exception message in the headers. (#679) +* Support consul service check for https. (#722) +* Support custom producer threads count options for sending. (#731) +* Upgrade dependent nuget packages to latest.
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Bug Fixed:
+Bug Fixed:
+Bug Fixed:
+Bug Fixed:
+Breaking Changes:
+In this version, we have made major improvements to the code structure, which have introduced some destructive changes.
+Publisher and Consumer are not compatible with older versions +This version is not compatible with older versions of the message protocol because we have improved the format in which messages are published and stored.
+Interface changes +We have done a lot of refactoring of the code, and some of the interfaces may be incompatible with older versions
+Detach the dashboard project
+Features:
+ISerializer
to support serialization of message body sent to MQ.ICapPublisher
to publish message with headers.Bug Fixed:
+Features:
+ConsumerInvoker
implementation. Thanks @hetaoosBug Fixed:
+Features:
+Bugs Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Bug Fixed:
+Features:
+Breaking Changes:
+In order to support the "version isolation" feature, we introduced a new version field in version 2.4.0 to isolate different versions of the message, so this requires some adjustments to the database table structure. You can use the following SQL to add a version field to your database CAP related table.
+MySQL +
ALTER TABLE `cap.published` ADD Version VARCHAR(20) NULL;
+ALTER TABLE `cap.received` ADD Version VARCHAR(20) NULL;
+
SQL Server +
ALTER TABLE Cap.[Published] ADD Version VARCHAR(20) NULL;
+ALTER TABLE Cap.[Received] ADD Version VARCHAR(20) NULL;
+
PostgreSQL +
ALTER TABLE cap.published ADD "Version" VARCHAR(20) NULL;
+ALTER TABLE cap.received ADD "Version" VARCHAR(20) NULL;
+
MongoDb +
db.CapPublishedMessage.update({},{"$set" : {"Version" : "1"}});
+db.CapReceivedMessage.update({},{"$set" : {"Version" : "1"}});
+
Bug Fixed:
+Features:
+Bug Fixed:
+In this version, we made some breaking changes for the publisher API, you can see this blog to understand the story behind.
+If you have any migration question, please comment in issue (#190).
+Breaking Changes:
+Features:
+ +Bug Fixed:
+Features: +- Performance improvement
+Bug Fixed:
+Because version 2.2.3 was not released to nuget, so released 2.2.4.
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Features:
+Bug Fixed:
+Bug Fixed:
+Bug Fixed:
+Features:
+Bug Fixed:
+Bug Fixed:
+Bug Fixed:
+ +Features:
+ICapPublisher
api supported callback subsrciber.Bug Fixed:
+Features:
+ICapPublisher
.Bug Fixed:
+Features:
+CAP is a library based on .net standard, which is a solution to deal with distributed transactions, also has the function of EventBus, it is lightweight, easy to use, and efficient.
+In the process of building an SOA or MicroService system, we usually need to use the event to integrate each service. In the process, simple use of message queue does not guarantee reliability. CAP adopts local message table program integrated with the current database to solve exceptions that may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case.
+You can also use CAP as an EventBus. CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during subscription and sending process.
+CAP implements the Outbox Pattern described in the eShop ebook
+ +++Atomicity when publishing events to the event bus with a worker microservice
+
For detailed instructions see the Getting Started Guide.
+One of the easiest ways to contribute is to participate in discussions and discuss issues. You can also contribute by submitting pull requests with code changes.
+If you have any question or problems, please report them on the CAP repository:
+ +CAP is licensed under the MIT license.
+ + + + + + + + + + + + +CAP is a library based on .net standard, which is a solution to deal with distributed transactions, also has the function of EventBus, it is lightweight, easy to use, and efficient.
"},{"location":"#introduction","title":"Introduction","text":"In the process of building an SOA or MicroService system, we usually need to use the event to integrate each service. In the process, simple use of message queue does not guarantee reliability. CAP adopts local message table program integrated with the current database to solve exceptions that may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case.
You can also use CAP as an EventBus. CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during subscription and sending process.
CAP implements the Outbox Pattern described in the eShop ebook
Atomicity when publishing events to the event bus with a worker microservice
For detailed instructions see the Getting Started Guide.
"},{"location":"#contributing","title":"Contributing","text":"One of the easiest ways to contribute is to participate in discussions and discuss issues. You can also contribute by submitting pull requests with code changes.
If you have any question or problems, please report them on the CAP repository:
Report Issue Active Issues
"},{"location":"#license","title":"License","text":"CAP is licensed under the MIT license.
"},{"location":"about/contact-us/","title":"Contact Us","text":""},{"location":"about/contact-us/#authors","title":"Authors","text":"MIT License
Copyright \u00a9 2016 - 2023 Savorboard
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 NONINFRINGEMENT. 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.
"},{"location":"about/release-notes/","title":"Release Notes","text":""},{"location":"about/release-notes/#version-800-dec-14-2023","title":"Version 8.0.0 (Dec 14, 2023)","text":"Breaking Changes
Removed DefaultAuthenticationScheme
, UseChallengeOnAuth
, DefaultChallengeScheme
and AuthorizationPolicy
options of DotNetCore.CAP.Dashboard. Now CAP dashboard auth/authz mechanism to leverage the \"ASP.NET Core\" way of doing it, see #1428.
Features:
FallbackWindowLookbackSeconds
option to configure the retry processor to pick up the backtrack time window for Scheduled or Failed status messages. (#1455) Thanks @apatoziBug Fixed:
Features:
Bug Fixed:
Features:
EnableConsumerPrefetch
and UseDispatchingPerGroup
will work together without interference. (#1399)Bug Fixed:
Breaking Changes
ProducerThreadCount
configuration option. Now automatically send task managed by the .NET thread pool. (#1380)Features:
Bug Fixed:
Features:
AutoDeleteOnIdle
option for Azure Service Bus. (#1350) Thanks @StevenDevooghtBug Fixed:
FailedRetryInterval
. (#1359) Thanks @li-zheng-haoUseDispatchingPerGroup
option. (#1356) Thanks @sampsonye @li-zheng-haoFeatures:
Bug Fixed:
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Bug Fixed:
Breaking Changes:
SubscribeFilter
method to asynchronous.IConsumerClient
interface OnMessage
and OnLog
is from event to delegate.Features:
Others:
Bug Fixed:
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Bug Fixed:
Features:
Features:
Bug Fixed:
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Features: * Support record the exception message in the headers. (#679) * Support consul service check for https. (#722) * Support custom producer threads count options for sending. (#731) * Upgrade dependent nuget packages to latest.
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Bug Fixed:
Bug Fixed:
Bug Fixed:
Bug Fixed:
Breaking Changes:
In this version, we have made major improvements to the code structure, which have introduced some destructive changes.
Publisher and Consumer are not compatible with older versions This version is not compatible with older versions of the message protocol because we have improved the format in which messages are published and stored.
Interface changes We have done a lot of refactoring of the code, and some of the interfaces may be incompatible with older versions
Detach the dashboard project
Features:
ISerializer
to support serialization of message body sent to MQ.ICapPublisher
to publish message with headers.Bug Fixed:
Features:
ConsumerInvoker
implementation. Thanks @hetaoosBug Fixed:
Features:
Bugs Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Bug Fixed:
Features:
Breaking Changes:
In order to support the \"version isolation\" feature, we introduced a new version field in version 2.4.0 to isolate different versions of the message, so this requires some adjustments to the database table structure. You can use the following SQL to add a version field to your database CAP related table.
MySQL
ALTER TABLE `cap.published` ADD Version VARCHAR(20) NULL;\nALTER TABLE `cap.received` ADD Version VARCHAR(20) NULL;\n
SQL Server
ALTER TABLE Cap.[Published] ADD Version VARCHAR(20) NULL;\nALTER TABLE Cap.[Received] ADD Version VARCHAR(20) NULL;\n
PostgreSQL
ALTER TABLE cap.published ADD \"Version\" VARCHAR(20) NULL;\nALTER TABLE cap.received ADD \"Version\" VARCHAR(20) NULL;\n
MongoDb
db.CapPublishedMessage.update({},{\"$set\" : {\"Version\" : \"1\"}});\ndb.CapReceivedMessage.update({},{\"$set\" : {\"Version\" : \"1\"}});\n
Bug Fixed:
Features:
Bug Fixed:
In this version, we made some breaking changes for the publisher API, you can see this blog to understand the story behind.
If you have any migration question, please comment in issue (#190).
Breaking Changes:
Features:
Bug Fixed:
Features: - Performance improvement
Bug Fixed:
Because version 2.2.3 was not released to nuget, so released 2.2.4.
"},{"location":"about/release-notes/#version-223-2018-06-05","title":"Version 2.2.3 (2018-06-05)","text":"Features:
Bug Fixed:
Features:
Bug Fixed:
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Features:
Bug Fixed:
Bug Fixed:
Bug Fixed:
Features:
Bug Fixed:
Bug Fixed:
Bug Fixed:
Features:
ICapPublisher
api supported callback subsrciber.Bug Fixed:
Features:
ICapPublisher
.Bug Fixed:
Features:
By default, you can specify configuration when you register CAP services into the DI container for ASP.NET Core project.
services.AddCap(config=> {\n// config.XXX \n});\n
services
is IServiceCollection
interface, which can be found in the Microsoft.Extensions.DependencyInjection
package."},{"location":"user-guide/en/cap/configuration/#what-is-minimum-configuration-required-for-cap","title":"What is minimum configuration required for CAP","text":"you have to configure at least a transport and a storage. If you want to get started quickly you can use the following configuration:
services.AddCap(capOptions => {\ncapOptions.UseInMemoryQueue(); //Required Savorboard.CAP.InMemoryMessageQueue nuget package.\ncapOptions.UseInmemoryStorage();\n});\n
For specific transport and storage configuration, you can take a look at the configuration options provided by the specific components in the Transports section and the Persistent section.
"},{"location":"user-guide/en/cap/configuration/#custom-configuration","title":"Custom configuration","text":"The CapOptions
is used to store configuration information. By default they have default values, sometimes you may need to customize them.
Default: cap.queue.{assembly name}
The default consumer group name, corresponds to different names in different Transports, you can customize this value to customize the names in Transports for easy viewing.
Mapping
Map to Queue Names in RabbitMQ. Map to Consumer Group Id in Apache Kafka. Map to Subscription Name in Azure Service Bus. Map to Queue Group Name in NATS. Map to Consumer Group in Redis Streams.
"},{"location":"user-guide/en/cap/configuration/#groupnameprefix","title":"GroupNamePrefix","text":"Default: Null
Add unified prefixes for consumer group. https://github.com/dotnetcore/CAP/pull/780
"},{"location":"user-guide/en/cap/configuration/#topicnameprefix","title":"TopicNamePrefix","text":"Default: Null
Add unified prefixes for topic/queue name. https://github.com/dotnetcore/CAP/pull/780
"},{"location":"user-guide/en/cap/configuration/#versioning","title":"Versioning","text":"Default: v1
It is used to specify a version of a message to isolate messages of different versions of the service. It is often used in A/B testing or multi-service version scenarios. Following are application scenarios that needs versioning:
Business Iterative and compatible
Due to the rapid iteration of services, the data structure of the message is not fixed during each service integration process. Sometimes we add or modify certain data structures to accommodate the newly introduced requirements. If you have a brand new system, there's no problem, but if your system is already deployed to a production environment and serves customers, this will cause new features to be incompatible with the old data structure when they go online, and then these changes can cause serious problems. To work around this issue, you can only clean up message queues and persistent messages before starting the application, which is obviously not acceptable for production environments.
Multiple versions of the server
Sometimes, the server's server needs to provide multiple sets of interfaces to support different versions of the app. Data structures of the same interface and server interaction of these different versions of the app may be different, so usually server does not provide the same routing addresses to adapt to different versions of App calls.
Using the same persistent table/collection in different instance
If you want multiple different instance services to use the same database, in versions prior to 2.4, we could isolate database tables for different instances by specifying different table names. After version 2.4 this can be achived through CAP configuration, by configuring different table name prefixes.
Check out the blog to learn more about the Versioning feature: https://www.cnblogs.com/savorboard/p/cap-2-4.html
"},{"location":"user-guide/en/cap/configuration/#failedretryinterval","title":"FailedRetryInterval","text":"Default: 60 sec
During the message sending process if message transport fails, CAP will try to send the message again. This configuration option is used to configure the interval between each retry.
During the message sending process if consumption method fails, CAP will try to execute the method again. This configuration option is used to configure the interval between each retry.
Retry & Interval
By default if failure occurs on send or consume, retry will start after 4 minutes (FallbackWindowLookbackSeconds) in order to avoid possible problems caused by setting message state delays. Failures in the process of sending and consuming messages will be retried 3 times immediately, and will be retried polling after 3 times, at which point the FailedRetryInterval configuration will take effect.
Multi-instance concurrent retries
We introduced database-based distributed locks in version 7.1.0 to solve the problem of retrying concurrent fetches from the database under multiple instances, you need to explicitly configure UseStorageLock
to true.
Default: false
If set to true, we will use a database-based distributed lock to solve the problem of concurrent fetches data by retry processes with multiple instances. This will generate the cap.lock table in the database.
"},{"location":"user-guide/en/cap/configuration/#collectorcleaninginterval","title":"CollectorCleaningInterval","text":"Default: 300 sec
The interval of the collector processor deletes expired messages.
"},{"location":"user-guide/en/cap/configuration/#consumerthreadcount","title":"ConsumerThreadCount","text":"Default: 1
Number of consumer threads, when this value is greater than 1, the order of message execution cannot be guaranteed.
"},{"location":"user-guide/en/cap/configuration/#failedretrycount","title":"FailedRetryCount","text":"Default: 50
Maximum number of retries. When this value is reached, retry will stop and the maximum number of retries will be modified by setting this parameter.
"},{"location":"user-guide/en/cap/configuration/#fallbackwindowlookbackseconds","title":"FallbackWindowLookbackSeconds","text":"Default: 240 sec
Configure the retry processor to pick up the fallback window lookback time for Scheduled
or Failed
status messages.
Default: NULL
Type: Action<FailedInfo>
Failure threshold callback. This action is called when the retry reaches the value set by FailedRetryCount
, you can receive notification by specifying this parameter to make a manual intervention. For example, send an email or notification.
Default: 24*3600 sec (1 days)
The expiration time (in seconds) of the success message. When the message is sent or consumed successfully, it will be removed from database storage when the time reaches SucceedMessageExpiredAfter
seconds. You can set the expiration time by specifying this value.
Default: 15*24*3600 sec(15 days)
The expiration time (in seconds) of the failed message. When the message is sent or consumed failed, it will be removed from database storage when the time reaches FailedMessageExpiredAfter
seconds. You can set the expiration time by specifying this value.
Default: false
If true
then all consumers within the same group pushes received messages to own dispatching pipeline channel. Each channel has set thread count to ConsumerThreadCount
value.
Default: false\uff0c Before version 7.0 the default behavior is true
Renamed to EnableSubscriberParallelExecute
option, Please use the new option.
Default: false
If true
, CAP will prefetch some message from the broker as buffered, then execute the subscriber method. After the execution is done, it will fetch the next batch for execution.
Precautions
Setting it to true may cause some problems. When the subscription method executes too slowly and takes too long, it will cause the retry thread to pick up messages that have not yet been executed. The retry thread picks up messages from 4 minutes (FallbackWindowLookbackSeconds) ago by default , that is to say, if the message backlog of more than 4 minutes (FallbackWindowLookbackSeconds) on the consumer side will be picked up again and executed again
"},{"location":"user-guide/en/cap/configuration/#subscriberparallelexecutethreadcount","title":"SubscriberParallelExecuteThreadCount","text":"Default: Environment.ProcessorCount
With the EnableSubscriberParallelExecute
option enabled, specify the number of parallel task execution threads.
Default: 1
With the EnableSubscriberParallelExecute
option enabled, multiplier used to determine the buffered capacity size in subscriber parallel execution. The buffer capacity is computed by multiplying this factor with the value of SubscriberParallelExecuteThreadCount
, which represents the number of threads allocated for parallel processing.
Default: false\uff0c The (7.2 <= Version < 8.1) the default behavior is true
By default, messages sent are first placed into the Channel in memory and then processed linearly. If set to true, the task of sending messages will be processed in parallel by the .NET thread pool, which will greatly increase the speed of sending.
"},{"location":"user-guide/en/cap/filter/","title":"Filter","text":"Subscriber filters are similar to ASP.NET MVC filters and are mainly used to process additional work before and after the subscriber method is executed. Such as transaction management or logging, etc.
"},{"location":"user-guide/en/cap/filter/#create-subscribe-filter","title":"Create subscribe filter","text":""},{"location":"user-guide/en/cap/filter/#create-filter","title":"Create Filter","text":"Create a new filter class and inherit the SubscribeFilter
abstract class.
public class MyCapFilter: SubscribeFilter\n{\npublic override Task OnSubscribeExecutingAsync(ExecutingContext context)\n{\n// before subscribe method exectuing\n}\n\npublic override Task OnSubscribeExecutedAsync(ExecutedContext context)\n{\n// after subscribe method executed\n}\n\npublic override Task OnSubscribeExceptionAsync(ExceptionContext context)\n{\n// subscribe method execution exception\n}\n}\n
In some scenarios, if you want to terminate the subscriber method execution, you can throw an exception in OnSubscribeExecutingAsync
, and choose to ignore the exception in OnSubscribeExceptionAsync
.
To ignore exceptions, you can setting context.ExceptionHandled = true
in ExceptionContext
public override Task OnSubscribeExceptionAsync(ExceptionContext context)\n{\ncontext.ExceptionHandled = true;\n}\n
"},{"location":"user-guide/en/cap/filter/#configuration-filter","title":"Configuration Filter","text":"Use AddSubscribeFilter<>
to add a filter.
services.AddCap(opt =>\n{\n// ***\n}.AddSubscribeFilter<MyCapFilter>();\n
Currently, we do not support adding multiple filters.
"},{"location":"user-guide/en/cap/idempotence/","title":"Idempotence","text":"Imdempotence (which you may read a formal definition of on Wikipedia, when we are talking about messaging, is when a message redelivery can be handled without ending up in an unintended state.
"},{"location":"user-guide/en/cap/idempotence/#delivery-guarantees1","title":"Delivery guarantees1","text":"Before we talk about idempotency, let's talk about the delivery of messages on the consumer side.
Since CAP doesn't uses MS DTC or other type of 2PC distributed transaction mechanism, there is a problem that the message is strictly delivered at least once. Specifically, in a message-based system, there are three possibilities:
Exactly once has a (*) next to it, because in the general case, it is simply not possible.
"},{"location":"user-guide/en/cap/idempotence/#at-most-once","title":"At Most Once","text":"The At Most Once delivery guarantee covers the case when you are guaranteed to receive all messages either once, or maybe not at all.
This type of delivery guarantee can arise from your messaging system and your code performing its actions in the following order:
1. Remove message from queue\n2. Start work transaction\n3. Handle message (your code)\n4. Success?\n Yes:\n 1. Commit work transaction\n No: \n 1. Roll back work transaction\n 2. Put message back into the queue\n
In the best case scenario, this is all well and good \u2013 your messages will be received, and work transactions will be committed, and you will be happy.
However, the sun does not always shine, and stuff tends to fail \u2013 especially if you do alot of stuff. Consider e.g. what would happen if anything fails after having performed step (1), and then \u2013 when you try to execute step (4)/(2) (i.e. put the message back into the queue) \u2013 the network was temporarily unavailable, or the message broker restarted, or the host machine decided to reboot because it had installed an update.
This can be OK if it's what you want, but most things in CAP revolve around the concept of DURABLE messages, i.e. messages whose contents is just as important as the data in your database.
"},{"location":"user-guide/en/cap/idempotence/#at-least-once","title":"At Least Once","text":"This delivery guarantee covers the case when you are guaranteed to receive all messages either once, or maybe more times if something has failed.
It requires a slight change to the order we are executing our steps in, and it requires that the message queue system supports transactions, either in the form of the traditional begin-commit-rollback protocol (MSMQ does this), or in the form of a receive-ack-nack protocol (RabbitMQ, Azure Service Bus, etc. do this).
Check this out \u2013 if we do this:
1. Grab lease on message in queue\n2. Start work transaction\n3. Handle message (your code)\n4. Success?\n Yes: \n 1. Commit work transaction\n 2. Delete message from queue\n No: \n 1. Roll back work transaction\n 2. Release lease on message\n
and the \"lease\" we grabbed on the message in step (1) is associated with an appropriate timeout, then we are guaranteed that no matter how wrong things go, we will only actually remove the message from the queue (i.e. execute step (4)/(2)) if we have successfully committed our \"work transaction\".
"},{"location":"user-guide/en/cap/idempotence/#what-is-a-work-transaction","title":"What is a \"work transaction\"?","text":"It depends on what you're doing \ud83d\ude04 maybe it's a transaction in a relational database (which traditionally have pretty good support in this regard), maybe it's a transaction in a document database that happens to support transaction (like RavenDB or Postgres), or maybe it's a conceptual transaction in the form of whichever work you happen to carry out as a consequence of handling a message, e.g. update a bunch of documents in MongoDB, move some files around in the file system, or mutate some obscure in-mem data structure.
The fact that the \"work transaction\" is just a conceptual thing is what makes it impossible to support the aforementioned Exactly Once delivery guarantee \u2013 it's just not generally possible to commit or roll back a \"work transaction\" and a \"queue transaction\" (which is what we could call the protocol carried out with the message queue systems) atomically and consistently.
"},{"location":"user-guide/en/cap/idempotence/#idempotence-at-cap","title":"Idempotence at CAP","text":"In CAP, At Least Once delivery guarantee is used.
Since we have a temporary storage medium (database table), we may be able to do At Most Once, but in order to strictly guarantee that the message will not be lost, we do not provide related functions or configurations.
"},{"location":"user-guide/en/cap/idempotence/#why-are-we-not-providingachieving-idempotency","title":"Why are we not providing(achieving) idempotency ?","text":"The message was successfully written, but the execution of the Consumer method failed.
There are a lot of reasons why the Consumer method fails. I don't know if the specific scene is blindly retrying or not retrying is an incorrect choice. For example, if the consumer is debiting service, if the execution of the debit is successful, but fails to write the debit log, the CAP will judge that the consumer failed to execute and try again. If the client does not guarantee idempotency, the framework will retry it, which will inevitably lead to serious consequences for multiple debits.
The execution of the Consumer method succeeded, but received the same message.
This scenario is also possible. If the Consumer has been successfully executed at the beginning, but for some reason, such as the Broker recovery, same message has been received, CAP will consider this as a new message after receiving the Broker message. Message will be executed again by the Consumer. Because it is a new message, CAP cannot be idempotent at this time.
The current data storage mode can not be idempotent.
Since the table of the CAP message is deleted after 1 hour for the successfully consumed message, if the historical message cannot be idempotent. Historically, if the broker has maintained or manually processed some messages for some reason.
Industry practices.
Many event-driven frameworks require users to ensure idempotent operations, such as ENode, RocketMQ, etc...
From an implementation point of view, CAP can do some less stringent idempotence, but strict idempotent can not be guaranteed.
"},{"location":"user-guide/en/cap/idempotence/#naturally-idempotent-message-processing","title":"Naturally idempotent message processing","text":"Generally, the best way to deal with message redeliveries is to make the processing of each message naturally idempotent.
Natural idempotence arises when the processing of a message consists of calling an idempotent method on a domain object, like
obj.MarkAsDeleted();\n
or
obj.UpdatePeriod(message.NewPeriod);\n
You can use the INSERT ON DUPLICATE KEY UPDATE
provided by the database to easily done.
Another way of making message processing idempotent, is to simply track IDs of processed messages explicitly, and then make your code handle a redelivery.
Assuming that you are keeping track of message IDs by using an IMessageTracker
that uses the same transactional data store as the rest of your work, your code might look somewhat like this:
readonly IMessageTracker _messageTracker;\n\npublic SomeMessageHandler(IMessageTracker messageTracker)\n{\n_messageTracker = messageTracker;\n}\n\n[CapSubscribe]\npublic async Task Handle(SomeMessage message) {\nif (await _messageTracker.HasProcessed(message.Id))\n{\nreturn;\n}\n\n// do the work here\n// ...\n\n// remember that this message has been processed\nawait _messageTracker.MarkAsProcessed(messageId);\n}\n
As for the implementation of IMessageTracker
, you can use a storage message Id such as Redis or a database and the corresponding processing state.
The chapter refers to the Delivery guarantees of rebus, which I think is described very good.\u00a0\u21a9
The data sent by using the ICapPublisher
interface is called Message
.
TimeoutException thrown in consumer using HTTPClient
By default, if the consumer throws an OperationCanceledException
(including TaskCanceledException
), we consider this to be normal user behavior and ignore the exception. If you use HTTPClient in the consumer method and configure the request timeout, due to the design issue of HTTP Client, you may need to handle the exception separately and re-throw non OperationCanceledException
, refer to #1368.
Wiki : Compensating transaction
In some cases, consumers need to return the execution value to tell the publisher, so that the publisher can implement some compensation actions, usually we called message compensation.
Usually you can notify the upstream by republishing a new message in the consumer code. CAP provides a simple way to do this. You can specify callbackName
parameter when publishing message, usually this only applies to point-to-point consumption. The following is an example.
For example, in an e-commerce application, the initial status of the order is pending, and the status is marked as succeeded when the product quantity is successfully deducted, otherwise it is failed.
// ============= Publisher =================\n\n_capBus.Publish(\"place.order.qty.deducted\", contentObj: new { OrderId = 1234, ProductId = 23255, Qty = 1 }, callbackName: \"place.order.mark.status\"); // publisher using `callbackName` to subscribe consumer result\n\n[CapSubscribe(\"place.order.mark.status\")]\npublic void MarkOrderStatus(JsonElement param)\n{\nvar orderId = param.GetProperty(\"OrderId\").GetInt32();\nvar isSuccess = param.GetProperty(\"IsSuccess\").GetBoolean();\n\nif(isSuccess){\n// mark order status to succeeded\n}\nelse{\n// mark order status to failed\n}\n}\n\n// ============= Consumer ===================\n\n[CapSubscribe(\"place.order.qty.deducted\")]\npublic object DeductProductQty(JsonElement param)\n{\nvar orderId = param.GetProperty(\"OrderId\").GetInt32();\nvar productId = param.GetProperty(\"ProductId\").GetInt32();\nvar qty = param.GetProperty(\"Qty\").GetInt32();\n\n//business logic \n\nreturn new { OrderId = orderId, IsSuccess = true };\n}\n
"},{"location":"user-guide/en/cap/messaging/#heterogeneous-system-integration","title":"Heterogeneous system integration","text":"In version 3.0+, we reconstructed the message structure. We used the Header in the message protocol in the message queue to transmit some additional information, so that we can do it in the Body without modifying or packaging the user\u2019s original The message data format and content are sent.
This approach is reasonable. It helps to better integrate in heterogeneous systems. Compared with previous versions, users do not need to know the message structure used inside CAP to complete the integration work.
Now we divide the message into Header and Body for transmission.
The data in the body is the content of the original message sent by the user, that is, the content sent by calling the Publish method. We do not perform any packaging, but send it to the message queue after serialization.
In the Header, we need to pass some additional information so that the CAP can extract the key features for operation when the message is received.
The following is the content that needs to be written into the header of the message when sending a message in a heterogeneous system:
Key DataType Description cap-msg-id string Message Id, Generated by snowflake algorithm, can also be guid cap-msg-name string The name of the message cap-msg-type string The type of message,typeof(T).FullName
(not required) cap-senttime string sending time (not required) cap-kafka-key string Partitioning by Kafka Key"},{"location":"user-guide/en/cap/messaging/#custom-headers","title":"Custom headers","text":"To consume messages sent without CAP headers, both AzureServiceBus, Kafka and RabbitMQ consumers can inject a minimal set of headers using the CustomHeadersBuilder
property as shown below (RabbitMQ example):
container.AddCap(x =>\n{\nx.UseRabbitMQ(z =>\n{\nz.ExchangeName = \"TestExchange\";\nz.CustomHeadersBuilder = (msg, sp) =>\n[\n new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),\n new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)\n ];\n});\n});\n
After adding cap-msg-id
and cap-msg-name
, CAP consumers receive messages sent directly from any external system, like the RabbitMQ management tool when using RabbitMQ as a transport.
To publish messages with CAP headers
var headers = new Dictionary<string, string?>()\n{\n{\"cap-kafka-key\", request.OrderId }\n};\n_publisher.Publish<OrderRequest>(\"OrderRequest\", request,headers);\n
"},{"location":"user-guide/en/cap/messaging/#scheduling","title":"Scheduling","text":"After CAP receives a message, it sends the message to Transport(RabitMq, Kafka...), which is transported by transport.
When you send message using the ICapPublisher
interface, CAP will dispatch message to the corresponding Transport. Currently, bulk messaging is not supported.
For more information on transports, see Transports section.
"},{"location":"user-guide/en/cap/messaging/#storage","title":"Storage","text":"CAP will store the message after receiving it. For more information on storage, see the Storage section.
"},{"location":"user-guide/en/cap/messaging/#retry","title":"Retry","text":"Retrying plays an important role in the overall CAP architecture design, CAP retry messages that fail to send or fail to execute. There are several retry strategies used throughout the CAP design process.
"},{"location":"user-guide/en/cap/messaging/#send-retry","title":"Send retry","text":"During the message sending process, when the broker crashes or the connection fails or an abnormality occurs, CAP will retry the sending. Retry 3 times for the first time, retry every minute after 4 minutes (FallbackWindowLookbackSeconds), and +1 retry. When the total number of retries reaches 50, CAP will stop retrying.
You can adjust the total number of retries by setting FailedRetryCount in CapOptions Or use FailedThresholdCallback to receive notifications when the maximum retry count is reached.
It will stop when the maximum number of times is reached. You can see the reason for the failure in Dashboard and choose whether to manually retry.
"},{"location":"user-guide/en/cap/messaging/#consumption-retry","title":"Consumption retry","text":"The consumer method is executed when the Consumer receives the message and will retry when an exception occurs. This retry strategy is the same as the send retry.
We introduced database-based distributed locks in version 7.1.0 to deal with the problem of concurrent data acquisition of database retries under multiple instances, you need to explicitly configure UseStorageLock
option to true.
Whether sending fails or consumption fails, we will store the exception message in the cap-exception field within the message header. You can find it in the Content field's JSON in the database table.
"},{"location":"user-guide/en/cap/messaging/#data-cleanup","title":"Data Cleanup","text":"There is an ExpiresAt
field in the database message table indicating the expiration time of the message. When the message is sent successfully, status will be changed to Successed
, and ExpiresAt
will be set to 1 day later.
Consuming failure will change the message status to Failed
and ExpiresAt
will be set to 15 days later (You can use FailedMessageExpiredAfter configuration items to custom).
By default, the data of the message in the table is deleted every 5 minutes to avoid performance degradation caused by too much data. The cleanup strategy ExpiresAt
is performed when field is not empty and is less than the current time.
That is to say, the message with the status Failed (by default they have been retried 50 times), if you do not have manual intervention for 15 days, it will also be cleaned up.
You can use CollectorCleaningInterval configuration items to custom the interval time.
"},{"location":"user-guide/en/cap/serialization/","title":"Serialization","text":"We provide the ISerializer
interface to support serialization of messages. By default, json is used to serialize messages and store them in the database.
public class YourSerializer: ISerializer\n{\nTask<TransportMessage> SerializeAsync(Message message)\n{\n\n}\n\nTask<Message> DeserializeAsync(TransportMessage transportMessage, Type valueType)\n{\n\n}\n}\n
Then register your implemented serializer in the container:
services.AddSingleton<ISerializer, YourSerializer>();\n\n// ---\nservices.AddCap \n
"},{"location":"user-guide/en/cap/transactions/","title":"Transaction","text":""},{"location":"user-guide/en/cap/transactions/#distributed-transactions","title":"Distributed transactions?","text":"CAP does not directly provide out-of-the-box MS DTC or 2PC-based distributed transactions, instead we provide a solution that can be used to solve problems encountered in distributed transactions.
In a distributed environment, using 2PC or DTC-based distributed transactions can be very expensive due to the overhead involved in communication which affects performance. In addition, since distributed transactions based on 2PC or DTC are also subject to the CAP theorem, it will have to give up availability (A in CAP) when network partitioning occurs.
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.1
For the processing of distributed transactions, CAP uses the \"Eventual Consistency and Compensation\" scheme.
"},{"location":"user-guide/en/cap/transactions/#eventual-consistency-and-compensation-1","title":"Eventual Consistency and Compensation 1","text":"By far, one of the most feasible models of handling consistency across microservices is eventual consistency.
This model doesn\u2019t 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.
"},{"location":"user-guide/en/cap/transactions/#a-case-for-eventual-consistency","title":"A Case for Eventual Consistency","text":"For example, suppose we need to solve the following task:
Second task is to ensure, for example, that this user wasn\u2019t banned from our servers for some reason.
But it could take time, and we\u2019d like to extract it to a separate microservice. It wouldn\u2019t be reasonable to keep the user waiting for so long just to know that he was registered successfully.
One way to solve it would be with a message-driven approach including compensation. Let\u2019s consider the following architecture:
The messaging platform could ensure that the messages sent by the microservices are persisted. Then they would be delivered at a later time if the receiver wasn't currently available
"},{"location":"user-guide/en/cap/transactions/#best-case-scenario","title":"Best case scenario","text":"In this architecture, best case scenario would be:
After we\u2019ve gone through all these steps, the system should be in a consistent state. However, for some period of time, user entity appeared to be in an incomplete state.
The last step, when the user microservice removes the invalid account, is a compensation phase.
"},{"location":"user-guide/en/cap/transactions/#failure-scenarios","title":"Failure Scenarios","text":"Now let\u2019s consider some failure scenarios:
Even if some of the messages were issued multiple times, this wouldn\u2019t affect the consistency of the data in the microservices\u2019 databases.
By carefully considering all possible failure scenarios, we can ensure that our system would satisfy the conditions of eventual consistency. At the same time, we wouldn\u2019t need to deal with the costly distributed transactions.
But we have to be aware that ensuring eventual consistency is a complex task. It doesn\u2019t have a single solution for all cases.
This chapter is quoted from: https://www.baeldung.com/transactions-across-microservices \u21a9\u21a9
One of the easiest ways to contribute is to participate in discussions and discuss issues.
If you have any question or problems, please report them on the CAP repository:
Report Issue Active Issues
"},{"location":"user-guide/en/getting-started/contributing/#submitting-changes","title":"Submitting Changes","text":"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.
"},{"location":"user-guide/en/getting-started/contributing/#additional-resources","title":"Additional Resources","text":"Filtering issues and pull requests
Using search to filter issues and pull requests
CAP is an EventBus and a solution for solving distributed transaction problems in microservices or SOA systems. It helps create a microservices system that is scalable, reliable, and easy to change.
In Microsoft's eShop microservices sample project, it is recommended to use CAP as the EventBus in the production environment.
What is EventBus\uff1f
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 events that are being sent and received, other components will never know about the substitution.
Compared to other Services Bus or Event Bus, CAP has its own characteristics. It does not require users to implement or inherit any interface when sending messages or processing messages. It has very high flexibility. We have always believed that the appointment is greater than the configuration, so the CAP is very simple to use, very friendly to the novice, and lightweight.
CAP is modular in design and highly scalable. You have many options to choose from, including message queues, storage, serialization, etc. Many elements of the system can be replaced with custom implementations.
"},{"location":"user-guide/en/getting-started/introduction/#related-videos","title":"Related videos","text":"Video: bilibili Tutorial
Video: Youtube Tutorial
Video: Youtube Tutorial - @CodeOpinion
Video: Tencent Tutorial
"},{"location":"user-guide/en/getting-started/introduction/#related-articles","title":"Related articles","text":"Article: Introduction and how to use
Article: New features in version 7.0
Article: New features in version 6.0
Article: New features in version 5.0
Article: New features in version 3.0
Article: New features in version 2.6
Article: New features in version 2.5
Article: New features in version 2.4
Article: New features in version 2.3
Article: .NET Core Community The first thousand-star project was born: CAP
"},{"location":"user-guide/en/getting-started/quick-start/","title":"Quick Start","text":"Learn how to build a microservices event bus architecture using CAP, which offers advantages over direct integration of message queues, and what out-of-the-box features it provides.
"},{"location":"user-guide/en/getting-started/quick-start/#installation","title":"Installation","text":"PM> Install-Package DotNetCore.CAP\n
"},{"location":"user-guide/en/getting-started/quick-start/#integrated-in-aspnet-core","title":"Integrated in Asp.Net Core","text":"For quick start, we use memory-based event storage and message transport.
PM> Install-Package DotNetCore.CAP.InMemoryStorage\nPM> Install-Package Savorboard.CAP.InMemoryMessageQueue\n
In Startup.cs
\uff0cadd the following configuration:
public void ConfigureServices(IServiceCollection services)\n{\nservices.AddCap(x =>\n{\nx.UseInMemoryStorage();\nx.UseInMemoryMessageQueue();\n});\n}\n
"},{"location":"user-guide/en/getting-started/quick-start/#publish-message","title":"Publish Message","text":"public class PublishController : Controller\n{\n[Route(\"~/send\")]\npublic IActionResult SendMessage([FromServices]ICapPublisher capBus)\n{\ncapBus.Publish(\"test.show.time\", DateTime.Now);\n\nreturn Ok();\n}\n}\n
"},{"location":"user-guide/en/getting-started/quick-start/#publish-delay-message","title":"Publish delay message","text":"public class PublishController : Controller\n{\n[Route(\"~/send/delay\")]\npublic IActionResult SendDelayMessage([FromServices]ICapPublisher capBus)\n{\ncapBus.PublishDelay(TimeSpan.FromSeconds(100),\"test.show.time\", DateTime.Now);\n\nreturn Ok();\n}\n}\n
"},{"location":"user-guide/en/getting-started/quick-start/#publish-with-extra-header","title":"Publish with extra header","text":"var header = new Dictionary<string, string>()\n{\n[\"my.header.first\"] = \"first\",\n[\"my.header.second\"] = \"second\"\n};\n\ncapBus.Publish(\"test.show.time\", DateTime.Now, header);\n
"},{"location":"user-guide/en/getting-started/quick-start/#process-message","title":"Process Message","text":"public class ConsumerController : Controller\n{\n[NonAction]\n[CapSubscribe(\"test.show.time\")]\npublic void ReceiveMessage(DateTime time)\n{\nConsole.WriteLine(\"message time is:\" + time);\n}\n}\n
"},{"location":"user-guide/en/getting-started/quick-start/#process-with-extra-header","title":"Process with extra header","text":"[CapSubscribe(\"test.show.time\")]\npublic void ReceiveMessage(DateTime time, [FromCap]CapHeader header)\n{\nConsole.WriteLine(\"message time is:\" + time);\nConsole.WriteLine(\"message firset header :\" + header[\"my.header.first\"]);\nConsole.WriteLine(\"message second header :\" + header[\"my.header.second\"]);\n}\n
"},{"location":"user-guide/en/getting-started/quick-start/#summary","title":"Summary","text":"One of the most powerful advantages of asynchronous messaging over direct integrated message queues is reliability, where failures in one part of the system do not propagate or cause the entire system to crash. Messages are stored inside the CAP to ensure the reliability of the message, and strategies such as retry are used to achieve the final consistency of data between services.
"},{"location":"user-guide/en/monitoring/consul/","title":"Consul","text":"Consul is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud.
"},{"location":"user-guide/en/monitoring/consul/#consul-configuration-for-dashboard","title":"Consul Configuration for dashboard","text":"CAP's Dashboard uses Consul as a service discovery to get the data of other nodes, and you can switch to the Servers page to see other nodes.
Click the Switch
button to switch to the target node, CAP will use a proxy to get the data of the node you switched to.
The following is a configuration example, you need to configure them on each node.
services.AddCap(x =>\n{\nx.UseMySql(Configuration.GetValue<string>(\"ConnectionString\"));\nx.UseRabbitMQ(\"localhost\");\nx.UseDashboard();\nx.UseConsulDiscovery(_ =>\n{\n_.DiscoveryServerHostName = \"localhost\";\n_.DiscoveryServerPort = 8500;\n_.CurrentNodeHostName = Configuration.GetValue<string>(\"ASPNETCORE_HOSTNAME\");\n_.CurrentNodePort = Configuration.GetValue<int>(\"ASPNETCORE_PORT\");\n_.NodeId = Configuration.GetValue<string>(\"NodeId\");\n_.NodeName = Configuration.GetValue<string>(\"NodeName\");\n});\n});\n
Consul 1.6.2:
consul agent -dev\n
Windows 10, ASP.NET Core 3.1:
set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5001&& dotnet run --urls=http://localhost:5001 NodeId=1 NodeName=CAP-1 ConnectionString=\"Server=localhost;Database=aaa;UserId=xxx;Password=xxx;\"\nset ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5002&& dotnet run --urls=http://localhost:5002 NodeId=2 NodeName=CAP-2 ConnectionString=\"Server=localhost;Database=bbb;UserId=xxx;Password=xxx;\"\n
"},{"location":"user-guide/en/monitoring/dashboard/","title":"Dashboard","text":"CAP provides a Dashboard for viewing messages, and features provided by Dashboard make it easy to view and manage messages.
Usage Limit
The Dashboard is only supported for use in ASP.NET Core, Not supported for console application
"},{"location":"user-guide/en/monitoring/dashboard/#enable-dashboard","title":"Enable Dashboard","text":"By default, Dashboard middleware will not be launched. To enable Dashboard functionality you need to add the following code to your configuration:
services.AddCap(x =>\n{\n//...\n\n// Register Dashboard\nx.UseDashboard();\n});\n
By default, you can open the Dashboard by visiting the url http://localhost:xxx/cap
.
Default \uff1a'/cap'
You can change the path of the Dashboard by modifying this configuration option.
Default: 2000ms
This configuration option is used to configure the Dashboard front end to get the polling time of the status interface (/stats).
Default: true
Explicitly allows anonymous access for the CAP dashboard API, passing AllowAnonymous to the ASP.NET Core global authorization filter.
Default: null.
Authorization policy for the Dashboard. Required if AllowAnonymousExplicit
is false.
From version 8.0.0, the CAP Dashboard leverages ASP.NET Core authentication mechanisms allowing extensibility through custom authorization policies and ASP.NET Core authentication and authorization middlewares to authorize Dashboard access. For more details of ASP.NET Core authentication internals, check the official docs.
You can view the examples below in the sample project Sample.Dashboard.Auth
.
services.AddCap(cap =>\n{\ncap.UseDashboard(d =>\n{\nd.AllowAnonymousExplicit = true;\n});\ncap.UseInMemoryStorage();\ncap.UseInMemoryMessageQueue();\n});\n
"},{"location":"user-guide/en/monitoring/dashboard/#example-open-id","title":"Example: Open Id","text":"services\n.AddAuthorization(options =>\n{ options.AddPolicy(DashboardAuthorizationPolicy, policy => policy\n.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)\n.RequireAuthenticatedUser());\n})\n.AddAuthentication(opt => opt.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)\n.AddCookie()\n.AddOpenIdConnect(options =>\n{\n...\n});\n\nservices.AddCap(cap =>\n{\ncap.UseDashboard(d =>\n{\nd.AuthorizationPolicy = DashboardAuthorizationPolicy;\n});\ncap.UseInMemoryStorage();\ncap.UseInMemoryMessageQueue();\n});\n
"},{"location":"user-guide/en/monitoring/dashboard/#example-custom-authentication-scheme","title":"Example: Custom Authentication Scheme","text":"const string MyDashboardAuthenticationPolicy = \"MyDashboardAuthenticationPolicy\";\n\nservices.AddAuthorization(options =>\n{ options.AddPolicy(MyDashboardAuthenticationPolicy, policy => policy\n.AddAuthenticationSchemes(MyDashboardAuthenticationSchemeDefaults.Scheme)\n.RequireAuthenticatedUser());\n})\n.AddAuthentication()\n.AddScheme<MyDashboardAuthenticationSchemeOptions, MyDashboardAuthenticationHandler>(MyDashboardAuthenticationSchemeDefaults.Scheme,null);\n\nservices.AddCap(cap =>\n{\ncap.UseDashboard(d =>\n{\nd.AuthorizationPolicy = MyDashboardAuthenticationPolicy;\n});\ncap.UseInMemoryStorage();\ncap.UseInMemoryMessageQueue();\n});\n
"},{"location":"user-guide/en/monitoring/diagnostics/","title":"Diagnostics","text":"Diagnostics provides a set of features that make it easy for us to document critical operations that occurs during the application's operation, their execution time, etc., allowing administrators to find the root cause of problems, especially in production environments.
"},{"location":"user-guide/en/monitoring/diagnostics/#tracing","title":"Tracing","text":"The CAP provides support for DiagnosticSource
with a listener name of CapDiagnosticListener
.
Diagnostics provides tracing event information as follows:
Related objects, you can find at the DotNetCore.CAP.Diagnostics
namespace.
Skywalking's C# client provides support for CAP Diagnostics. You can use SkyAPM-dotnet to tracking.
Try to read the README to integrate it in your project.
Example tracking image :
"},{"location":"user-guide/en/monitoring/diagnostics/#others-apm-support","title":"Others APM support","text":"There is currently no support for APMs other than Skywalking, and if you would like to support CAP diagnostic events in other APM, you can refer to the code here to implement it:
At present, apart from Skywalking, we have not provided support for other APMs. If you need it, you can refer the code here to implementation, and we also welcome the Pull Request.
https://github.com/SkyAPM/SkyAPM-dotnet/tree/master/src/SkyApm.Diagnostics.CAP
"},{"location":"user-guide/en/monitoring/diagnostics/#metrics","title":"Metrics","text":"Metrics are numerical measurements reported over time, most often used to monitor the health of an application and generate alerts. For example, a web service might track how many requests it receives each second, how many milliseconds it took to respond, and how many of the responses sent an error back to the user.
CAP 7.0 is support for EventSource
, and the counters name is DotNetCore.CAP.EventCounter
.
CAP provides the following metrics:
dotnet-counters is a performance monitoring tool for ad-hoc health monitoring and first-level performance investigation. It can observe performance counter values that are published via the EventCounter API or the Meter API.
Use the following commands to monitor metrics in CAP:
dotnet-counters ps\ndotnet-counters monitor --process-id=25496 --counters=DotNetCore.CAP.EventCounter\n
process-id\uff1a The ID of the CAP process to collect counter data from.
"},{"location":"user-guide/en/monitoring/diagnostics/#monitor-with-dashboard","title":"Monitor with dashboard","text":"You can configure x.UseDashboard()
to open the dashboard to view Metrics graph charts.
In the Realtime Metric Graph, the time axis will scroll in real time over time so that you can see the rate of publishing and consuming messages per second, And the consumer execution time is \"dotted\" on the Y1 axis (Y0 axis is the rates, and the Y1 axis is the execution elpsed time).
"},{"location":"user-guide/en/monitoring/kubernetes/","title":"Kubernetes","text":"Kubernetes, also known as K8s, is an open-source system for automating deployment, scaling, and management of containerized applications.
"},{"location":"user-guide/en/monitoring/kubernetes/#kubernetes-in-the-dashboard","title":"Kubernetes in the Dashboard","text":"Our Dashboard has supported Kubernetes as a service discovery from version 7.2.0 onwards. You can switch to the Node page, then select a k8s namespace, and CAP will list all Services under that namespace. After clicking the Switch button, the Dashboard will detect whether the CAP service of that node is available. If it is available, it will proxy to the switched node for data viewing.
Here is a configuration example:
services.AddCap(x =>\n{\n// ...\nx.UseDashboard();\nx.UseK8sDiscovery();\n});\n
The component will automatically detect whether it is inside the cluster. If it is inside the cluster, the Pod must be granted Kubernetes Api permissions. Refer to the next section.
"},{"location":"user-guide/en/monitoring/kubernetes/#assign-pod-access-to-kubernetes-api","title":"Assign Pod Access to Kubernetes Api","text":"If the ServiceAccount associated with your Deployment does not have access to the K8s Api, you need to grant the namespaces
, services
resources the get
, list
permissions.
Here is an example yaml. First create a ServiceAccount and ClusterRole and set the related permissions, then bind them using ClusterRoleBinding. Finally, use serviceAccountName: api-access
to specify in Deployment.
apiVersion: v1\nkind: ServiceAccount\nmetadata:\n name: api-access\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n name: ns-svc-reader\nrules:\n- apiGroups: [\"\"]\n resources: [\"namespaces\", \"services\"]\n verbs: [\"get\", \"watch\", \"list\"]\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n name: read-pods\nsubjects:\n- kind: ServiceAccount\n name: api-access\n namespace: default\nroleRef:\n kind: ClusterRole\n name: ns-svc-reader\n apiGroup: rbac.authorization.k8s.io\n\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: api-access-deployment\nspec:\n replicas: 1\n selector:\n matchLabels:\n app: api-access-app\n template:\n metadata:\n labels:\n app: api-access-app\n spec:\n serviceAccountName: api-access\n containers:\n - name: api-access-container\n image: your_image\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n name: api-access-service\nspec:\n selector:\n app: api-access-app\n ports:\n - protocol: TCP\n port: 80\n targetPort: 80\n
"},{"location":"user-guide/en/monitoring/kubernetes/#using-dashboard-standalone","title":"Using Dashboard Standalone","text":"You can use the Dashboard standalone without configuring CAP, in this case, the Dashboard can be deployed as a separate Pod in the Kubernetes cluster just for data viewing. The service to be viewed no longer needs to configure the cap.UseK8sDiscovery()
option.
services.AddCapDashboardStandalone();\n
Similarly, you need to configure the access for the ServiceAccount for this Pod.
"},{"location":"user-guide/en/monitoring/opentelemetry/","title":"OpenTelemetry","text":"https://opentelemetry.io/
OpenTelemetry is a collection of tools, APIs, and SDKs. Use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help you analyze your software\u2019s performance and behavior.
"},{"location":"user-guide/en/monitoring/opentelemetry/#integration","title":"Integration","text":"You can find it here about how to use OpenTelemetry in console applications or ASP.NET Core, at here we mainly describe how to tracing CAP data to OpenTelemetry.
"},{"location":"user-guide/en/monitoring/opentelemetry/#configuration","title":"Configuration","text":"Install the CAP OpenTelemetry package into the project.
dotnet add package DotNetCore.Cap.OpenTelemetry\n
The OpenTelemetry data comes from diagnostics, add the instrumentation of CAP to the configuration of OpenTelemetry.
services.AddOpenTelemetryTracing((builder) => builder\n.AddAspNetCoreInstrumentation()\n.AddCapInstrumentation() // <-- Add this line\n.AddZipkinExporter()\n);\n
If you don't use a framework that does this automatically for you (like aspnetcore), make sure you enable a listener, for example:
ActivitySource.AddActivityListener(new ActivityListener()\n{\nShouldListenTo = _ => true,\nSample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,\nActivityStarted = activity => Console.WriteLine($\"{activity.ParentId}:{activity.Id} - Start\"),\nActivityStopped = activity => Console.WriteLine($\"{activity.ParentId}:{activity.Id} - Stop\")\n});\n
Here is a diagram of CAP's tracking data in Zipkin: "},{"location":"user-guide/en/monitoring/opentelemetry/#context-propagation","title":"Context Propagation","text":"CAP supports Context Propagation by injecting traceparent
and baggage
headers when sending messages and restoring the context from those headers when receiving messages.
CAP uses the configured Propagators.DefaultTextMapPropagator propagator, which is usually set to both TraceContextPropagator and BaggagePropagator by the dotnet OpenTelemetry SDK but can be set in your your client program. For example, to opt out of the Baggage propagation, you can call:
OpenTelemetry.Sdk.SetDefaultTextMapPropagator(\nnew TraceContextPropagator());\n
See the dotnet OpenTelemetry.Api readme for more details.
"},{"location":"user-guide/en/samples/eshoponcontainers/","title":"eShopOnContainers","text":"eShopOnContainers is a sample application written in C# running on .NET Core using a microservice architecture, Domain Driven Design.
.NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers.
This reference application is cross-platform at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
The architecture proposes a microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the communication protocol between the client apps and the microservices and supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus (a light message broker, to choose between RabbitMQ or Azure Service Bus, underneath) plus other features defined at the roadmap.
"},{"location":"user-guide/en/samples/eshoponcontainers/#eshoponcontainers-with-cap","title":"eShopOnContainers with CAP","text":"You can see how to use caps in eShopOnContainers at the Github repository.
https://github.com/yang-xiaodong/eShopOnContainers
"},{"location":"user-guide/en/samples/faq/","title":"FAQ","text":"Any IM group(e.g Tencent QQ group) to learn and chat about CAP?
None of 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.
Does it require different databases, one each for producer and consumer in CAP?
No difference necessary, a recommendation is to use a dedicated database for each program.
Otherwise, look at Q&A below.
How to use the same database for different applications?
Define a table prefix name in ConfigureServices
method.
Code example\uff1a
public void ConfigureServices(IServiceCollection services)\n{\nservices.AddCap(x =>\n{\nx.UseKafka(\"\");\nx.UseMySql(opt =>\n{\nopt.ConnectionString = \"connection string\";\nopt.TableNamePrefix = \"appone\"; // different table name prefix here\n});\n});\n}\n
Can CAP not use the database as event storage? I just want to send the message
Not yet.
The purpose of CAP is that ensure consistency principle right in microservice or SOA architectures. The solution is based on ACID features of database, there is no sense about a single client wapper of message queue without database.
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.
"},{"location":"user-guide/en/samples/github/","title":"Github Samples","text":"You can find the sample code at the Github repository:
https://github.com/dotnetcore/CAP/tree/master/samples
CAP + Aspire + Azure Service Bus + Azure SQL
https://github.com/NikiforovAll/cap-aspire
"},{"location":"user-guide/en/storage/general/","title":"General","text":"CAP needs to use storage media with persistence capabilities to store event messages in databases or other NoSql facilities. CAP uses this approach to deal with loss of messages in all environments or network anomalies. Reliability of messages is the cornerstone of distributed transactions, so messages cannot be lost under any circumstances.
"},{"location":"user-guide/en/storage/general/#persistence","title":"Persistence","text":""},{"location":"user-guide/en/storage/general/#before-sent","title":"Before sent","text":"Before message enters the message queue, CAP uses the local database table to persist the message, which ensures that the message is not lost when the message queue is abnormal or a network error occurs.
To ensure the reliability of this mechanism, CAP uses the same database transactions as the business code to ensure that business operations and CAP messages are consistent in the persistence process. That is to say, in the process of message persistence, the database will be rolled back when any one of the exceptions occurs.
"},{"location":"user-guide/en/storage/general/#after-sent","title":"After sent","text":"After the message enters the message queue, CAP will start the persistence function of the message queue. We need to explain how CAP message is persisted in RabbitMQ and Kafka.
For message persistence in RabbitMQ, CAP uses a consumer queue with message persistence, but there may be exceptions here.
Ready for production?
By default, queues registered by CAP in RabbitMQ are persistent. When used in a production environment, we recommend that you start all consumers once to create the queues with persistence, which ensures that all queues are created before the message is sent.
Since Kafka is born with message persistence using files, Kafka will ensure that messages are properly persisted without loss after the message enters Kafka.
"},{"location":"user-guide/en/storage/general/#storage","title":"Storage","text":""},{"location":"user-guide/en/storage/general/#supported-storages","title":"Supported storages","text":"CAP supports the following types of transaction-enabled databases for storage:
After CAP is started, two tables are generated in used storage, by default the name is Cap.Published
and Cap.Received
.
Table structure of Published :
NAME DESCRIPTION TYPE Id Message Id int Version Message Version string Name Topic Name string Content Json Content string Added Added Time DateTime ExpiresAt Expire time DateTime Retries Retry times int StatusName Status Name stringTable structure of Received :
NAME DESCRIPTION TYPE Id Message Id int Version Message Version string Name Topic Name string Group Group Name string Content Json Content string Added Added Time DateTime ExpiresAt Expire time DateTime Retries Retry times int StatusName Status Name stringTable structure of Lock (Optional):
NAME DESCRIPTION TYPE Key Lock Id string Instance Acquired instance of lock string LastLockTime Last acquired lock time DateTime"},{"location":"user-guide/en/storage/general/#wapper-object","title":"Wapper Object","text":"When CAP sends a message, it will store original message object in a second package in the Content
field.
The following is the Wapper Object data structure of Content field.
NAME DESCRIPTION TYPE Id Message Id string Timestamp Message created time string Content Message content string CallbackName Consumer callback topic name stringThe Id
field is generate using the mongo objectid algorithm.
Thanks to the community for supporting CAP, the following is the implementation of community-supported storage
SQLite (@colinin) \uff1ahttps://github.com/colinin/DotNetCore.CAP.Sqlite
LiteDB (@maikebing) \uff1ahttps://github.com/maikebing/CAP.Extensions
SQLite & Oracle (@cocosip) \uff1ahttps://github.com/cocosip/CAP-Extensions
In-memory storage is often used in development and test environments, and if you use memory-based storage you lose the reliability of local transaction messages.
"},{"location":"user-guide/en/storage/in-memory-storage/#configuration","title":"Configuration","text":"To use in-memory storage, you need to install following package from NuGet:
PM> Install-Package DotNetCore.CAP.InMemoryStorage\n
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseInMemoryStorage();\n// x.UseXXX ...\n});\n}\n
CAP will clean every 5 minutes Successful messages in memory.
"},{"location":"user-guide/en/storage/in-memory-storage/#publish-with-transaction","title":"Publish with transaction","text":"In-Memory Storage does not support Transaction mode to send messages.
"},{"location":"user-guide/en/storage/mongodb/","title":"MongoDB","text":"MongoDB is a cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with schema.
CAP supports MongoDB since version 2.3 .
MongoDB supports ACID transactions since version 4.0, so CAP only supports MongoDB above 4.0, and MongoDB needs to be deployed as a cluster, because MongoDB's ACID transaction requires a cluster to be used.
For a quick development of the MongoDB 4.0+ cluster for the development environment, you can refer to this article.
"},{"location":"user-guide/en/storage/mongodb/#configuration","title":"Configuration","text":"To use MongoDB storage, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.MongoDB\n
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseMongoDB(opt=>{\n//MongoDBOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/storage/mongodb/#mongodb-options","title":"MongoDB Options","text":"NAME DESCRIPTION TYPE DEFAULT DatabaseName Database name string cap DatabaseConnection Database connection string string mongodb://localhost:27017 ReceivedCollection Database received message collection name string cap.received PublishedCollection Database published message collection name string cap.published"},{"location":"user-guide/en/storage/mongodb/#publish-with-transaction","title":"Publish with transaction","text":"The following example shows how to leverage CAP and MongoDB for local transaction integration.
//NOTE: Before your test, your need to create database and collection at first.\n// Mongo can't create databases and collections in transactions automatic, \n// so you need to create them separately, simulating a record insert \n// will automatically create.\n\n// var mycollection = _client.GetDatabase(\"test\")\n// .GetCollection<BsonDocument>(\"test.collection\");\n// mycollection.InsertOne(new BsonDocument { { \"test\", \"test\" } });\n\nusing (var session = _client.StartTransaction(_capBus, autoCommit: false))\n{\nvar collection = _client.GetDatabase(\"test\")\n.GetCollection<BsonDocument>(\"test.collection\");\n\ncollection.InsertOne(session, new BsonDocument { { \"hello\", \"world\" } });\n\n_capBus.Publish(\"sample.rabbitmq.mongodb\", DateTime.Now);\n\nsession.CommitTransaction();\n}\n
"},{"location":"user-guide/en/storage/mysql/","title":"MySQL","text":"MySQL is an open-source relational database management system. CAP supports MySQL database.
"},{"location":"user-guide/en/storage/mysql/#configuration","title":"Configuration","text":"To use MySQL storage, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.MySql\n
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseMySql(opt=>{\n//MySqlOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/storage/mysql/#mysqloptions","title":"MySqlOptions","text":"NAME DESCRIPTION TYPE DEFAULT TableNamePrefix CAP table name prefix string cap ConnectionString Database connection string string null"},{"location":"user-guide/en/storage/mysql/#publish-with-transaction","title":"Publish with transaction","text":""},{"location":"user-guide/en/storage/mysql/#adonet-with-transaction","title":"ADO.NET with transaction","text":"private readonly ICapPublisher _capBus;\n\nusing (var connection = new MySqlConnection(AppDbContext.ConnectionString))\n{\nusing (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))\n{\n//your business code\nconnection.Execute(\"insert into test(name) values('test')\", transaction: (IDbTransaction)transaction.DbTransaction);\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ntransaction.Commit();\n}\n}\n
"},{"location":"user-guide/en/storage/mysql/#entityframework-with-transaction","title":"EntityFramework with transaction","text":"private readonly ICapPublisher _capBus;\n\nusing (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))\n{\ndbContext.Persons.Add(new Person() { Name = \"ef.transaction\" });\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ndbContext.SaveChanges();\ntrans.Commit();\n}\n
"},{"location":"user-guide/en/storage/postgresql/","title":"Postgre SQL","text":"PostgreSQL is an open-source relational database management system. CAP supports PostgreSQL database.
"},{"location":"user-guide/en/storage/postgresql/#configuration","title":"Configuration","text":"To use PostgreSQL storage, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.PostgreSql\n
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UsePostgreSql(opt=>{\n//PostgreSqlOptions\n}); // x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/storage/postgresql/#postgresqloptions","title":"PostgreSqlOptions","text":"NAME DESCRIPTION TYPE DEFAULT Schema Database schema string cap ConnectionString Database connection string string DataSource Data source NpgsqlDataSource"},{"location":"user-guide/en/storage/postgresql/#publish-with-transaction","title":"Publish with transaction","text":""},{"location":"user-guide/en/storage/postgresql/#adonet-with-transaction","title":"ADO.NET with transaction","text":"private readonly ICapPublisher _capBus;\n\nusing (var connection = new NpgsqlConnection(\"ConnectionString\"))\n{\nusing (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))\n{\n//your business code\nconnection.Execute(\"insert into test(name) values('test')\", transaction: (IDbTransaction)transaction.DbTransaction);\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ntransaction.Commit();\n}\n}\n
"},{"location":"user-guide/en/storage/postgresql/#entityframework-with-transaction","title":"EntityFramework with transaction","text":"private readonly ICapPublisher _capBus;\n\nusing (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))\n{\ndbContext.Persons.Add(new Person() { Name = \"ef.transaction\" });\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ndbContext.SaveChanges();\ntrans.Commit();\n}\n
"},{"location":"user-guide/en/storage/sqlserver/","title":"SQL Server","text":"SQL Server is a relational database management system developed by Microsoft. CAP supports SQL Server database.
Warning
We currently use Microsoft.Data.SqlClient
as the database driver, which is the future of SQL Server drivers, and we have abandoned System.Data.SqlClient
, we suggest that you switch to.
To use SQL Server storage, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.SqlServer\n
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseSqlServer(opt=>{\n//SqlServerOptions\n}); // x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/storage/sqlserver/#sqlserveroptions","title":"SqlServerOptions","text":"NAME DESCRIPTION TYPE DEFAULT Schema Database schema string Cap ConnectionString Database connection string string"},{"location":"user-guide/en/storage/sqlserver/#publish-with-transaction","title":"Publish with transaction","text":""},{"location":"user-guide/en/storage/sqlserver/#adonet-with-transaction","title":"ADO.NET with transaction","text":"private readonly ICapPublisher _capBus;\n\nusing (var connection = new SqlConnection(\"ConnectionString\"))\n{\nusing (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))\n{\n//your business code\nconnection.Execute(\"insert into test(name) values('test')\", transaction: (IDbTransaction)transaction.DbTransaction);\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ntransaction.Commit();\n}\n}\n
"},{"location":"user-guide/en/storage/sqlserver/#entityframework-with-transaction","title":"EntityFramework with transaction","text":"private readonly ICapPublisher _capBus;\n\nusing (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))\n{\ndbContext.Persons.Add(new Person() { Name = \"ef.transaction\" });\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ndbContext.SaveChanges();\ntrans.Commit();\n}\n
"},{"location":"user-guide/en/transport/aws-sqs/","title":"Amazon SQS","text":"AWS SQS is a fully managed message queuing service that enables you to decouple and scale microservices, distributed systems, and serverless applications.
AWS SNS is a highly available, durable, secure, fully managed pub/sub messaging service that enables you to decouple microservices, distributed systems, and serverless applications.
"},{"location":"user-guide/en/transport/aws-sqs/#how-cap-uses-aws-sns-and-sqs","title":"How CAP uses AWS SNS and SQS","text":""},{"location":"user-guide/en/transport/aws-sqs/#sns","title":"SNS","text":"Because CAP works based on the topic pattern, it needs to use AWS SNS, which simplifies the publish and subscribe architecture of messages.
When CAP startups, all subscription names will be registered as SNS topics, and you will see a list of all registered topics in the management console.
SNS does not support use of symbols such as .
:
as the name of the topic, so we replaced it. We replaced .
with -
and :
with _
Precautions
Amazon SNS currently allows maximum size of published messages to be 256KB
For example, you have the following two subscriber methods in your current project
[CapSubscribe(\"sample.sns.foo\")]\npublic void TestFoo(DateTime value)\n{\n}\n\n[CapSubscribe(\"sample.sns.bar\")]\npublic void TestBar(DateTime value)\n{\n}\n
After CAP startups, you will see in SNS management console: "},{"location":"user-guide/en/transport/aws-sqs/#sqs","title":"SQS","text":"For each consumer group, CAP will create a corresponding SQS queue, the name of the queue is the name of the DefaultGroup
in the configuration options, and the queue type is Standard.
The SQS queue will subscribe to Topic in SNS, as shown below:
Precautions
Due to the limitation of AWS SNS, when you remove the subscription method, CAP will not delete topics or queues on AWS SNS or SQS, you need to delete them manually.
"},{"location":"user-guide/en/transport/aws-sqs/#configuration","title":"Configuration","text":"To use AWS SQS as the transport, you need to install the packages from NuGet:
Install-Package DotNetCore.CAP.AmazonSQS\n
Next, add configuration items to the ConfigureServices
method of Startup.cs
:
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseAmazonSQS(opt=>\n{\n//AmazonSQSOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/transport/aws-sqs/#amazonsqs-options","title":"AmazonSQS Options","text":"The SQS configuration parameters provided directly by the CAP:
NAME DESCRIPTION TYPE DEFAULT Region AWS Region Amazon.RegionEndpoint Credentials AWS AK SK Information Amazon.Runtime.AWSCredentialsIf your project runs in AWS EC2, you don't need to set Credentials, you can directly apply IAM policy for EC2.
Credentials requires the SNS,SQS IAM policy.
"},{"location":"user-guide/en/transport/azure-service-bus/","title":"Azure Service Bus","text":"Microsoft Azure Service Bus is a fully managed enterprise integration message broker. Service Bus is most commonly used to decouple applications and services from each other, and is a reliable and secure platform for asynchronous data and state transfer.
Azure services can be used in CAP as a message transporter.
"},{"location":"user-guide/en/transport/azure-service-bus/#configuration","title":"Configuration","text":"Requirement
For the Service Bus pricing layer, CAP requires \"standard\" or \"advanced\" to support Topic functionality.
To use Azure Service Bus as a message transport, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.AzureServiceBus\n
Next, add configuration items to the ConfigureServices
method of Startup.cs
:
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseAzureServiceBus(opt=>\n{\n//AzureServiceBusOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/transport/azure-service-bus/#azureservicebus-options","title":"AzureServiceBus Options","text":"The AzureServiceBus configuration options provided directly by the CAP:
NAME DESCRIPTION TYPE DEFAULT ConnectionString Endpoint address string TopicPath Topic entity path string cap EnableSessions Enable Service bus sessions bool false MaxConcurrentSessions The maximum number of concurrent sessions that the processor can handle. Not applicable when EnableSessions is false. int 8 SessionIdleTimeout The maximum time to wait for a new message before the session is closed. If not specified, 60 seconds will be used by Azure Service Bus. TimeSpan null SubscriptionAutoDeleteOnIdle Automatically delete subscription after a certain idle interval. TimeSpan TimeSpan.MaxValue SubscriptionMessageLockDuration The amount of time the message is locked by a given receiver so that no other receiver receives the same message. TimeSpan 60 seconds SubscriptionDefaultMessageTimeToLive The default message time to live value for a subscription. This is the duration after which the message expires. TimeSpan TimeSpan.MaxValue SubscriptionMaxDeliveryCount The maximum number of times a message is delivered to the subscription before it is dead-lettered. int 10 MaxAutoLockRenewalDuration The maximum duration within which the lock will be renewed automatically. This value should be greater than the longest message lock duration. TimeSpan 5 minutes ManagementTokenProvider Token provider ITokenProvider null AutoCompleteMessages Gets a value that indicates whether the processor should automatically complete messages after the message handler has completed processing bool true CustomHeadersBuilder Adds custom and/or mandatory Headers for incoming messages from heterogeneous systems.Func<Message, IServiceProvider, List<KeyValuePair<string, string>>>?
null Namespace Namespace of Servicebus , Needs to be set when using with TokenCredential Property string null DefaultCorrelationHeaders Adds additional correlation properties to all correlation filters. IDictionary Dictionary.Empty SQLFilters Custom SQL Filters by name and expression on Topic Subscribtion List> null"},{"location":"user-guide/en/transport/azure-service-bus/#sessions","title":"Sessions","text":"When sessions are enabled (see EnableSessions
option above), every message sent will have a session id. To control the session id, include an extra header with name AzureServiceBusHeaders.SessionId
when publishing events:
ICapPublisher capBus = ...;\nstring yourEventName = ...;\nYourEventType yourEvent = ...;\n\nDictionary<string, string> extraHeaders = new Dictionary<string, string>();\nextraHeaders.Add(AzureServiceBusHeaders.SessionId, <your-session-id>);\n\ncapBus.Publish(yourEventName, yourEvent, extraHeaders);\n
If no session id header is present, the message id will be used as the session id.
"},{"location":"user-guide/en/transport/azure-service-bus/#heterogeneous-systems","title":"Heterogeneous Systems","text":"Sometimes you might want to listen to a message that was published by an external system. In this case, you need to add a set of two mandatory headers for CAP compatibility as shown below.
c.UseAzureServiceBus(asb =>\n{\nasb.ConnectionString = ...\nasb.CustomHeadersBuilder = (msg, sp) =>\n[\n new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),\n new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)\n ];\n});\n
"},{"location":"user-guide/en/transport/azure-service-bus/#sql-filters","title":"SQL Filters","text":"You can set SQL filters on subscribtion level to get desired messages and not to have custom logic on business side. More about Azure Service Bus SQL FILTERS - Link
SQLFilters is List Of KeyValuePair , Key is filter name and Value SQL Expression.
c.UseAzureServiceBus(asb =>\n{\nasb.ConnectionString = ...\nasb.SQLFilters = new List<KeyValuePair<string, string>> {\n\nnew KeyValuePair<string,string>(\"IOTFilter\",\"FromIOTHub='true'\"),//The message will be handled if ApplicationProperties contains IOTFilter and value is true\nnew KeyValuePair<string,string>(\"SequenceFilter\",\"sys.enqueuedSequenceNumber >= 300\")\n};\n});\n
"},{"location":"user-guide/en/transport/general/","title":"Transports","text":"Transports move data from one place to another \u2013 between acquisition programs and pipelines, between pipelines and the entity database, and even between pipelines and external systems.
"},{"location":"user-guide/en/transport/general/#supported-transports","title":"Supported transports","text":"CAP supports several transport methods:
Azure Service Bus
vs RabbitMQ
: http://geekswithblogs.net/michaelstephenson/archive/2012/08/12/150399.aspx
Kafka
vs RabbitMQ
: https://stackoverflow.com/questions/42151544/is-there-any-reason-to-use-rabbitmq-over-kafka
Thanks to the community for supporting CAP, the following is the implementation of community-supported transport
ActiveMQ (@Lukas Zhang): https://github.com/lukazh
RedisMQ (@\u6728\u6728) https://github.com/difudotnet/CAP.RedisMQ.Extensions
ZeroMQ (@maikebing)\uff1a https://github.com/maikebing/CAP.Extensions
MQTT (@john jiang): https://github.com/jinzaz/jinzaz.CAP.MQTT
In Memory Queue is a memory-based message queue provided by Community.
"},{"location":"user-guide/en/transport/in-memory-queue/#configuration","title":"Configuration","text":"To use In Memory Queue as a message transporter, you need to install the following package from NuGet:
PM> Install-Package Savorboard.CAP.InMemoryMessageQueue\n
Next, add configuration options to the ConfigureServices
method of Startup.cs
:
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseInMemoryMessageQueue();\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/transport/kafka/","title":"Apache Kafka\u00ae","text":"Apache Kafka\u00ae is an open-source stream-processing software platform developed by LinkedIn and donated to the Apache Software Foundation, written in Scala and Java.
Kafka\u00ae can be used in CAP as a message transporter.
"},{"location":"user-guide/en/transport/kafka/#configuration","title":"Configuration","text":"To use Kafka transporter, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.Kafka\n
Then you can add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseKafka(opt=>{\n//KafkaOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/transport/kafka/#kafka-options","title":"Kafka Options","text":"The Kafka configuration parameters provided directly by the CAP:
NAME DESCRIPTION TYPE DEFAULT Servers Broker server address string ConnectionPoolSize connection pool size int 10 CustomHeadersBuilder Custom subscribe headers Func<> N/A"},{"location":"user-guide/en/transport/kafka/#customheadersbuilder-options","title":"CustomHeadersBuilder Options","text":"When the message sent from a heterogeneous system, because of the CAP needs to define additional headers, so an exception will occur at this time. By providing this parameter to set the custom headersn to make the subscriber works.
You can find the description of heterogeneous system integration here.
Sometimes, if you want to get additional context information from Broker, you can also add it through this option. For example, add information such as Offset or Partition.
Example\uff1a
x.UseKafka(opt =>\n{\n//...\n\nopt.CustomHeadersBuilder = (kafkaResult,sp) => new List<KeyValuePair<string, string>>\n{\nnew KeyValuePair<string, string>(\"my.kafka.offset\", kafkaResult.Offset.ToString()),\nnew KeyValuePair<string, string>(\"my.kafka.partition\", kafkaResult.Partition.ToString())\n};\n});\n
Then you can get the header you added by this way:
[CapSubscribe(\"sample.kafka.postgrsql\")]\npublic void HeadersTest(DateTime value, [FromCap]CapHeader header)\n{\nvar offset = header[\"my.kafka.offset\"];\nvar partition = header[\"my.kafka.partition\"];\n}\n
"},{"location":"user-guide/en/transport/kafka/#kafka-mainconfig-options","title":"Kafka MainConfig Options","text":"If you need more native Kakfa related configuration options, you can set them in the MainConfig
configuration option:
services.AddCap(capOptions => {\ncapOptions.UseKafka(kafkaOption=>\n{\n// kafka options.\n// kafkaOptions.MainConfig.Add(\"\", \"\");\n});\n});\n
MainConfig
is a configuration dictionary, you can find a list of supported configuration options through the following link.
https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
"},{"location":"user-guide/en/transport/nats/","title":"NATS","text":"NATS is a simple, secure and performant communications system for digital systems, services and devices. NATS is part of the Cloud Native Computing Foundation (CNCF).
Warning
Since version 5.2+, CAP's relevant features have been implemented based on JetStream, so it needs to be explicitly enabled on the server.
You need to enable JetStream by specifying the --jetstream
parameter when starting the NATS Server in order to use CAP properly.
To use NATS transporter, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.NATS\n
Then you can add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\nservices.AddCap(capOptions =>\n{\ncapOptions.UseNATS(natsOptions=>{\n//NATS Options\n});\n});\n}\n
"},{"location":"user-guide/en/transport/nats/#nats-options","title":"NATS Options","text":"NATS configuration parameters provided directly by the CAP:
NAME DESCRIPTION TYPE DEFAULT Options NATS client configuration Options Options Servers Server url/urls used to connect to the NATs server. string NULL ConnectionPoolSize number of connections pool uint 10 DeliverPolicy The point in the stream to receive messages from (\u26a0\ufe0f Removed from version 8.1.0, useConsumerOptions
instead.) enum DeliverPolicy.New StreamOptions \ud83c\udd95 Stream configuration Action NULL ConsumerOptions \ud83c\udd95 Consumer configuration Action NULL"},{"location":"user-guide/en/transport/nats/#nats-configurationoptions","title":"NATS ConfigurationOptions","text":"If you need more native NATS related configuration options, you can set them in the Options
option:
services.AddCap(capOptions => {\ncapOptions.UseNATS(natsOptions=>\n{\n// NATS options.\nnatsOptions.Options.Url=\"\";\n});\n});\n
Options
is a NATS.Client ConfigurationOptions , you can find more details through this link
Apache Pulsar is a cloud-native, distributed messaging and streaming platform originally created at Yahoo! and now a top-level Apache Software Foundation project.
Pulsar can be used in CAP as a message transporter.
"},{"location":"user-guide/en/transport/pulsar/#configuration","title":"Configuration","text":"To use Pulsar transporter, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.Pulsar\n
Then you can add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UsePulsar(opt => {\n//Pulsar options\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/transport/pulsar/#pulsar-options","title":"Pulsar Options","text":"The Pulsar configuration parameters provided directly by the CAP:
NAME DESCRIPTION TYPE DEFAULT ServiceUrl Broker server address string TlsOptions Tls configuration object"},{"location":"user-guide/en/transport/rabbitmq/","title":"RabbitMQ","text":"RabbitMQ is an open-source message-broker software that originally implemented the Advanced Message Queuing Protocol and has since been extended with a plug-in architecture to support Streaming Text Oriented Messaging Protocol, Message Queuing Telemetry Transport, and other protocols.
RabbitMQ can be used in CAP as a message transporter.
Notes
When using RabbitMQ, the consumer integrated with the CAP application will automatically create a persistent queue after it is started for the first time. Subsequent messages will be normally transmitted to the queue and consumed. However, if you have never started the consumer, the queue will not be created. In this case, if you publish messages first, RabbitMQ Exchange will discard the messages received directly until the consumer is started and the queue is created.
"},{"location":"user-guide/en/transport/rabbitmq/#configuration","title":"Configuration","text":"To use RabbitMQ transporter, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.RabbitMQ\n
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseRabbitMQ(opt=>\n{\n//RabbitMQOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/en/transport/rabbitmq/#rabbitmq-options","title":"RabbitMQ Options","text":"The RabbitMQ configuration parameters provided directly by CAP:
NAME DESCRIPTION TYPE DEFAULT HostName Broker host address string localhost UserName Broker user name string guest Password Broker password string guest VirtualHost Broker virtual host string / Port Port int -1 ExchangeName Default exchange name string cap.default.topic QueueArguments Extra queuex-arguments
QueueArgumentsOptions N/A ConnectionFactoryOptions RabbitMQClient native connection options ConnectionFactory N/A CustomHeadersBuilder Custom subscribe headers See the blow N/A PublishConfirms Enable publish confirms bool false BasicQosOptions Specify Qos of message prefetch BasicQos N/A"},{"location":"user-guide/en/transport/rabbitmq/#connectionfactory-option","title":"ConnectionFactory Option","text":"If you need more native ConnectionFactory
configuration options, you can set it by 'ConnectionFactoryOptions' option:
services.AddCap(x =>\n{\nx.UseRabbitMQ(o =>\n{\no.HostName = \"localhost\";\no.ConnectionFactoryOptions = opt => { //rabbitmq client ConnectionFactory config\n};\n});\n});\n
"},{"location":"user-guide/en/transport/rabbitmq/#customheadersbuilder-option","title":"CustomHeadersBuilder Option","text":"When the message sent from the RabbitMQ management console or a heterogeneous system, because of the CAP needs to define additional headers, so an exception will occur at this time. By providing this parameter to set the custom headersn to make the subscriber works.
You can find the description of Header Information here.
Example\uff1a
x.UseRabbitMQ(aa =>\n{\naa.CustomHeadersBuilder = (msg, sp) =>\n[\n new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),\n new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)\n ];\n});\n
"},{"location":"user-guide/en/transport/rabbitmq/#how-to-connect-cluster","title":"How to connect cluster","text":"using comma split connection string, like this:
x=> x.UseRabbitMQ(\"localhost:5672,localhost:5673,localhost:5674\")\n
"},{"location":"user-guide/en/transport/redis-streams/","title":"Redis Streams","text":"Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker.
Redis Stream is a new data type introduced with Redis 5.0, which models a log data structure in a more abstract way with an append only data structure.
Redis Streams can be used in CAP as a message transporter.
"},{"location":"user-guide/en/transport/redis-streams/#configuration","title":"Configuration","text":"To use Redis Streams transporter, you need to install the following package from NuGet:
PM> Install-Package DotNetCore.CAP.RedisStreams\n
Then you can add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)\n{\nservices.AddCap(capOptions =>\n{\ncapOptions.UseRedis(redisOptions=>{\n//redisOptions\n});\n});\n}\n
"},{"location":"user-guide/en/transport/redis-streams/#redis-streams-options","title":"Redis Streams Options","text":"Redis configuration parameters provided directly by the CAP:
NAME DESCRIPTION TYPE DEFAULT Configuration redis connection configuration (StackExchange.Redis) ConfigurationOptions ConfigurationOptions StreamEntriesCount number of entries returned from a stream while reading uint 10 ConnectionPoolSize number of connections pool uint 10"},{"location":"user-guide/en/transport/redis-streams/#redis-configuration-options","title":"Redis Configuration Options","text":"If you need more native Redis related configuration options, you can set them in the Configuration
option:
services.AddCap(capOptions => {\ncapOptions.UseRedis(redisOptions=>\n{\n// redis options.\nredisOptions.Configuration.EndPoints.Add(IPAddress.Loopback, 0);\n});\n});\n
Configuration
is a StackExchange.Redis ConfigurationOptions , you can find more details through this link
Since redis streams does not have the feature of deletes all messages that already acknowledged by all groups issue , so you need to consider if using a script to perform the deletion regularly.
"},{"location":"user-guide/zh/cap/configuration/","title":"\u914d\u7f6e","text":"\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u4f60\u5728\u5411DI\u5bb9\u5668\u4e2d\u6ce8\u518cCAP\u670d\u52a1\u7684\u65f6\u5019\u6307\u5b9a\u6b64\u914d\u7f6e\u3002
services.AddCap(config=> {\n// config.XXX \n});\n
\u5176\u4e2d services
\u4ee3\u8868\u7684\u662f IServiceCollection
\u63a5\u53e3\u5bf9\u8c61\uff0c\u5b83\u4f4d\u4e8e Microsoft.Extensions.DependencyInjection
\u4e0b\u9762\u3002
\u6700\u7b80\u5355\u7684\u56de\u7b54\u5c31\u662f\uff0c\u81f3\u5c11\u4f60\u8981\u914d\u7f6e\u4e00\u4e2a\u4f20\u8f93\u5668\u548c\u4e00\u4e2a\u5b58\u50a8\uff0c\u5982\u679c\u4f60\u60f3\u5feb\u901f\u5f00\u59cb\u4f60\u53ef\u4ee5\u4f7f\u7528\u4e0b\u9762\u7684\u914d\u7f6e\uff1a
services.AddCap(config => {\nconfig.UseInMemoryMessageQueue(); //\u9700\u8981\u5f15\u7528 Savorboard.CAP.InMemoryMessageQueue \u5305\nconfig.UseInMemoryStorage();\n});\n
\u6709\u5173\u5177\u4f53\u7684\u4f20\u8f93\u5668\u914d\u7f6e\u548c\u5b58\u50a8\u914d\u7f6e\uff0c\u4f60\u53ef\u4ee5\u67e5\u770b Transports \u7ae0\u8282\u548c Persistent \u7ae0\u8282\u4e2d\u5177\u4f53\u7ec4\u4ef6\u63d0\u4f9b\u7684\u914d\u7f6e\u9879\u3002
"},{"location":"user-guide/zh/cap/configuration/#cap","title":"CAP \u4e2d\u7684\u81ea\u5b9a\u4e49\u914d\u7f6e","text":"\u5728 AddCap
\u4e2d CapOptions
\u5bf9\u8c61\u662f\u7528\u6765\u5b58\u50a8\u914d\u7f6e\u76f8\u5173\u4fe1\u606f\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\u5b83\u4eec\u90fd\u5177\u6709\u4e00\u4e9b\u9ed8\u8ba4\u503c\uff0c\u6709\u4e9b\u65f6\u5019\u4f60\u53ef\u80fd\u9700\u8981\u81ea\u5b9a\u4e49\u3002
\u9ed8\u8ba4\u503c\uff1acap.queue.{\u7a0b\u5e8f\u96c6\u540d\u79f0}
\u9ed8\u8ba4\u7684\u6d88\u8d39\u8005\u7ec4\u7684\u540d\u5b57\uff0c\u5728\u4e0d\u540c\u7684 Transports \u4e2d\u5bf9\u5e94\u4e0d\u540c\u7684\u540d\u5b57\uff0c\u53ef\u4ee5\u901a\u8fc7\u81ea\u5b9a\u4e49\u6b64\u503c\u6765\u81ea\u5b9a\u4e49\u4e0d\u540c Transports \u4e2d\u7684\u540d\u5b57\uff0c\u4ee5\u4fbf\u4e8e\u67e5\u770b\u3002
Mapping
\u5728 RabbitMQ \u4e2d\u6620\u5c04\u5230 Queue Names\u3002 \u5728 Apache Kafka \u4e2d\u6620\u5c04\u5230 Consumer Group Id\u3002 \u5728 Azure Service Bus \u4e2d\u6620\u5c04\u5230 Subscription Name\u3002 \u5728 NATS \u4e2d\u6620\u5c04\u5230 Queue Group Name. \u5728 Redis Streams \u4e2d\u6620\u5c04\u5230 Consumer Group.
"},{"location":"user-guide/zh/cap/configuration/#groupnameprefix","title":"GroupNamePrefix","text":"\u9ed8\u8ba4\u503c\uff1aNull
\u4e3a\u8ba2\u9605 Group \u7edf\u4e00\u6dfb\u52a0\u524d\u7f00\u3002 https://github.com/dotnetcore/CAP/pull/780
"},{"location":"user-guide/zh/cap/configuration/#topicnameprefix","title":"TopicNamePrefix","text":"\u9ed8\u8ba4\u503c\uff1a Null
\u4e3a Topic \u7edf\u4e00\u6dfb\u52a0\u524d\u7f00\u3002 https://github.com/dotnetcore/CAP/pull/780
"},{"location":"user-guide/zh/cap/configuration/#version","title":"Version","text":"\u9ed8\u8ba4\u503c\uff1av1
\u7528\u4e8e\u7ed9\u6d88\u606f\u6307\u5b9a\u7248\u672c\u6765\u9694\u79bb\u4e0d\u540c\u7248\u672c\u670d\u52a1\u7684\u6d88\u606f\uff0c\u5e38\u7528\u4e8eA/B\u6d4b\u8bd5\u6216\u8005\u591a\u670d\u52a1\u7248\u672c\u7684\u573a\u666f\u3002\u4ee5\u4e0b\u662f\u5176\u5e94\u7528\u573a\u666f\uff1a
\u4e1a\u52a1\u5feb\u901f\u8fed\u4ee3\uff0c\u9700\u8981\u5411\u524d\u517c\u5bb9
\u7531\u4e8e\u4e1a\u52a1\u7684\u5feb\u901f\u8fed\u4ee3\uff0c\u5728\u5404\u4e2a\u670d\u52a1\u96c6\u6210\u7684\u8fc7\u7a0b\u4e2d\uff0c\u6d88\u606f\u7684\u6570\u636e\u7ed3\u6784\u5e76\u4e0d\u662f\u56fa\u5b9a\u4e0d\u53d8\u7684\uff0c\u6709\u4e9b\u65f6\u5019\u6211\u4eec\u4e3a\u4e86\u9002\u5e94\u65b0\u5f15\u5165\u7684\u9700\u6c42\uff0c\u4f1a\u6dfb\u52a0\u6216\u8005\u4fee\u6539\u4e00\u4e9b\u6570\u636e\u7ed3\u6784\u3002\u5982\u679c\u4f60\u662f\u4e00\u5957\u5168\u65b0\u7684\u7cfb\u7edf\u8fd9\u6ca1\u6709\u4ec0\u4e48\u95ee\u9898\uff0c\u4f46\u662f\u5982\u679c\u4f60\u7684\u7cfb\u7edf\u5df2\u7ecf\u90e8\u7f72\u5230\u751f\u4ea7\u73af\u5883\u4e86\u5e76\u4e14\u6b63\u5728\u670d\u52a1\u5ba2\u6237\uff0c\u8fd9\u5c31\u4f1a\u5bfc\u81f4\u65b0\u7684\u529f\u80fd\u5728\u4e0a\u7ebf\u7684\u65f6\u5019\u548c\u65e7\u7684\u6570\u636e\u7ed3\u6784\u53d1\u751f\u4e0d\u517c\u5bb9\uff0c\u90a3\u4e48\u8fd9\u4e9b\u6539\u53d8\u53ef\u80fd\u4f1a\u5bfc\u81f4\u51fa\u73b0\u4e25\u91cd\u7684\u95ee\u9898\uff0c\u8981\u60f3\u89e3\u51b3\u8fd9\u4e2a\u95ee\u9898\uff0c\u53ea\u80fd\u628a\u6d88\u606f\u961f\u5217\u548c\u6301\u4e45\u5316\u7684\u6d88\u606f\u5168\u90e8\u6e05\u7a7a\uff0c\u7136\u540e\u624d\u80fd\u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\uff0c\u8fd9\u5bf9\u4e8e\u751f\u4ea7\u73af\u5883\u6765\u8bf4\u663e\u7136\u662f\u81f4\u547d\u7684\u3002
\u591a\u4e2a\u7248\u672c\u7684\u670d\u52a1\u7aef
\u6709\u4e9b\u65f6\u5019\uff0cApp\u7684\u670d\u52a1\u7aef\u9700\u8981\u63d0\u4f9b\u591a\u5957\u63a5\u53e3\uff0c\u6765\u652f\u6301\u4e0d\u540c\u7248\u672c\u7684App\uff0c\u8fd9\u4e9b\u4e0d\u540c\u7248\u672c\u7684App\u76f8\u540c\u7684\u63a5\u53e3\u548c\u670d\u52a1\u7aef\u4ea4\u4e92\u7684\u6570\u636e\u7ed3\u6784\u53ef\u80fd\u662f\u4e0d\u4e00\u6837\u7684\uff0c\u6240\u4ee5\u901a\u5e38\u60c5\u51b5\u4e0b\u670d\u52a1\u7aef\u63d0\u4f9b\u4e0d\u7528\u7684\u8def\u7531\u5730\u5740\u6765\u9002\u914d\u4e0d\u540c\u7248\u672c\u7684App\u8c03\u7528\u3002
\u4e0d\u540c\u5b9e\u4f8b\uff0c\u4f7f\u7528\u76f8\u540c\u7684\u6301\u4e45\u5316\u8868/\u96c6\u5408
\u5e0c\u671b\u591a\u4e2a\u4e0d\u540c\u5b9e\u4f8b\u7684\u7a0b\u5e8f\u53ef\u4ee5\u516c\u7528\u76f8\u540c\u7684\u6570\u636e\u5e93\uff0c\u5728 2.4 \u4e4b\u524d\u7684\u7248\u672c\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u6307\u5b9a\u4e0d\u540c\u7684\u8868\u540d\u6765\u9694\u79bb\u4e0d\u540c\u5b9e\u4f8b\u7684\u6570\u636e\u5e93\u8868\uff0c\u5373\u5728CAP\u914d\u7f6e\u7684\u65f6\u5019\u901a\u8fc7\u914d\u7f6e\u4e0d\u540c\u7684\u8868\u540d\u524d\u7f00\u6765\u5b9e\u73b0\u3002
\u67e5\u770b\u535a\u5ba2\u6765\u4e86\u89e3\u66f4\u591a\u5173\u4e8e Version \u7684\u4fe1\u606f\uff1a https://www.cnblogs.com/savorboard/p/cap-2-4.html
"},{"location":"user-guide/zh/cap/configuration/#failedretryinterval","title":"FailedRetryInterval","text":"\u9ed8\u8ba4\u503c\uff1a60 \u79d2
\u5728\u6d88\u606f\u53d1\u9001\u7684\u65f6\u5019\uff0c\u5982\u679c\u53d1\u9001\u5931\u8d25\uff0cCAP\u5c06\u4f1a\u5bf9\u6d88\u606f\u8fdb\u884c\u91cd\u8bd5\uff0c\u6b64\u914d\u7f6e\u9879\u7528\u6765\u914d\u7f6e\u6bcf\u6b21\u91cd\u8bd5\u7684\u95f4\u9694\u65f6\u95f4\u3002
\u5728\u6d88\u606f\u6d88\u8d39\u7684\u8fc7\u7a0b\u4e2d\uff0c\u5982\u679c\u6d88\u8d39\u5931\u8d25\uff0cCAP\u5c06\u4f1a\u5bf9\u6d88\u606f\u8fdb\u884c\u91cd\u8bd5\u6d88\u8d39\uff0c\u6b64\u914d\u7f6e\u9879\u7528\u6765\u914d\u7f6e\u6bcf\u6b21\u91cd\u8bd5\u7684\u95f4\u9694\u65f6\u95f4\u3002
\u91cd\u8bd5 & \u95f4\u9694
\u5728\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u91cd\u8bd5\u5c06\u5728\u53d1\u9001\u548c\u6d88\u8d39\u6d88\u606f\u5931\u8d25\u7684 FallbackWindowLookbackSeconds\uff084\u5206\u949f\u540e\uff09 \u5f00\u59cb\uff0c\u8fd9\u662f\u4e3a\u4e86\u907f\u514d\u8bbe\u7f6e\u6d88\u606f\u72b6\u6001\u5ef6\u8fdf\u5bfc\u81f4\u53ef\u80fd\u51fa\u73b0\u7684\u95ee\u9898\u3002 \u53d1\u9001\u548c\u6d88\u8d39\u6d88\u606f\u7684\u8fc7\u7a0b\u4e2d\u5931\u8d25\u4f1a\u7acb\u5373\u91cd\u8bd5 3 \u6b21\uff0c\u5728 3 \u6b21\u4ee5\u540e\u5c06\u8fdb\u5165\u91cd\u8bd5\u8f6e\u8be2\uff0c\u6b64\u65f6 FailedRetryInterval \u914d\u7f6e\u624d\u4f1a\u751f\u6548\u3002
\u591a\u5b9e\u4f8b\u5e76\u53d1\u91cd\u8bd5
\u6211\u4eec\u57287.1.0\u7248\u672c\u4e2d\u5f15\u5165\u4e86\u57fa\u4e8e\u6570\u636e\u5e93\u7684\u5206\u5e03\u5f0f\u9501\u4ee5\u5e94\u5bf9\u5728\u591a\u4e2a\u5b9e\u4f8b\u4e0b\u5bf9\u6570\u636e\u5e93\u91cd\u8bd5\u7684\u5e76\u53d1\u6570\u636e\u83b7\u53d6\u95ee\u9898\uff0c\u4f60\u9700\u8981\u663e\u5f0f\u914d\u7f6e UseStorageLock
\u4e3a true\u3002
\u9ed8\u8ba4\u503c: false
\u5982\u679c\u8bbe\u7f6e\u4e3atrue\uff0c\u6211\u4eec\u5c06\u4f7f\u7528\u57fa\u4e8e\u6570\u636e\u5e93\u7684\u5206\u5e03\u5f0f\u9501\u4ee5\u5e94\u5bf9\u91cd\u8bd5\u8fdb\u7a0b\u5728\u591a\u4e2a\u5b9e\u4f8b\u4e0b\u5bf9\u6570\u636e\u5e93\u6570\u636e\u7684\u5e76\u53d1\u83b7\u53d6\u95ee\u9898\u3002\u8fd9\u5c06\u4f1a\u5728\u6570\u636e\u5e93\u751f\u6210 cap.lock \u8868\u3002
"},{"location":"user-guide/zh/cap/configuration/#consumerthreadcount","title":"ConsumerThreadCount","text":"\u9ed8\u8ba4\u503c\uff1a1
\u6d88\u8d39\u8005\u7ebf\u7a0b\u5e76\u884c\u5904\u7406\u6d88\u606f\u7684\u7ebf\u7a0b\u6570\uff0c\u5f53\u8fd9\u4e2a\u503c\u5927\u4e8e1\u65f6\uff0c\u5c06\u4e0d\u80fd\u4fdd\u8bc1\u6d88\u606f\u6267\u884c\u7684\u987a\u5e8f\u3002
"},{"location":"user-guide/zh/cap/configuration/#collectorcleaninginterval","title":"CollectorCleaningInterval","text":"\u9ed8\u8ba4\u503c\uff1a300 \u79d2
\u6536\u96c6\u5668\u5220\u9664\u5df2\u7ecf\u8fc7\u671f\u6d88\u606f\u7684\u65f6\u95f4\u95f4\u9694\u3002
"},{"location":"user-guide/zh/cap/configuration/#failedretrycount","title":"FailedRetryCount","text":"\u9ed8\u8ba4\u503c\uff1a50
\u91cd\u8bd5\u7684\u6700\u5927\u6b21\u6570\u3002\u5f53\u8fbe\u5230\u6b64\u8bbe\u7f6e\u503c\u65f6\uff0c\u5c06\u4e0d\u4f1a\u518d\u7ee7\u7eed\u91cd\u8bd5\uff0c\u901a\u8fc7\u6539\u53d8\u6b64\u53c2\u6570\u6765\u8bbe\u7f6e\u91cd\u8bd5\u7684\u6700\u5927\u6b21\u6570\u3002
"},{"location":"user-guide/zh/cap/configuration/#fallbackwindowlookbackseconds","title":"FallbackWindowLookbackSeconds","text":"\u9ed8\u8ba4\u503c\uff1a240 \u79d2
\u914d\u7f6e\u91cd\u8bd5\u5904\u7406\u5668\u62fe\u53d6 Scheduled
\u6216 Failed
\u72b6\u6001\u6d88\u606f\u7684\u56de\u9000\u65f6\u95f4\u7a97\u3002
\u9ed8\u8ba4\u503c\uff1aNULL
\u7c7b\u578b\uff1aAction<FailedInfo>
\u91cd\u8bd5\u9608\u503c\u7684\u5931\u8d25\u56de\u8c03\u3002\u5f53\u91cd\u8bd5\u8fbe\u5230 FailedRetryCount \u8bbe\u7f6e\u7684\u503c\u7684\u65f6\u5019\uff0c\u5c06\u8c03\u7528\u6b64 Action \u56de\u8c03\uff0c\u4f60\u53ef\u4ee5\u901a\u8fc7\u6307\u5b9a\u6b64\u56de\u8c03\u6765\u63a5\u6536\u5931\u8d25\u8fbe\u5230\u6700\u5927\u7684\u901a\u77e5\uff0c\u4ee5\u505a\u51fa\u4eba\u5de5\u4ecb\u5165\u3002\u4f8b\u5982\u53d1\u9001\u90ae\u4ef6\u6216\u8005\u77ed\u4fe1\u3002
"},{"location":"user-guide/zh/cap/configuration/#succeedmessageexpiredafter","title":"SucceedMessageExpiredAfter","text":"\u9ed8\u8ba4\u503c\uff1a24*3600 \u79d2\uff081\u5929\u540e\uff09
\u6210\u529f\u6d88\u606f\u7684\u8fc7\u671f\u65f6\u95f4\uff08\u79d2\uff09\u3002 \u5f53\u6d88\u606f\u53d1\u9001\u6216\u8005\u6d88\u8d39\u6210\u529f\u65f6\u5019\uff0c\u5728\u65f6\u95f4\u8fbe\u5230 SucceedMessageExpiredAfter
\u79d2\u65f6\u5019\u5c06\u4f1a\u4ece Persistent \u4e2d\u5220\u9664\uff0c\u4f60\u53ef\u4ee5\u901a\u8fc7\u6307\u5b9a\u6b64\u503c\u6765\u8bbe\u7f6e\u8fc7\u671f\u7684\u65f6\u95f4\u3002
\u9ed8\u8ba4\u503c\uff1a15*24*3600 \u79d2\uff0815\u5929\u540e\uff09
\u5931\u8d25\u6d88\u606f\u7684\u8fc7\u671f\u65f6\u95f4\uff08\u79d2\uff09\u3002 \u5f53\u6d88\u606f\u53d1\u9001\u6216\u8005\u6d88\u8d39\u5931\u8d25\u65f6\u5019\uff0c\u5728\u65f6\u95f4\u8fbe\u5230 FailedMessageExpiredAfter
\u79d2\u65f6\u5019\u5c06\u4f1a\u4ece Persistent \u4e2d\u5220\u9664\uff0c\u4f60\u53ef\u4ee5\u901a\u8fc7\u6307\u5b9a\u6b64\u503c\u6765\u8bbe\u7f6e\u8fc7\u671f\u7684\u65f6\u95f4\u3002
\u9ed8\u8ba4\u503c: false
\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cCAP\u4f1a\u5c06\u6240\u6709\u6d88\u8d39\u8005\u7ec4\u7684\u6d88\u606f\u90fd\u5148\u653e\u7f6e\u5230\u5185\u5b58\u540c\u4e00\u4e2aChannel\u4e2d\uff0c\u7136\u540e\u7ebf\u6027\u5904\u7406\u3002 \u5982\u679c\u8bbe\u7f6e\u4e3a true\uff0c\u5219\u6bcf\u4e2a\u6d88\u8d39\u8005\u7ec4\u90fd\u4f1a\u6839\u636e ConsumerThreadCount
\u8bbe\u7f6e\u7684\u503c\u521b\u5efa\u5355\u72ec\u7684\u7ebf\u7a0b\u8fdb\u884c\u5904\u7406\u3002
\u5728\u540c\u65f6\u914d\u5408\u4f7f\u7528 EnableConsumerPrefetch
\u65f6\uff0c\u8bf7\u53c2\u8003 issue #1399 \u4ee5\u6e05\u6670\u5176\u9884\u671f\u884c\u4e3a\u3002
\u9ed8\u8ba4\u503c: false\uff0c \u5728 7.0 \u7248\u672c\u4e4b\u524d\u9ed8\u8ba4\u884c\u4e3a true
\u8be5\u914d\u7f6e\u9879\u5df2\u88ab\u91cd\u547d\u540d\u4e3a EnableSubscriberParallelExecute
\uff0c\u8bf7\u4f7f\u7528\u65b0\u9009\u9879\u3002
\u9ed8\u8ba4\u503c: false
\u5982\u679c\u8bbe\u7f6e\u4e3a true
\uff0cCAP\u5c06\u63d0\u524d\u4eceBroker\u62c9\u53d6\u4e00\u6279\u6d88\u606f\u7f6e\u4e8e\u5185\u5b58\u7f13\u51b2\u533a\uff0c\u7136\u540e\u6267\u884c\u8ba2\u9605\u65b9\u6cd5\uff1b\u5f53\u8ba2\u9605\u65b9\u6cd5\u6267\u884c\u5b8c\u6210\u540e\uff0c\u62c9\u53d6\u4e0b\u4e00\u6279\u6d88\u606f\u81f3\u4e8e\u7f13\u51b2\u533a\u7136\u540e\u6267\u884c\u3002
\u6ce8\u610f\u4e8b\u9879
\u8bbe\u7f6e\u4e3a true \u53ef\u80fd\u4f1a\u4ea7\u751f\u4e00\u4e9b\u95ee\u9898\uff0c\u5f53\u8ba2\u9605\u65b9\u6cd5\u6267\u884c\u8fc7\u6162\u8017\u65f6\u592a\u4e45\u65f6\uff0c\u4f1a\u5bfc\u81f4\u91cd\u8bd5\u7ebf\u7a0b\u62fe\u53d6\u5230\u8fd8\u672a\u6267\u884c\u7684\u7684\u6d88\u606f\u3002\u91cd\u8bd5\u7ebf\u7a0b\u9ed8\u8ba4\u62fe\u53d64\u5206\u949f\u524d\uff08FallbackWindowLookbackSeconds \u914d\u7f6e\u9879\uff09\u7684\u6d88\u606f\uff0c\u4e5f\u5c31\u662f\u8bf4\u5982\u679c\u6d88\u8d39\u7aef\u79ef\u538b\u4e86\u8d85\u8fc74\u5206\u949f\uff08FallbackWindowLookbackSeconds \u914d\u7f6e\u9879\uff09\u7684\u6d88\u606f\u5c31\u4f1a\u88ab\u91cd\u65b0\u62fe\u53d6\u5230\u518d\u6b21\u6267\u884c
"},{"location":"user-guide/zh/cap/configuration/#subscriberparallelexecutethreadcount","title":"SubscriberParallelExecuteThreadCount","text":"Default: Environment.ProcessorCount
\u5f53\u542f\u7528 EnableSubscriberParallelExecute
\u65f6, \u53ef\u901a\u8fc7\u6b64\u53c2\u6570\u6267\u884c\u5e76\u884c\u5904\u7406\u7684\u7ebf\u7a0b\u6570\uff0c\u9ed8\u8ba4\u503c\u4e3a\u5904\u7406\u5668\u4e2a\u6570\u3002
Default: 1
\u5f53\u542f\u7528 EnableSubscriberParallelExecute
\u65f6, \u901a\u8fc7\u6b64\u53c2\u6570\u8bbe\u7f6e\u7f13\u51b2\u533a\u548c\u7ebf\u7a0b\u6570\u7684\u56e0\u5b50\u7cfb\u6570\uff0c\u4e5f\u5c31\u662f\u7f13\u51b2\u533a\u5927\u5c0f\u7b49\u4e8e SubscriberParallelExecuteThreadCount
\u4e58 SubscriberParallelExecuteBufferFactor
.
\u9ed8\u8ba4\u503c: false
\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u53d1\u9001\u7684\u6d88\u606f\u90fd\u5148\u653e\u7f6e\u5230\u5185\u5b58\u540c\u4e00\u4e2aChannel\u4e2d\uff0c\u7136\u540e\u7ebf\u6027\u5904\u7406\u3002 \u5982\u679c\u8bbe\u7f6e\u4e3a true\uff0c\u5219\u53d1\u9001\u6d88\u606f\u7684\u4efb\u52a1\u5c06\u7531.NET\u7ebf\u7a0b\u6c60\u5e76\u884c\u5904\u7406\uff0c\u8fd9\u4f1a\u5927\u5927\u63d0\u9ad8\u53d1\u9001\u7684\u901f\u5ea6\u3002
"},{"location":"user-guide/zh/cap/filter/","title":"\u8fc7\u6ee4\u5668","text":"\u4ece 5.1.0 \u7248\u672c\u540e\uff0c\u6211\u4eec\u5f15\u5165\u4e86\u5bf9\u8ba2\u9605\u8005\u8fc7\u6ee4\u5668\u7684\u652f\u6301\uff0c\u4ee5\u4f7f\u5728\u67d0\u4e9b\u573a\u666f\uff08\u5982\u4e8b\u52a1\u5904\u7406\uff0c\u65e5\u5fd7\u8bb0\u5f55\u7b49\uff09\u4e2d\u53d8\u5f97\u5bb9\u6613\u3002
"},{"location":"user-guide/zh/cap/filter/#_2","title":"\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u5668","text":""},{"location":"user-guide/zh/cap/filter/#_3","title":"\u6dfb\u52a0\u8fc7\u6ee4\u5668","text":"\u521b\u5efa\u4e00\u4e2a\u8fc7\u6ee4\u5668\u7c7b\uff0c\u5e76\u7ee7\u627f SubscribeFilter
\u62bd\u8c61\u7c7b\u3002
public class MyCapFilter: SubscribeFilter\n{\npublic override Task OnSubscribeExecutingAsync(ExecutingContext context)\n{\n// \u8ba2\u9605\u65b9\u6cd5\u6267\u884c\u524d\n}\n\npublic override Task OnSubscribeExecutedAsync(ExecutedContext context)\n{\n// \u8ba2\u9605\u65b9\u6cd5\u6267\u884c\u540e\n}\n\npublic override Task OnSubscribeExceptionAsync(ExceptionContext context)\n{\n// \u8ba2\u9605\u65b9\u6cd5\u6267\u884c\u5f02\u5e38\n}\n}\n
\u5728\u4e00\u4e9b\u573a\u666f\u4e2d\uff0c\u5982\u679c\u60f3\u7ec8\u6b62\u8ba2\u9605\u8005\u65b9\u6cd5\u6267\u884c\uff0c\u53ef\u4ee5\u5728 OnSubscribeExecutingAsync
\u4e2d\u629b\u51fa\u5f02\u5e38\uff0c\u5e76\u4e14\u5728 OnSubscribeExceptionAsync
\u4e2d\u9009\u62e9\u5ffd\u7565\u8be5\u5f02\u5e38\u3002
\u901a\u8fc7\u5728 ExceptionContext
\u4e2d\u8bbe\u7f6e context.ExceptionHandled = true
\u6765\u5ffd\u7565\u5f02\u5e38\u3002
public override Task OnSubscribeExceptionAsync(ExceptionContext context)\n{\ncontext.ExceptionHandled = true;\n}\n
"},{"location":"user-guide/zh/cap/filter/#_4","title":"\u914d\u7f6e\u8fc7\u6ee4\u5668","text":"services.AddCap(opt =>\n{\n// ***\n}.AddSubscribeFilter<MyCapFilter>();\n
\u76ee\u524d\uff0c \u6211\u4eec\u8fd8\u4e0d\u652f\u6301\u540c\u65f6\u6dfb\u52a0\u591a\u4e2a\u8fc7\u6ee4\u5668\u3002
\u8fc7\u6ee4\u5668\u4e2d\u4f7f\u7528 AsyncLocal \u7684\u95ee\u9898
\u6211\u4eec\u4e0d\u5efa\u8bae\u5728\u8fc7\u6ee4\u5668\u4e2d\u4f7f\u7528AsyncLocal\uff0c\u56e0\u4e3a\u8fc7\u6ee4\u5668\u7684\u751f\u547d\u5468\u671f\u4e3aScoped\uff0c\u6240\u4ee5\u76f4\u63a5\u5b9a\u4e49\u4e34\u65f6\u53d8\u91cf\u5373\u53ef\u5728\u6574\u4e2a\u6267\u884c\u5468\u671f\u5185\u5171\u4eab\u53d8\u91cf\u503c\u3002 \u7136\u540e\uff0c\u5982\u679c\u7531\u4e8e\u4e00\u4e9b\u4f60\u65e0\u6cd5\u63a7\u5236\u7684\u539f\u56e0\u8981\u4f7f\u7528\uff0c\u7531\u4e8eAsyncLocal\u7684\u8bbe\u8ba1\u95ee\u9898\uff0c\u5219\u53ef\u5c06\u5f02\u6b65\u8fc7\u6ee4\u5668\u4f5c\u4e3a\u540c\u6b65\u4f7f\u7528\uff0c\u4e5f\u5c31\u662f\u7ee7\u627f\u7684\u65b9\u6cd5\u6784\u9020\u4e2d\u4e0d\u6dfb\u52a0 async \u5173\u952e\u5b57\u3002
"},{"location":"user-guide/zh/cap/idempotence/","title":"\u5e42\u7b49\u6027","text":"\u5e42\u7b49\u6027\uff08\u4f60\u53ef\u4ee5\u5728Wikipedia\u8bfb\u5230\u5173\u4e8e\u5e42\u7b49\u6027\u7684\u5b9a\u4e49\uff09\uff0c\u5f53\u6211\u4eec\u8c08\u8bba\u5e42\u7b49\u65f6\uff0c\u4e00\u822c\u662f\u6307\u53ef\u4ee5\u91cd\u590d\u5904\u7406\u4f20\u9012\u7684\u6d88\u606f\uff0c\u800c\u4e0d\u4f1a\u4ea7\u751f\u610f\u5916\u7684\u7ed3\u679c\u3002
"},{"location":"user-guide/zh/cap/idempotence/#_2","title":"\u4ea4\u4ed8\u4fdd\u8bc1","text":"\u5728\u8bf4\u5e42\u7b49\u6027\u4e4b\u524d\uff0c\u6211\u4eec\u5148\u6765\u8bf4\u4e0b\u5173\u4e8e\u6d88\u8d39\u7aef\u7684\u6d88\u606f\u4ea4\u4ed8\u3002
\u7531\u4e8eCAP\u4e0d\u662f\u4f7f\u7528\u7684 MS DTC \u6216\u5176\u4ed6\u7c7b\u578b\u76842PC\u5206\u5e03\u5f0f\u4e8b\u52a1\u673a\u5236\uff0c\u6240\u4ee5\u5b58\u5728\u81f3\u5c11\u6d88\u606f\u4e25\u683c\u4ea4\u4ed8\u4e00\u6b21\u7684\u95ee\u9898\uff0c\u5177\u4f53\u7684\u8bf4\u5728\u57fa\u4e8e\u6d88\u606f\u7684\u7cfb\u7edf\u4e2d\uff0c\u5b58\u5728\u4ee5\u4e0b\u4e09\u79cd\u53ef\u80fd\uff1a
\u5e26 * \u53f7\u8868\u793a\u5728\u5b9e\u9645\u573a\u666f\u4e2d\uff0c\u5f88\u96be\u8fbe\u5230\u3002
"},{"location":"user-guide/zh/cap/idempotence/#at-most-once","title":"At Most Once","text":"\u6700\u591a\u4e00\u6b21\u4ea4\u4ed8\u4fdd\u8bc1\uff0c\u6db5\u76d6\u4e86\u4fdd\u8bc1\u4e00\u6b21\u6216\u6839\u672c\u4e0d\u63a5\u6536\u6240\u6709\u6d88\u606f\u7684\u60c5\u51b5\u3002
\u8fd9\u79cd\u7c7b\u578b\u7684\u4f20\u9012\u4fdd\u8bc1\u53ef\u80fd\u6765\u81ea\u4f60\u7684\u6d88\u606f\u7cfb\u7edf\uff0c\u4f60\u7684\u4ee3\u7801\u6309\u4ee5\u4e0b\u987a\u5e8f\u6267\u884c\u5176\u64cd\u4f5c\uff1a
1. \u4ece\u961f\u5217\u79fb\u9664\u6d88\u606f\n2. \u5f00\u59cb\u4e00\u4e2a\u5de5\u4f5c\u4e8b\u52a1\n3. \u5904\u7406\u6d88\u606f ( \u4f60\u7684\u4ee3\u7801 )\n4. \u662f\u5426\u6210\u529f ?\n Yes:\n 1. \u63d0\u4ea4\u5de5\u4f5c\u4e8b\u52a1\n No: \n 1. \u56de\u6eda\u5de5\u4f5c\u4e8b\u52a1\n 2. \u5c06\u6d88\u606f\u53d1\u56de\u5230\u961f\u5217\u3002\n
\u6b63\u5e38\u60c5\u51b5\u4e0b\uff0c\u4ed6\u4eec\u5de5\u4f5c\u7684\u5f88\u597d\uff0c\u5de5\u4f5c\u4e8b\u52a1\u5c06\u88ab\u63d0\u4ea4\u3002
\u7136\u800c\uff0c\u6709\u4e9b\u65f6\u5019\u5e76\u4e0d\u80fd\u603b\u662f\u6210\u529f\uff0c\u6bd4\u5982\u5728 1 \u4e4b\u540e\u51fa\u73b0\u5f02\u5e38\uff0c\u6216\u8005\u662f\u4f60\u5728\u5c06\u6d88\u606f\u653e\u56de\u5230\u961f\u5217\u4e2d\u51fa\u73b0\u7f51\u7edc\u95ee\u9898\u7531\u6216\u8005\u5b95\u673a\u91cd\u542f\u7b49\u60c5\u51b5\u3002
\u4f7f\u7528\u8fd9\u4e2a\u534f\u8bae\uff0c\u4f60\u5c06\u5192\u7740\u4e22\u5931\u6d88\u606f\u7684\u98ce\u9669\uff0c\u5982\u679c\u53ef\u4ee5\u63a5\u53d7\uff0c\u90a3\u5c31\u6ca1\u6709\u5173\u7cfb\u3002
"},{"location":"user-guide/zh/cap/idempotence/#at-least-once","title":"At Least Once","text":"\u8fd9\u4e2a\u4ea4\u4ed8\u4fdd\u8bc1\u5305\u542b\u4f60\u6536\u5230\u81f3\u5c11\u4e00\u6b21\u7684\u6d88\u606f\uff0c\u5f53\u51fa\u73b0\u6545\u969c\u65f6\uff0c\u53ef\u80fd\u4f1a\u6536\u5230\u591a\u6b21\u6d88\u606f\u3002
\u5b83\u9700\u8981\u7a0d\u5fae\u6539\u53d8\u6211\u4eec\u6267\u884c\u6b65\u9aa4\u7684\u987a\u5e8f\uff0c\u5b83\u8981\u6c42\u6d88\u606f\u961f\u5217\u7cfb\u7edf\u652f\u6301\u4e8b\u52a1\u6216ACK\u673a\u5236\uff0c\u6bd4\u5982\u4f20\u7edf\u7684 begin-commit-rollback \u534f\u8bae\uff08MSMQ\u662f\u8fd9\u6837\uff09\uff0c\u6216\u8005\u662f receive-ack-nack \u534f\u8bae\uff08RabbitMQ\uff0cAzure Service Bus\u7b49\u662f\u8fd9\u6837\u7684\uff09\u3002
\u5927\u81f4\u6b65\u9aa4\u5982\u4e0b:
1. \u62a2\u5360\u961f\u5217\u4e2d\u7684\u6d88\u606f\u3002\n2. \u5f00\u59cb\u4e00\u4e2a\u5de5\u4f5c\u4e8b\u52a1\n3. \u5904\u7406\u6d88\u606f ( \u4f60\u7684\u4ee3\u7801 )\n4. \u662f\u5426\u6210\u529f ?\n Yes: \n 1. \u63d0\u4ea4\u5de5\u4f5c\u4e8b\u52a1\n 2. \u4ece\u961f\u5217\u5220\u9664\u6d88\u606f\n No: \n 1. \u56de\u6eda\u5de5\u4f5c\u4e8b\u52a1\n 2. \u4ece\u961f\u5217\u91ca\u653e\u62a2\u5360\u7684\u6d88\u606f\n
\u5f53\u51fa\u73b0\u5931\u8d25\u6216\u8005\u62a2\u5360\u6d88\u606f\u8d85\u65f6\u7684\u65f6\u5019\uff0c\u6211\u4eec\u603b\u662f\u80fd\u591f\u518d\u6b21\u63a5\u6536\u5230\u6d88\u606f\u4ee5\u4fdd\u8bc1\u6211\u4eec\u5de5\u4f5c\u4e8b\u52a1\u63d0\u4ea4\u6210\u529f\u3002
"},{"location":"user-guide/zh/cap/idempotence/#_3","title":"\u4ec0\u4e48\u662f \u201c\u5de5\u4f5c\u4e8b\u52a1\u201d ?","text":"\u4e0a\u9762\u6240\u8bf4\u7684\u201c\u5de5\u4f5c\u4e8b\u52a1\u201d\u5e76\u4e0d\u662f\u7279\u6307\u5173\u7cfb\u578b\u6570\u636e\u5e93\u4e2d\u7684\u4e8b\u52a1\uff0c\u8fd9\u91cc\u7684\u5de5\u4f5c\u4e8b\u52a1\u662f\u4e00\u4e2a\u6982\u5ff5\uff0c\u4e5f\u5c31\u662f\u8bf4\u6267\u884c\u4ee3\u7801\u7684\u539f\u5b50\u6027\u3002
\u6bd4\u5982\u5b83\u53ef\u4ee5\u662f\u4f20\u7edf\u7684RDMS\u4e8b\u52a1\uff0c\u4e5f\u6216\u8005\u662f MongoDB \u4e8b\u52a1\u6216\u8005\u662f\u4e00\u4e2a\u4ea4\u6613\u7b49\u3002
\u5728\u8fd9\u91cc\u5b83\u4ee3\u8868\u4e00\u4e2a\u6267\u884c\u5355\u5143\uff0c\u8fd9\u4e2a\u6267\u884c\u5355\u5143\u662f\u4e00\u4e2a\u6982\u5ff5\u6027\u7684\u4e8b\u5b9e\u4ee5\u652f\u6301\u524d\u9762\u63d0\u5230\u7684\u4ec5\u4ea4\u4ed8\u4e00\u6b21\u7684\u8fd9\u79cd\u95ee\u9898\u3002
\u901a\u5e38\uff0c\u4e0d\u53ef\u80fd\u505a\u5230\u6d88\u606f\u7684\u4e8b\u52a1\u548c\u5de5\u4f5c\u4e8b\u52a1\u6765\u5f62\u6210\u539f\u5b50\u6027\u8fdb\u884c\u63d0\u4ea4\u6216\u8005\u56de\u6eda\u3002
"},{"location":"user-guide/zh/cap/idempotence/#cap","title":"CAP \u4e2d\u7684\u5e42\u7b49\u6027","text":"\u5728CAP\u4e2d\uff0c\u6211\u4eec\u91c7\u7528\u7684\u4ea4\u4ed8\u4fdd\u8bc1\u4e3a At Least Once\u3002
\u7531\u4e8e\u6211\u4eec\u5177\u6709\u4e34\u65f6\u5b58\u50a8\u4ecb\u8d28\uff08\u6570\u636e\u5e93\u8868\uff09\uff0c\u4e5f\u8bb8\u53ef\u4ee5\u505a\u5230 At Most Once, \u4f46\u662f\u4e3a\u4e86\u4e25\u683c\u4fdd\u8bc1\u6d88\u606f\u4e0d\u4f1a\u4e22\u5931\uff0c\u6211\u4eec\u6ca1\u6709\u63d0\u4f9b\u76f8\u5173\u529f\u80fd\u6216\u914d\u7f6e\u3002
"},{"location":"user-guide/zh/cap/idempotence/#_4","title":"\u4e3a\u4ec0\u4e48\u6ca1\u6709\u5b9e\u73b0\u5e42\u7b49\uff1f","text":"1\u3001\u6d88\u606f\u5199\u5165\u6210\u529f\u4e86\uff0c\u4f46\u662f\u6b64\u65f6\u6267\u884cConsumer\u65b9\u6cd5\u5931\u8d25\u4e86
\u6267\u884cConsumer\u65b9\u6cd5\u5931\u8d25\u7684\u539f\u56e0\u6709\u975e\u5e38\u591a\uff0c\u6211\u5982\u679c\u4e0d\u77e5\u9053\u5177\u4f53\u7684\u573a\u666f\u76f2\u76ee\u8fdb\u884c\u91cd\u8bd5\u6216\u8005\u4e0d\u8fdb\u884c\u91cd\u8bd5\u90fd\u662f\u4e0d\u6b63\u786e\u7684\u9009\u62e9\u3002 \u4e3e\u4e2a\u4f8b\u5b50\uff1a\u5047\u5982\u6d88\u8d39\u8005\u4e3a\u6263\u6b3e\u670d\u52a1\uff0c\u5982\u679c\u662f\u6267\u884c\u6263\u6b3e\u6210\u529f\u4e86\uff0c\u4f46\u662f\u5728\u5199\u6263\u6b3e\u65e5\u5fd7\u7684\u65f6\u5019\u5931\u8d25\u4e86\uff0c\u6b64\u65f6CAP\u4f1a\u5224\u65ad\u4e3a\u6d88\u8d39\u8005\u6267\u884c\u5931\u8d25\uff0c\u8fdb\u884c\u91cd\u8bd5\u3002\u5982\u679c\u5ba2\u6237\u7aef\u81ea\u5df1\u6ca1\u6709\u4fdd\u8bc1\u5e42\u7b49\u6027\uff0c\u6846\u67b6\u5bf9\u5176\u8fdb\u884c\u91cd\u8bd5\uff0c\u8fd9\u91cc\u52bf\u5fc5\u4f1a\u9020\u6210\u591a\u6b21\u6263\u6b3e\u51fa\u73b0\u4e25\u91cd\u540e\u679c\u3002
2\u3001\u6267\u884cConsumer\u65b9\u6cd5\u6210\u529f\u4e86\uff0c\u4f46\u662f\u53c8\u6536\u5230\u4e86\u540c\u6837\u7684\u6d88\u606f
\u6b64\u5904\u573a\u666f\u4e5f\u662f\u53ef\u80fd\u5b58\u5728\u7684\uff0c\u5047\u5982\u5f00\u59cb\u7684\u65f6\u5019Consumer\u5df2\u7ecf\u6267\u884c\u6210\u529f\u4e86\uff0c\u4f46\u662f\u7531\u4e8e\u67d0\u79cd\u539f\u56e0\u5982 Broker \u5b95\u673a\u6062\u590d\u7b49\uff0c\u53c8\u6536\u5230\u4e86\u76f8\u540c\u7684\u6d88\u606f\uff0cCAP \u5728\u6536\u5230Broker\u6d88\u606f\u540e\u4f1a\u8ba4\u4e3a\u8fd9\u4e2a\u662f\u4e00\u4e2a\u65b0\u7684\u6d88\u606f\uff0c\u4f1a\u5bf9 Consumer\u518d\u6b21\u6267\u884c\uff0c\u7531\u4e8e\u662f\u65b0\u6d88\u606f\uff0c\u6b64\u65f6 CAP \u4e5f\u662f\u65e0\u6cd5\u505a\u5230\u5e42\u7b49\u7684\u3002
3\u3001\u76ee\u524d\u7684\u6570\u636e\u5b58\u50a8\u6a21\u5f0f\u65e0\u6cd5\u505a\u5230\u5e42\u7b49
\u7531\u4e8eCAP\u5b58\u6d88\u606f\u7684\u8868\u5bf9\u4e8e\u6210\u529f\u6d88\u8d39\u7684\u6d88\u606f\u4f1a\u4e8e1\u4e2a\u5c0f\u65f6\u540e\u5220\u9664\uff0c\u6240\u4ee5\u5982\u679c\u5bf9\u4e8e\u4e00\u4e9b\u5386\u53f2\u6027\u6d88\u606f\u65e0\u6cd5\u505a\u5230\u5e42\u7b49\u64cd\u4f5c\u3002 \u5386\u53f2\u6027\u6307\u7684\u662f\uff0c\u5047\u5982 Broker\u7531\u4e8e\u67d0\u79cd\u539f\u56e0\u7ef4\u62a4\u4e86\u6216\u8005\u662f\u4eba\u5de5\u5904\u7406\u7684\u4e00\u4e9b\u6d88\u606f\u3002
4\u3001\u4e1a\u754c\u505a\u6cd5
\u8bb8\u591a\u57fa\u4e8e\u4e8b\u4ef6\u9a71\u52a8\u7684\u6846\u67b6\u90fd\u662f\u8981\u6c42 \u7528\u6237 \u6765\u4fdd\u8bc1\u5e42\u7b49\u6027\u64cd\u4f5c\u7684\uff0c\u6bd4\u5982 ENode, RocketMQ \u7b49\u7b49...
\u4ece\u5b9e\u73b0\u7684\u89d2\u5ea6\u6765\u8bf4\uff0cCAP\u53ef\u4ee5\u505a\u4e00\u4e9b\u6bd4\u8f83\u4e0d\u4e25\u683c\u7684\u5e42\u7b49\uff0c\u4f46\u662f\u4e25\u683c\u7684\u5e42\u7b49\u65e0\u6cd5\u505a\u5230\u7684\u3002
"},{"location":"user-guide/zh/cap/idempotence/#_5","title":"\u4ee5\u81ea\u7136\u7684\u65b9\u5f0f\u5904\u7406\u5e42\u7b49\u6d88\u606f","text":"\u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u4fdd\u8bc1\u6d88\u606f\u88ab\u6267\u884c\u591a\u6b21\u800c\u4e0d\u4f1a\u4ea7\u751f\u610f\u5916\u7ed3\u679c\u662f\u5f88\u81ea\u7136\u7684\u4e00\u79cd\u65b9\u5f0f\u662f\u91c7\u7528\u64cd\u4f5c\u5bf9\u8c61\u81ea\u5e26\u7684\u4e00\u4e9b\u5e42\u7b49\u529f\u80fd\u3002\u6bd4\u5982\uff1a
\u6570\u636e\u5e93\u63d0\u4f9b\u7684 INSERT ON DUPLICATE KEY UPDATE
\u6216\u8005\u662f\u91c7\u53d6\u7c7b\u578b\u7684\u7a0b\u5e8f\u5224\u65ad\u884c\u4e3a\u3002
\u53e6\u5916\u4e00\u79cd\u5904\u7406\u5e42\u7b49\u6027\u7684\u65b9\u5f0f\u5c31\u662f\u5728\u6d88\u606f\u4f20\u9012\u7684\u8fc7\u7a0b\u4e2d\u4f20\u9012ID\uff0c\u7136\u540e\u7531\u5355\u72ec\u7684\u6d88\u606f\u8ddf\u8e2a\u5668\u6765\u5904\u7406\u3002
\u6bd4\u5982\u4f60\u4f7f\u7528\u5177\u6709\u4e8b\u52a1\u6570\u636e\u5b58\u50a8\u7684 IMessageTracker \u6765\u8ddf\u8e2a\u6d88\u606fID\uff0c\u4f60\u7684\u4ee3\u7801\u53ef\u80fd\u770b\u8d77\u6765\u50cf\u8fd9\u6837\uff1a
readonly IMessageTracker _messageTracker;\n\npublic SomeMessageHandler(IMessageTracker messageTracker)\n{\n_messageTracker = messageTracker;\n}\n\n[CapSubscribe]\npublic async Task Handle(SomeMessage message) {\nif (await _messageTracker.HasProcessed(message.Id))\n{\nreturn;\n}\n\n// do the work here\n// ...\n\n// remember that this message has been processed\nawait _messageTracker.MarkAsProcessed(messageId);\n}\n
\u81f3\u4e8e IMessageTracker
\u7684\u5b9e\u73b0\uff0c\u53ef\u4ee5\u4f7f\u7528\u8bf8\u5982Redis\u6216\u8005\u6570\u636e\u5e93\u7b49\u5b58\u50a8\u6d88\u606fId\u548c\u5bf9\u5e94\u7684\u5904\u7406\u72b6\u6001\u3002
\u4f7f\u7528 ICapPublisher
\u63a5\u53e3\u53d1\u9001\u51fa\u53bb\u7684\u6570\u636e\u79f0\u4e4b\u4e3a Message (\u6d88\u606f
)\u3002
\u4f60\u53ef\u4ee5\u9605\u8bfb quick-start \u6765\u5b66\u4e60\u5982\u4f55\u53d1\u9001\u548c\u5904\u7406\u6d88\u606f\u3002
\u6d88\u8d39\u8005\u4e2d\u4f7f\u7528 HTTPClient \u5f15\u53d1\u7684 TimeoutException
\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5982\u679c\u6d88\u8d39\u8005\u629b\u51fa OperationCanceledException
\uff08\u5305\u62ec TaskCanceledException
\uff09\uff0c\u6211\u4eec\u4f1a\u8ba4\u4e3a\u8fd9\u662f\u7528\u6237\u7684\u6b63\u5e38\u884c\u4e3a\u800c\u5bf9\u5f02\u5e38\u8fdb\u884c\u5ffd\u7565\u3002\u5982\u679c\u4f60\u5728\u6d88\u8d39\u8005\u65b9\u6cd5\u4e2d\u4f7f\u7528 HTTPClient \u5e76\u4e14\u8fdb\u884c\u4e86\u914d\u7f6e\u4e86Timeout\u914d\u7f6e\uff0c\u7531\u4e8eHTTP Client\u7684\u8bbe\u8ba1\u95ee\u9898\uff0c\u4f60\u53ef\u80fd\u9700\u8981\u5355\u72ec\u5bf9\u5f02\u5e38\u8fdb\u884c\u5904\u7406\u5e76\u91cd\u65b0\u5f15\u53d1\u975eOperationCanceledException\uff0c\u53c2\u8003 #1368
Compensating transaction
\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u6d88\u8d39\u8005\u9700\u8981\u8fd4\u56de\u503c\u4ee5\u544a\u8bc9\u53d1\u5e03\u8005\u6267\u884c\u7ed3\u679c\uff0c\u4ee5\u4fbf\u4e8e\u53d1\u5e03\u8005\u5b9e\u65bd\u4e00\u4e9b\u52a8\u4f5c\uff0c\u901a\u5e38\u60c5\u51b5\u4e0b\u8fd9\u5c5e\u4e8e\u8865\u507f\u8303\u56f4\u3002
\u4f60\u53ef\u4ee5\u5728\u6d88\u8d39\u8005\u6267\u884c\u7684\u4ee3\u7801\u4e2d\u901a\u8fc7\u91cd\u65b0\u53d1\u5e03\u4e00\u4e2a\u65b0\u6d88\u606f\u6765\u901a\u77e5\u4e0a\u6e38\uff0cCAP \u63d0\u4f9b\u4e86\u4e00\u79cd\u7b80\u5355\u7684\u65b9\u5f0f\u6765\u505a\u5230\u8fd9\u4e00\u70b9\u3002 \u4f60\u53ef\u4ee5\u5728\u53d1\u9001\u7684\u65f6\u5019\u6307\u5b9a callbackName
\u6765\u5f97\u5230\u6d88\u8d39\u8005\u7684\u6267\u884c\u7ed3\u679c\uff0c\u901a\u5e38\u8fd9\u4ec5\u9002\u7528\u4e8e\u70b9\u5bf9\u70b9\u7684\u6d88\u8d39\u3002\u4ee5\u4e0b\u662f\u4e00\u4e2a\u793a\u4f8b\u3002
\u4f8b\u5982\uff0c\u5728\u4e00\u4e2a\u7535\u5546\u7a0b\u5e8f\u4e2d\uff0c\u8ba2\u5355\u521d\u59cb\u72b6\u6001\u4e3a pending\uff0c\u5f53\u5546\u54c1\u6570\u91cf\u6210\u529f\u6263\u9664\u65f6\u5c06\u72b6\u6001\u6807\u8bb0\u4e3a succeeded \uff0c\u5426\u5219\u4e3a failed\u3002
// ============= Publisher =================\n\n_capBus.Publish(\"place.order.qty.deducted\", contentObj: new { OrderId = 1234, ProductId = 23255, Qty = 1 }, callbackName: \"place.order.mark.status\"); // publisher using `callbackName` to subscribe consumer result\n\n[CapSubscribe(\"place.order.mark.status\")]\npublic void MarkOrderStatus(JsonElement param)\n{\nvar orderId = param.GetProperty(\"OrderId\").GetInt32();\nvar isSuccess = param.GetProperty(\"IsSuccess\").GetBoolean();\n\nif(isSuccess){\n// mark order status to succeeded\n}\nelse{\n// mark order status to failed\n}\n}\n\n// ============= Consumer ===================\n\n[CapSubscribe(\"place.order.qty.deducted\")]\npublic object DeductProductQty(JsonElement param)\n{\nvar orderId = param.GetProperty(\"OrderId\").GetInt32();\nvar productId = param.GetProperty(\"ProductId\").GetInt32();\nvar qty = param.GetProperty(\"Qty\").GetInt32();\n\n//business logic \n\nreturn new { OrderId = orderId, IsSuccess = true };\n}\n
"},{"location":"user-guide/zh/cap/messaging/#_4","title":"\u5f02\u6784\u7cfb\u7edf\u96c6\u6210","text":"\u5728 3.0+ \u7248\u672c\u4e2d\uff0c\u6211\u4eec\u5bf9\u6d88\u606f\u7ed3\u6784\u8fdb\u884c\u4e86\u91cd\u6784\uff0c\u6211\u4eec\u5229\u7528\u4e86\u6d88\u606f\u961f\u5217\u4e2d\u6d88\u606f\u534f\u8bae\u4e2d\u7684 Header \u6765\u4f20\u8f93\u4e00\u4e9b\u989d\u5916\u4fe1\u606f\uff0c\u4ee5\u4fbf\u4e8e\u5728 Body \u4e2d\u6211\u4eec\u53ef\u4ee5\u505a\u5230\u4e0d\u9700\u8981\u4fee\u6539\u6216\u5305\u88c5\u4f7f\u7528\u8005\u7684\u539f\u59cb\u6d88\u606f\u6570\u636e\u683c\u5f0f\u548c\u5185\u5bb9\u8fdb\u884c\u53d1\u9001\u3002
\u8fd9\u6837\u7684\u505a\u6cd5\u662f\u5408\u7406\u7684\uff0c\u5b83\u6709\u52a9\u4e8e\u5728\u5f02\u6784\u7cfb\u7edf\u4e2d\u8fdb\u884c\u66f4\u597d\u7684\u96c6\u6210\uff0c\u76f8\u5bf9\u4e8e\u4ee5\u524d\u7684\u7248\u672c\u4f7f\u7528\u8005\u4e0d\u9700\u8981\u77e5\u9053CAP\u5185\u90e8\u4f7f\u7528\u7684\u6d88\u606f\u7ed3\u6784\u5c31\u53ef\u4ee5\u5b8c\u6210\u96c6\u6210\u5de5\u4f5c\u3002
\u73b0\u5728\u6211\u4eec\u5c06\u6d88\u606f\u5212\u5206\u4e3a Header \u548c Body \u6765\u8fdb\u884c\u4f20\u8f93\u3002
Body \u4e2d\u7684\u6570\u636e\u4e3a\u7528\u6237\u53d1\u9001\u7684\u539f\u59cb\u6d88\u606f\u5185\u5bb9\uff0c\u4e5f\u5c31\u662f\u8c03\u7528 Publish \u65b9\u6cd5\u53d1\u9001\u7684\u5185\u5bb9\uff0c\u6211\u4eec\u4e0d\u8fdb\u884c\u4efb\u4f55\u5305\u88c5\u4ec5\u4ec5\u662f\u5e8f\u5217\u5316\u540e\u4f20\u9012\u5230\u6d88\u606f\u961f\u5217\u3002
\u5728 Header \u4e2d\uff0c\u6211\u4eec\u9700\u8981\u4f20\u9012\u4e00\u4e9b\u989d\u5916\u4fe1\u606f\u4ee5\u4fbf\u4e8eCAP\u5728\u6536\u5230\u6d88\u606f\u65f6\u80fd\u591f\u63d0\u53d6\u5230\u5173\u952e\u7279\u5f81\u8fdb\u884c\u64cd\u4f5c\u3002
\u4ee5\u4e0b\u662f\u5728\u5f02\u6784\u7cfb\u7edf\u4e2d\uff0c\u9700\u8981\u5728\u53d1\u6d88\u606f\u7684\u65f6\u5019\u5411\u6d88\u606f\u7684Header \u4e2d\u5199\u5165\u7684\u5185\u5bb9\uff1a
\u952e \u7c7b\u578b \u8bf4\u660e cap-msg-id string \u6d88\u606fId\uff0c \u7531\u96ea\u82b1\u7b97\u6cd5\u751f\u6210\uff0c\u4e5f\u53ef\u4ee5\u662f guid cap-msg-name string \u6d88\u606f\u540d\u79f0\uff0c\u5373 Topic \u540d\u5b57 cap-msg-type string \u6d88\u606f\u7684\u7c7b\u578b, \u5373 typeof(T).FullName (\u975e\u5fc5\u987b) cap-senttime stringg \u53d1\u9001\u7684\u65f6\u95f4 (\u975e\u5fc5\u987b)\u4ee5 Java \u7cfb\u7edf\u53d1\u9001 RabbitMQ \u4e3a\u4f8b\uff1a
Map<String, Object> headers = new HashMap<String, Object>();\nheaders.put(\"cap-msg-id\", UUID.randomUUID().toString());\nheaders.put(\"cap-msg-name\", routingKey);\n\nchannel.basicPublish(exchangeName, routingKey,\nnew AMQP.BasicProperties.Builder()\n.headers(headers)\n.build(),\nmessageBodyBytes);\n// messageBodyBytes = \"\u53d1\u9001\u7684json\".getBytes(Charset.forName(\"UTF-8\"))\n// \u6ce8\u610f messageBody \u9ed8\u8ba4\u4e3a json \u7684 byte[]\uff0c\u5982\u679c\u91c7\u7528\u5176\u4ed6\u7cfb\u5217\u5316\uff0c\u9700\u8981\u5728CAP\u4fa7\u81ea\u5b9a\u4e49\u53cd\u5e8f\u5217\u5316\u5668\n
"},{"location":"user-guide/zh/cap/messaging/#_5","title":"\u6d88\u606f\u8c03\u5ea6","text":"CAP \u63a5\u6536\u5230\u6d88\u606f\u4e4b\u540e\u4f1a\u5c06\u6d88\u606f\u53d1\u9001\u5230 Transport, \u7531 Transport \u8fdb\u884c\u8fd0\u8f93\u3002
\u5f53\u4f60\u4f7f\u7528 ICapPublisher
\u63a5\u53e3\u53d1\u9001\u65f6\uff0cCAP\u5c06\u4f1a\u5c06\u6d88\u606f\u8c03\u5ea6\u5230\u76f8\u5e94\u7684 Transport\u4e2d\u53bb\uff0c\u76ee\u524d\u8fd8\u4e0d\u652f\u6301\u6279\u91cf\u53d1\u9001\u6d88\u606f\u3002
\u6709\u5173 Transports \u7684\u66f4\u591a\u4fe1\u606f\uff0c\u53ef\u4ee5\u67e5\u770b Transports \u7ae0\u8282\u3002
"},{"location":"user-guide/zh/cap/messaging/#_6","title":"\u6d88\u606f\u5b58\u50a8","text":"CAP \u63a5\u6536\u5230\u6d88\u606f\u4e4b\u540e\u4f1a\u5c06\u6d88\u606f\u8fdb\u884c Persistent\uff08\u6301\u4e45\u5316\uff09\uff0c \u6709\u5173 Persistent \u7684\u66f4\u591a\u4fe1\u606f\uff0c\u53ef\u4ee5\u67e5\u770b Persistent \u7ae0\u8282\u3002
"},{"location":"user-guide/zh/cap/messaging/#_7","title":"\u6d88\u606f\u91cd\u8bd5","text":"\u91cd\u8bd5\u5728\u6574\u4e2aCAP\u67b6\u6784\u8bbe\u8ba1\u4e2d\u5177\u6709\u91cd\u8981\u4f5c\u7528\uff0cCAP \u4e2d\u4f1a\u9488\u5bf9\u53d1\u9001\u5931\u8d25\u6216\u8005\u6267\u884c\u5931\u8d25\u7684\u6d88\u606f\u8fdb\u884c\u91cd\u8bd5\u3002\u5728\u6574\u4e2a CAP \u7684\u8bbe\u8ba1\u8fc7\u7a0b\u4e2d\u6709\u4ee5\u4e0b\u51e0\u5904\u91c7\u7528\u7684\u91cd\u8bd5\u7b56\u7565\u3002
1\u3001 \u53d1\u9001\u91cd\u8bd5
\u5728\u6d88\u606f\u53d1\u9001\u8fc7\u7a0b\u4e2d\uff0c\u5f53\u51fa\u73b0 Broker \u5b95\u673a\u6216\u8005\u8fde\u63a5\u5931\u8d25\u7684\u60c5\u51b5\u4ea6\u6216\u8005\u51fa\u73b0\u5f02\u5e38\u7684\u60c5\u51b5\u4e0b\uff0c\u8fd9\u4e2a\u65f6\u5019 CAP \u4f1a\u5bf9\u53d1\u9001\u7684\u91cd\u8bd5\uff0c\u7b2c\u4e00\u6b21\u91cd\u8bd5\u6b21\u6570\u4e3a 3\uff0c4\u5206\u949f\u540e\u4ee5\u540e\u6bcf\u5206\u949f\u91cd\u8bd5\u4e00\u6b21\uff0c\u8fdb\u884c\u6b21\u6570 +1\uff0c\u5f53\u603b\u6b21\u6570\u8fbe\u523050\u6b21\u540e\uff0cCAP\u5c06\u4e0d\u5bf9\u5176\u8fdb\u884c\u91cd\u8bd5\u3002
\u4f60\u53ef\u4ee5\u5728 CapOptions \u4e2d\u8bbe\u7f6e FailedRetryCount \u6765\u8c03\u6574\u9ed8\u8ba4\u91cd\u8bd5\u7684\u603b\u6b21\u6570\uff0c\u6216\u4f7f\u7528 FailedThresholdCallback \u5728\u8fbe\u5230\u6700\u5927\u91cd\u8bd5\u6b21\u6570\u65f6\u6536\u5230\u901a\u77e5\u3002
\u5f53\u5931\u8d25\u603b\u6b21\u6570\u8fbe\u5230\u9ed8\u8ba4\u5931\u8d25\u603b\u6b21\u6570\u540e\uff0c\u5c31\u4e0d\u4f1a\u8fdb\u884c\u91cd\u8bd5\u4e86\uff0c\u4f60\u53ef\u4ee5\u5728 Dashboard \u4e2d\u67e5\u770b\u6d88\u606f\u5931\u8d25\u7684\u539f\u56e0\uff0c\u7136\u540e\u8fdb\u884c\u4eba\u5de5\u91cd\u8bd5\u5904\u7406\u3002
2\u3001 \u6d88\u8d39\u91cd\u8bd5
\u5f53 Consumer \u63a5\u6536\u5230\u6d88\u606f\u65f6\uff0c\u4f1a\u6267\u884c\u6d88\u8d39\u8005\u65b9\u6cd5\uff0c\u5728\u6267\u884c\u6d88\u8d39\u8005\u65b9\u6cd5\u51fa\u73b0\u5f02\u5e38\u65f6\uff0c\u4f1a\u8fdb\u884c\u91cd\u8bd5\u3002\u8fd9\u4e2a\u91cd\u8bd5\u7b56\u7565\u548c\u4e0a\u9762\u7684 \u53d1\u9001\u91cd\u8bd5 \u662f\u76f8\u540c\u7684\u3002
\u65e0\u8bba\u53d1\u9001\u5931\u8d25\u6216\u8005\u6d88\u8d39\u5931\u8d25\uff0c\u6211\u4eec\u4f1a\u5c06\u5f02\u5e38\u6d88\u606f\u540c\u65f6\u5b58\u50a8\u5230\u6d88\u606f header \u4e2d\u7684 cap-exception \u5b57\u6bb5\u4e2d\uff0c\u4f60\u53ef\u4ee5\u5728\u6570\u636e\u5e93\u8868\u7684 Content \u5b57\u6bb5\u7684json\u4e2d\u627e\u5230\u3002
"},{"location":"user-guide/zh/cap/messaging/#_8","title":"\u6d88\u606f\u6570\u636e\u6e05\u7406","text":"\u6570\u636e\u5e93\u6d88\u606f\u8868\u4e2d\u5177\u6709\u4e00\u4e2a ExpiresAt \u5b57\u6bb5\u8868\u793a\u6d88\u606f\u7684\u8fc7\u671f\u65f6\u95f4\uff0c\u5f53\u6d88\u606f\u53d1\u9001\u6210\u529f\u6216\u8005\u6d88\u8d39\u6210\u529f\u540e\uff0cCAP \u4f1a\u5c06\u6d88\u606f\u72b6\u6001\u4e3a Successed \u7684 ExpiresAt \u8bbe\u7f6e\u4e3a 1\u5929 \u540e\u8fc7\u671f\uff0c\u4f1a\u5c06\u6d88\u606f\u72b6\u6001\u4e3a Failed \u7684 ExpiresAt \u8bbe\u7f6e\u4e3a 15\u5929 \u540e\u8fc7\u671f\uff08\u53ef\u901a\u8fc7 FailedMessageExpiredAfter \u914d\u7f6e)\u3002
CAP \u9ed8\u8ba4\u60c5\u51b5\u4e0b\u4f1a\u6bcf\u9694**5\u5206\u949f**\u5c06\u6d88\u606f\u8868\u7684\u6570\u636e\u8fdb\u884c\u6e05\u7406\u5220\u9664\uff0c\u907f\u514d\u6570\u636e\u91cf\u8fc7\u591a\u5bfc\u81f4\u6027\u80fd\u7684\u964d\u4f4e\u3002\u6e05\u7406\u89c4\u5219\u4e3a ExpiresAt \u4e0d\u4e3a\u7a7a\u5e76\u4e14\u5c0f\u4e8e\u5f53\u524d\u65f6\u95f4\u7684\u6570\u636e\u3002 \u4e5f\u5c31\u662f\u8bf4\u72b6\u6001\u4e3aFailed\u7684\u6d88\u606f\uff08\u6b63\u5e38\u60c5\u51b5\u4ed6\u4eec\u5df2\u7ecf\u88ab\u91cd\u8bd5\u4e86 50 \u6b21\uff09\uff0c\u5982\u679c\u4f6015\u5929\u6ca1\u6709\u4eba\u5de5\u4ecb\u5165\u5904\u7406\uff0c\u540c\u6837\u4f1a\u88ab\u6e05\u7406\u6389\u3002\u4f60\u53ef\u4ee5\u901a\u8fc7 CollectorCleaningInterval \u914d\u7f6e\u9879\u6765\u81ea\u5b9a\u4e49\u95f4\u9694\u65f6\u95f4\u3002
"},{"location":"user-guide/zh/cap/serialization/","title":"\u5e8f\u5217\u5316","text":"CAP \u63d0\u4f9b\u4e86 ISerializer
\u63a5\u53e3\u6765\u652f\u6301\u5bf9\u6d88\u606f\u8fdb\u884c\u5e8f\u5217\u5316\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\u6211\u4eec\u4f7f\u7528 json \u6765\u5bf9\u6d88\u606f\u8fdb\u884c\u5e8f\u5217\u5316\u5904\u7406\u5e76\u5b58\u50a8\u5230\u6570\u636e\u5e93\u4e2d\u3002
public class YourSerializer: ISerializer\n{\nTask<TransportMessage> SerializeAsync(Message message)\n{\n\n}\n\nTask<Message> DeserializeAsync(TransportMessage transportMessage, Type valueType)\n{\n\n}\n}\n
\u7136\u540e\u5c06\u4f60\u7684\u5b9e\u73b0\u6ce8\u518c\u5230\u5bb9\u5668\u4e2d:
//\u6ce8\u518c\u4f60\u7684\u81ea\u5b9a\u4e49\u5b9e\u73b0\nservices.AddSingleton<ISerializer, YourSerializer>();\n\n// ---\nservices.AddCap \n
"},{"location":"user-guide/zh/cap/transactions/","title":"\u4e8b\u52a1","text":""},{"location":"user-guide/zh/cap/transactions/#_2","title":"\u5206\u5e03\u5f0f\u4e8b\u52a1?","text":"CAP \u4e0d\u76f4\u63a5\u63d0\u4f9b\u5f00\u7bb1\u5373\u7528\u7684\u57fa\u4e8e DTC \u6216\u8005 2PC \u7684\u5206\u5e03\u5f0f\u4e8b\u52a1\uff0c\u76f8\u53cd\u6211\u4eec\u63d0\u4f9b\u4e00\u79cd\u53ef\u4ee5\u7528\u4e8e\u89e3\u51b3\u5728\u5206\u5e03\u5f0f\u4e8b\u52a1\u9047\u5230\u7684\u95ee\u9898\u7684\u4e00\u79cd\u89e3\u51b3\u65b9\u6848\u3002
\u5728\u5206\u5e03\u5f0f\u73af\u5883\u4e2d\uff0c\u7531\u4e8e\u6d89\u53ca\u901a\u8baf\u7684\u5f00\u9500\uff0c\u4f7f\u7528\u57fa\u4e8e2PC\u6216DTC\u7684\u5206\u5e03\u5f0f\u4e8b\u52a1\u5c06\u975e\u5e38\u6602\u8d35\uff0c\u5728\u6027\u80fd\u65b9\u9762\u4e5f\u540c\u6837\u5982\u6b64\u3002\u53e6\u5916\u7531\u4e8e\u57fa\u4e8e2PC\u6216DTC\u7684\u5206\u5e03\u5f0f\u4e8b\u52a1\u540c\u6837\u53d7**CAP\u5b9a\u7406**\u7684\u7ea6\u675f\uff0c\u5f53\u53d1\u751f\u7f51\u7edc\u5206\u533a\u65f6\u5b83\u5c06\u4e0d\u5f97\u4e0d\u653e\u5f03\u53ef\u7528\u6027(CAP\u4e2d\u7684A)\u3002
\u9488\u5bf9\u4e8e\u5206\u5e03\u5f0f\u4e8b\u52a1\u7684\u5904\u7406\uff0cCAP \u91c7\u7528\u7684\u662f\u201c\u5f02\u6b65\u786e\u4fdd\u201d\u8fd9\u79cd\u65b9\u6848\u3002
"},{"location":"user-guide/zh/cap/transactions/#_3","title":"\u5f02\u6b65\u786e\u4fdd","text":"\u5f02\u6b65\u786e\u4fdd\u8fd9\u79cd\u65b9\u6848\u53c8\u53eb\u505a\u672c\u5730\u6d88\u606f\u8868\uff0c\u8fd9\u662f\u4e00\u79cd\u7ecf\u5178\u7684\u65b9\u6848\uff0c\u65b9\u6848\u6700\u521d\u6765\u6e90\u4e8e eBay\uff0c\u53c2\u8003\u8d44\u6599\u89c1\u6bb5\u672b\u94fe\u63a5\u3002\u8fd9\u79cd\u65b9\u6848\u76ee\u524d\u4e5f\u662f\u4f01\u4e1a\u4e2d\u4f7f\u7528\u6700\u591a\u7684\u65b9\u6848\u4e4b\u4e00\u3002
\u76f8\u5bf9\u4e8e TCC \u6216\u8005 2PC/3PC \u6765\u8bf4\uff0c\u8fd9\u4e2a\u65b9\u6848\u5bf9\u4e8e\u5206\u5e03\u5f0f\u4e8b\u52a1\u6765\u8bf4\u662f\u6700\u7b80\u5355\u7684\uff0c\u800c\u4e14\u5b83\u662f\u53bb\u4e2d\u5fc3\u5316\u7684\u3002\u5728TCC \u6216\u8005 2PC \u7684\u65b9\u6848\u4e2d\uff0c\u5fc5\u987b\u5177\u6709\u4e8b\u52a1\u534f\u8c03\u5668\u6765\u5904\u7406\u6bcf\u4e2a\u4e0d\u540c\u670d\u52a1\u4e4b\u95f4\u7684\u72b6\u6001\uff0c\u800c\u6b64\u79cd\u65b9\u6848\u4e0d\u9700\u8981\u4e8b\u52a1\u534f\u8c03\u5668\u3002 \u53e6\u5916 2PC/TCC \u8fd9\u79cd\u65b9\u6848\u5982\u679c\u670d\u52a1\u4f9d\u8d56\u8fc7\u591a\uff0c\u4f1a\u5e26\u6765\u7ba1\u7406\u590d\u6742\u6027\u589e\u52a0\u548c\u7a33\u5b9a\u6027\u98ce\u9669\u589e\u5927\u7684\u95ee\u9898\u3002\u8bd5\u60f3\u5982\u679c\u6211\u4eec\u5f3a\u4f9d\u8d56 10 \u4e2a\u670d\u52a1\uff0c9 \u4e2a\u90fd\u6267\u884c\u6210\u529f\u4e86\uff0c\u6700\u540e\u4e00\u4e2a\u6267\u884c\u5931\u8d25\u4e86\uff0c\u90a3\u4e48\u662f\u4e0d\u662f\u524d\u9762 9 \u4e2a\u90fd\u8981\u56de\u6eda\u6389\uff1f\u8fd9\u4e2a\u6210\u672c\u8fd8\u662f\u975e\u5e38\u9ad8\u7684\u3002
\u4f46\u662f\uff0c\u5e76\u4e0d\u662f\u8bf4 2PC \u6216\u8005 TCC \u8fd9\u79cd\u65b9\u6848\u4e0d\u597d\uff0c\u56e0\u4e3a\u6bcf\u4e00\u79cd\u65b9\u6848\u90fd\u6709\u5176\u76f8\u5bf9\u4f18\u52bf\u7684\u4f7f\u7528\u573a\u666f\u548c\u4f18\u7f3a\u70b9\uff0c\u8fd9\u91cc\u5c31\u4e0d\u505a\u8fc7\u591a\u4ecb\u7ecd\u4e86\u3002
\u4e2d\u6587\uff1ahttp://www.cnblogs.com/savorboard/p/base-an-acid-alternative.html \u82f1\u6587\uff1ahttp://queue.acm.org/detail.cfm?id=1394128
"},{"location":"user-guide/zh/getting-started/contributing/","title":"\u8d21\u732e","text":"\u8d21\u732e\u6700\u7b80\u5355\u7684\u65b9\u5f0f\u4e4b\u4e00\u5c31\u662f\u53c2\u4e0e\u8ba8\u8bba\u548cissue\u8ba8\u8bba\u3002
\u5982\u679c\u60a8\u6709\u4efb\u4f55\u7591\u95ee\u6216\u95ee\u9898\uff0c\u8bf7\u5728CAP\u4ed3\u5e93\u4e2d\u62a5\u544a\uff1a
Report Issue Active Issues
"},{"location":"user-guide/zh/getting-started/contributing/#_2","title":"\u63d0\u4ea4\u66f4\u6539","text":"\u60a8\u8fd8\u53ef\u4ee5\u901a\u8fc7\u63d0\u4ea4\u4ee3\u7801\u66f4\u6539PR\u6765\u505a\u51fa\u8d21\u732e\u3002
Pull requests \u53ef\u8ba9\u60a8\u544a\u8bc9\u5176\u4ed6\u4eba\u5df2\u63a8\u9001\u5230GitHub\u4e0a\u5b58\u50a8\u5e93\u7684\u66f4\u6539\u3002 \u6253\u5f00 Pull requests \u540e\uff0c\u60a8\u53ef\u4ee5\u4e0e\u534f\u4f5c\u8005\u8ba8\u8bba\u548c\u5ba1\u67e5\u505a\u51fa\u7684\u66f4\u6539\uff0c\u5e76\u5728\u66f4\u6539\u5408\u5e76\u5230\u5b58\u50a8\u5e93\u4e4b\u524d\u6dfb\u52a0\u540e\u7eed\u63d0\u4ea4\u3002
"},{"location":"user-guide/zh/getting-started/contributing/#_3","title":"\u5176\u4ed6\u8d44\u6e90","text":"issue \u548c pull requests
\u4f7f\u7528\u641c\u7d22\u8fc7\u6ee4 issue \u548c pull requests
CAP \u662f\u4e00\u4e2aEventBus\uff0c\u540c\u65f6\u4e5f\u662f\u4e00\u4e2a\u5728\u5fae\u670d\u52a1\u6216\u8005SOA\u7cfb\u7edf\u4e2d\u89e3\u51b3\u5206\u5e03\u5f0f\u4e8b\u52a1\u95ee\u9898\u7684\u4e00\u4e2a\u6846\u67b6\u3002\u5b83\u6709\u52a9\u4e8e\u521b\u5efa\u53ef\u6269\u5c55\uff0c\u53ef\u9760\u5e76\u4e14\u6613\u4e8e\u66f4\u6539\u7684\u5fae\u670d\u52a1\u7cfb\u7edf\u3002
\u5728\u5fae\u8f6f\u7684 eShop \u5fae\u670d\u52a1\u793a\u4f8b\u9879\u76ee\u4e2d\uff0c\u63a8\u8350\u4f7f\u7528 CAP \u4f5c\u4e3a\u751f\u4ea7\u73af\u5883\u53ef\u7528\u7684 EventBus\u3002
\u4ec0\u4e48\u662f EventBus\uff1f
\u4e8b\u4ef6\u603b\u7ebf\u662f\u4e00\u79cd\u673a\u5236\uff0c\u5b83\u5141\u8bb8\u4e0d\u540c\u7684\u7ec4\u4ef6\u5f7c\u6b64\u901a\u4fe1\u800c\u4e0d\u5f7c\u6b64\u4e86\u89e3\u3002 \u7ec4\u4ef6\u53ef\u4ee5\u5c06\u4e8b\u4ef6\u53d1\u9001\u5230Eventbus\uff0c\u800c\u65e0\u9700\u77e5\u9053\u662f\u8c01\u6765\u63a5\u542c\u6216\u6709\u591a\u5c11\u5176\u4ed6\u4eba\u6765\u63a5\u542c\u3002 \u7ec4\u4ef6\u4e5f\u53ef\u4ee5\u4fa6\u542cEventbus\u4e0a\u7684\u4e8b\u4ef6\uff0c\u800c\u65e0\u9700\u77e5\u9053\u8c01\u53d1\u9001\u4e86\u4e8b\u4ef6\u3002 \u8fd9\u6837\uff0c\u7ec4\u4ef6\u53ef\u4ee5\u76f8\u4e92\u901a\u4fe1\u800c\u65e0\u9700\u76f8\u4e92\u4f9d\u8d56\u3002 \u540c\u6837\uff0c\u5f88\u5bb9\u6613\u66ff\u6362\u4e00\u4e2a\u7ec4\u4ef6\u3002 \u53ea\u8981\u65b0\u7ec4\u4ef6\u4e86\u89e3\u6b63\u5728\u53d1\u9001\u548c\u63a5\u6536\u7684\u4e8b\u4ef6\uff0c\u5176\u4ed6\u7ec4\u4ef6\u5c31\u6c38\u8fdc\u4e0d\u4f1a\u77e5\u9053.
\u76f8\u5bf9\u4e8e\u5176\u4ed6\u7684 Service Bus \u6216\u8005 Event Bus\uff0c CAP \u62e5\u6709\u81ea\u5df1\u7684\u7279\u8272\uff0c\u5b83\u4e0d\u8981\u6c42\u4f7f\u7528\u8005\u53d1\u9001\u6d88\u606f\u6216\u8005\u5904\u7406\u6d88\u606f\u7684\u65f6\u5019\u5b9e\u73b0\u6216\u8005\u7ee7\u627f\u4efb\u4f55\u63a5\u53e3\uff0c\u62e5\u6709\u975e\u5e38\u9ad8\u7684\u7075\u6d3b\u6027\u3002\u6211\u4eec\u4e00\u76f4\u575a\u4fe1\u7ea6\u5b9a\u5927\u4e8e\u914d\u7f6e\uff0c\u6240\u4ee5CAP\u4f7f\u7528\u8d77\u6765\u975e\u5e38\u7b80\u5355\uff0c\u5bf9\u4e8e\u65b0\u624b\u975e\u5e38\u53cb\u597d\uff0c\u5e76\u4e14\u62e5\u6709\u8f7b\u91cf\u7ea7\u3002
CAP \u91c7\u7528\u6a21\u5757\u5316\u8bbe\u8ba1\uff0c\u5177\u6709\u9ad8\u5ea6\u7684\u53ef\u6269\u5c55\u6027\u3002\u4f60\u6709\u8bb8\u591a\u9009\u9879\u53ef\u4ee5\u9009\u62e9\uff0c\u5305\u62ec\u6d88\u606f\u961f\u5217\uff0c\u5b58\u50a8\uff0c\u5e8f\u5217\u5316\u65b9\u5f0f\u7b49\uff0c\u7cfb\u7edf\u7684\u8bb8\u591a\u5143\u7d20\u5185\u5bb9\u53ef\u4ee5\u66ff\u6362\u4e3a\u81ea\u5b9a\u4e49\u5b9e\u73b0\u3002
"},{"location":"user-guide/zh/getting-started/introduction/#_2","title":"\u76f8\u5173\u89c6\u9891","text":"Video: bilibili \u6559\u7a0b
Video: Youtube \u6559\u7a0b
Video: \u817e\u8baf\u89c6\u9891\u6559\u7a0b
"},{"location":"user-guide/zh/getting-started/introduction/#_3","title":"\u76f8\u5173\u6587\u7ae0","text":"Article: CAP \u4ecb\u7ecd\u53ca\u4f7f\u7528
Article: CAP 7.0 \u7248\u672c\u4e2d\u7684\u65b0\u7279\u6027
Article: CAP 6.0 \u7248\u672c\u4e2d\u7684\u65b0\u7279\u6027
Article: CAP 5.0 \u7248\u672c\u4e2d\u7684\u65b0\u7279\u6027
Article: CAP 3.0 \u7248\u672c\u4e2d\u7684\u65b0\u7279\u6027
Article: CAP 2.6 \u7248\u672c\u4e2d\u7684\u65b0\u7279\u6027
Article: CAP 2.5 \u7248\u672c\u4e2d\u7684\u65b0\u7279\u6027
Article: CAP 2.4 \u7248\u672c\u4e2d\u7684\u65b0\u7279\u6027
Article: CAP 2.3 \u7248\u672c\u4e2d\u7684\u65b0\u7279\u6027\u7528
Article: .NET Core Community \u9996\u4e2a\u5343\u661f\u9879\u76ee\u8bde\u751f\uff1aCAP
"},{"location":"user-guide/zh/getting-started/quick-start/","title":"\u5feb\u901f\u5f00\u59cb","text":"\u4e86\u89e3\u5982\u4f55\u4f7f\u7528 CAP \u6784\u5efa\u5fae\u670d\u52a1\u4e8b\u4ef6\u603b\u7ebf\u67b6\u6784\uff0c\u5b83\u6bd4\u76f4\u63a5\u96c6\u6210\u6d88\u606f\u961f\u5217\u63d0\u4f9b\u4e86\u54ea\u4e9b\u4f18\u52bf\uff0c\u5b83\u63d0\u4f9b\u4e86\u54ea\u4e9b\u5f00\u7bb1\u5373\u7528\u7684\u529f\u80fd\u3002
"},{"location":"user-guide/zh/getting-started/quick-start/#_2","title":"\u5b89\u88c5","text":"PM> Install-Package DotNetCore.CAP\n
"},{"location":"user-guide/zh/getting-started/quick-start/#aspnet-core","title":"\u5728 Asp.Net Core \u4e2d\u96c6\u6210","text":"\u4ee5\u4fbf\u4e8e\u5feb\u901f\u542f\u52a8\uff0c\u6211\u4eec\u4f7f\u7528\u57fa\u4e8e\u5185\u5b58\u7684\u4e8b\u4ef6\u5b58\u50a8\u548c\u6d88\u606f\u961f\u5217\u3002
PM> Install-Package DotNetCore.CAP.InMemoryStorage\nPM> Install-Package Savorboard.CAP.InMemoryMessageQueue\n
\u5728 Startup.cs
\u4e2d\uff0c\u6dfb\u52a0\u4ee5\u4e0b\u914d\u7f6e\uff1a
public void ConfigureServices(IServiceCollection services)\n{\nservices.AddCap(x =>\n{\nx.UseInMemoryStorage();\nx.UseInMemoryMessageQueue();\n});\n}\n
"},{"location":"user-guide/zh/getting-started/quick-start/#_3","title":"\u53d1\u9001\u6d88\u606f","text":"public class PublishController : Controller\n{\n[Route(\"~/send\")]\npublic IActionResult SendMessage([FromServices]ICapPublisher capBus)\n{\ncapBus.Publish(\"test.show.time\", DateTime.Now);\n\nreturn Ok();\n}\n}\n
"},{"location":"user-guide/zh/getting-started/quick-start/#_4","title":"\u53d1\u9001\u5ef6\u8fdf\u6d88\u606f","text":"public class PublishController : Controller\n{\n[Route(\"~/send/delay\")]\npublic IActionResult SendDelayMessage([FromServices]ICapPublisher capBus)\n{\ncapBus.PublishDelay(TimeSpan.FromSeconds(100),\"test.show.time\", DateTime.Now);\n\nreturn Ok();\n}\n}\n
"},{"location":"user-guide/zh/getting-started/quick-start/#_5","title":"\u53d1\u9001\u5305\u542b\u5934\u4fe1\u606f\u7684\u6d88\u606f","text":"var header = new Dictionary<string, string>()\n{\n[\"my.header.first\"] = \"first\",\n[\"my.header.second\"] = \"second\"\n};\n\ncapBus.Publish(\"test.show.time\", DateTime.Now, header);\n
"},{"location":"user-guide/zh/getting-started/quick-start/#_6","title":"\u5904\u7406\u6d88\u606f","text":"public class ConsumerController : Controller\n{\n[NonAction]\n[CapSubscribe(\"test.show.time\")]\npublic void ReceiveMessage(DateTime time)\n{\nConsole.WriteLine(\"message time is:\" + time);\n}\n}\n
"},{"location":"user-guide/zh/getting-started/quick-start/#_7","title":"\u5904\u7406\u5305\u542b\u5934\u4fe1\u606f\u7684\u6d88\u606f","text":"[CapSubscribe(\"test.show.time\")]\npublic void ReceiveMessage(DateTime time, [FromCap]CapHeader header)\n{\nConsole.WriteLine(\"message time is:\" + time);\nConsole.WriteLine(\"message firset header :\" + header[\"my.header.first\"]);\nConsole.WriteLine(\"message second header :\" + header[\"my.header.second\"]);\n}\n
"},{"location":"user-guide/zh/getting-started/quick-start/#_8","title":"\u6458\u8981","text":"\u76f8\u5bf9\u4e8e\u76f4\u63a5\u96c6\u6210\u6d88\u606f\u961f\u5217\uff0c\u5f02\u6b65\u6d88\u606f\u4f20\u9012\u6700\u5f3a\u5927\u7684\u4f18\u52bf\u4e4b\u4e00\u662f\u53ef\u9760\u6027\uff0c\u7cfb\u7edf\u7684\u4e00\u4e2a\u90e8\u5206\u4e2d\u7684\u6545\u969c\u4e0d\u4f1a\u4f20\u64ad\uff0c\u4e5f\u4e0d\u4f1a\u5bfc\u81f4\u6574\u4e2a\u7cfb\u7edf\u5d29\u6e83\u3002 \u5728 CAP \u5185\u90e8\u4f1a\u5c06\u6d88\u606f\u8fdb\u884c\u5b58\u50a8\uff0c\u4ee5\u4fdd\u8bc1\u6d88\u606f\u7684\u53ef\u9760\u6027\uff0c\u5e76\u914d\u5408\u91cd\u8bd5\u7b49\u7b56\u7565\u4ee5\u8fbe\u5230\u5404\u4e2a\u670d\u52a1\u4e4b\u95f4\u7684\u6570\u636e\u6700\u7ec8\u4e00\u81f4\u6027\u3002
"},{"location":"user-guide/zh/monitoring/consul/","title":"Consul","text":"Consul \u662f\u4e00\u4e2a\u5206\u5e03\u5f0f\u670d\u52a1\u7f51\u683c\uff0c\u7528\u4e8e\u8de8\u4efb\u4f55\u8fd0\u884c\u65f6\u5e73\u53f0\u548c\u516c\u5171\u6216\u79c1\u6709\u4e91\u8fde\u63a5\uff0c\u4fdd\u62a4\u548c\u914d\u7f6e\u670d\u52a1\u3002
"},{"location":"user-guide/zh/monitoring/consul/#dashboard-consul","title":"Dashboard \u4e2d\u7684 Consul \u914d\u7f6e","text":"CAP\u7684 Dashboard \u4f7f\u7528 Consul \u4f5c\u4e3a\u670d\u52a1\u53d1\u73b0\u6765\u663e\u793a\u5176\u4ed6\u8282\u70b9\u7684\u6570\u636e\uff0c\u7136\u540e\u4f60\u5c31\u5728\u4efb\u610f\u8282\u70b9\u7684 Dashboard \u4e2d\u5207\u6362\u5230 Servers \u9875\u9762\u770b\u5230\u5176\u4ed6\u7684\u8282\u70b9\u3002
\u901a\u8fc7\u70b9\u51fb Switch \u6309\u94ae\u6765\u5207\u6362\u5230\u5176\u4ed6\u7684\u8282\u70b9\u770b\u5230\u5176\u4ed6\u8282\u70b9\u7684\u6570\u636e\uff0c\u800c\u4e0d\u5fc5\u8bbf\u95ee\u5f88\u591a\u5730\u5740\u6765\u5206\u522b\u67e5\u770b\u3002
\u4ee5\u4e0b\u662f\u4e00\u4e2a\u914d\u7f6e\u793a\u4f8b, \u4f60\u9700\u8981\u5728\u6bcf\u4e2a\u8282\u70b9\u5206\u522b\u914d\u7f6e\uff1a
services.AddCap(x =>\n{\nx.UseMySql(Configuration.GetValue<string>(\"ConnectionString\"));\nx.UseRabbitMQ(\"localhost\");\nx.UseDashboard();\nx.UseConsulDiscovery(_ =>\n{\n_.DiscoveryServerHostName = \"localhost\";\n_.DiscoveryServerPort = 8500;\n_.CurrentNodeHostName = Configuration.GetValue<string>(\"ASPNETCORE_HOSTNAME\");\n_.CurrentNodePort = Configuration.GetValue<int>(\"ASPNETCORE_PORT\");\n_.NodeId = Configuration.GetValue<string>(\"NodeId\");\n_.NodeName = Configuration.GetValue<string>(\"NodeName\");\n});\n});\n
Consul 1.6.2:
consul agent -dev\n
Windows 10, ASP.NET Core 3.1:
set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5001&& dotnet run --urls=http://localhost:5001 NodeId=1 NodeName=CAP-1 ConnectionString=\"Server=localhost;Database=aaa;UserId=xxx;Password=xxx;\"\nset ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5002&& dotnet run --urls=http://localhost:5002 NodeId=2 NodeName=CAP-2 ConnectionString=\"Server=localhost;Database=bbb;UserId=xxx;Password=xxx;\"\n
"},{"location":"user-guide/zh/monitoring/dashboard/","title":"Dashboard","text":"CAP \u539f\u751f\u63d0\u4f9b\u4e86 Dashboard \u4f9b\u67e5\u770b\u6d88\u606f\uff0c\u5229\u7528 Dashboard \u63d0\u4f9b\u7684\u529f\u80fd\u53ef\u4ee5\u5f88\u65b9\u4fbf\u7684\u67e5\u770b\u548c\u7ba1\u7406\u6d88\u606f\u3002
\u4f7f\u7528\u9650\u5236
Dashboard \u53ea\u652f\u6301\u5728 ASP.NET Core \u4e2d\u4f7f\u7528\uff0c\u4e0d\u652f\u6301\u63a7\u5236\u53f0\u5e94\u7528(Console App)
"},{"location":"user-guide/zh/monitoring/dashboard/#dashboard_1","title":"\u542f\u7528 Dashboard","text":"\u9996\u5148\uff0c\u4f60\u9700\u8981\u5b89\u88c5Dashboard\u7684 NuGet \u5305\u3002
PM> Install-Package DotNetCore.CAP.Dashboard\n
\u7136\u540e\uff0c\u5728\u914d\u7f6e\u4e2d\u6dfb\u52a0\u5982\u4e0b\u4ee3\u7801\uff1a
services.AddCap(x =>\n{\n//...\n\n// Register Dashboard\nx.UseDashboard();\n});\n
\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u4f60\u53ef\u4ee5\u8bbf\u95ee http://localhost:xxx/cap
\u8fd9\u4e2a\u5730\u5740\u6253\u5f00Dashboard\u3002
\u9ed8\u8ba4\u503c\uff1aN/A
\u5f53\u4f4d\u4e8e\u4ee3\u7406\u540e\u65f6\uff0c\u901a\u8fc7\u914d\u7f6e\u6b64\u53c2\u6570\u53ef\u4ee5\u6307\u5b9a\u4ee3\u7406\u8bf7\u6c42\u524d\u7f00\u3002
\u9ed8\u8ba4\u503c\uff1a'/cap'
\u4f60\u53ef\u4ee5\u901a\u8fc7\u4fee\u6539\u6b64\u914d\u7f6e\u9879\u6765\u66f4\u6539Dashboard\u7684\u8bbf\u95ee\u8def\u5f84\u3002
\u9ed8\u8ba4\u503c\uff1a2000 \u6beb\u79d2
\u6b64\u914d\u7f6e\u9879\u7528\u6765\u914d\u7f6eDashboard \u524d\u7aef \u83b7\u53d6\u72b6\u6001\u63a5\u53e3(/stats)\u7684\u8f6e\u8be2\u65f6\u95f4
Default: true
\u663e\u5f0f\u5141\u8bb8\u5bf9 CAP \u4eea\u8868\u677f API \u8fdb\u884c\u533f\u540d\u8bbf\u95ee\uff0c\u5f53\u542f\u7528ASP.NET Core \u5168\u5c40\u6388\u6743\u7b5b\u9009\u5668\u8bf7\u542f\u7528 AllowAnonymous\u3002
Default: null.
Dashboard \u7684\u6388\u6743\u7b56\u7565\u3002 \u9700\u8bbe\u7f6e AllowAnonymousExplicit
\u4e3a false\u3002
\u4ece\u7248\u672c 8.0.0 \u5f00\u59cb\uff0cCAP \u4eea\u8868\u677f\u5229\u7528 ASP.NET Core \u8eab\u4efd\u9a8c\u8bc1\u673a\u5236\uff0c\u5141\u8bb8\u901a\u8fc7\u81ea\u5b9a\u4e49\u6388\u6743\u7b56\u7565\u548c ASP.NET Core \u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u4e2d\u95f4\u4ef6\u8fdb\u884c\u6269\u5c55\uff0c\u4ee5\u6388\u6743\u4eea\u8868\u677f\u8bbf\u95ee\u3002 \u6709\u5173 ASP.NET Core \u8eab\u4efd\u9a8c\u8bc1\u5185\u90e8\u7ed3\u6784\u7684\u66f4\u591a\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u67e5\u770b\u5b98\u65b9\u6587\u6863.
\u60a8\u53ef\u4ee5\u5728\u793a\u4f8b\u9879\u76ee Sample.Dashboard.Auth
\u4e2d\u67e5\u770b\u793a\u4f8b\u4ee3\u7801\u3002
services.AddCap(cap =>\n{\ncap.UseDashboard(d =>\n{\nd.AllowAnonymousExplicit = true;\n});\ncap.UseInMemoryStorage();\ncap.UseInMemoryMessageQueue();\n});\n
"},{"location":"user-guide/zh/monitoring/dashboard/#example-open-id","title":"Example: Open Id","text":"services\n.AddAuthorization(options =>\n{ options.AddPolicy(DashboardAuthorizationPolicy, policy => policy\n.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)\n.RequireAuthenticatedUser());\n})\n.AddAuthentication(opt => opt.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)\n.AddCookie()\n.AddOpenIdConnect(options =>\n{\n...\n});\n\nservices.AddCap(cap =>\n{\ncap.UseDashboard(d =>\n{\nd.AuthorizationPolicy = DashboardAuthorizationPolicy;\n});\ncap.UseInMemoryStorage();\ncap.UseInMemoryMessageQueue();\n});\n
"},{"location":"user-guide/zh/monitoring/dashboard/#_2","title":"\u81ea\u5b9a\u4e49\u8ba4\u8bc1","text":"\u4ece 8.0.0 \u7248\u5f00\u59cb\uff0cCAP \u63a7\u5236\u9762\u677f\u5229\u7528 ASP.NET Core \u8eab\u4efd\u9a8c\u8bc1\u673a\u5236\uff0c\u5141\u8bb8\u901a\u8fc7\u81ea\u5b9a\u4e49\u6388\u6743\u7b56\u7565\u548c ASP.NET Core \u8eab\u4efd\u9a8c\u8bc1\u4e0e\u6388\u6743\u4e2d\u95f4\u4ef6\u8fdb\u884c\u6269\u5c55\u3002\u6709\u5173 ASP.NET Core \u8eab\u4efd\u9a8c\u8bc1\u5185\u90e8\u673a\u5236\u7684\u66f4\u591a\u8be6\u60c5\uff0c\u8bf7\u67e5\u9605 \u5b98\u65b9\u6587\u6863\u3002
\u60a8\u53ef\u4ee5\u5728\u793a\u4f8b\u9879\u76ee Sample.Dashboard.Auth
\u4e2d\u67e5\u770b\u4ee5\u4e0b\u793a\u4f8b\u3002
services.AddCap(cap =>\n{\ncap.UseDashboard(d =>\n{\nd.AllowAnonymousExplicit = true;\n});\ncap.UseInMemoryStorage();\ncap.UseInMemoryMessageQueue();\n});\n
"},{"location":"user-guide/zh/monitoring/dashboard/#open-id","title":"\u4f8b\u5b50\uff1a\u4f7f\u7528 Open Id","text":"services\n.AddAuthorization(options =>\n{ options.AddPolicy(DashboardAuthorizationPolicy, policy => policy\n.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)\n.RequireAuthenticatedUser());\n})\n.AddAuthentication(opt => opt.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)\n.AddCookie()\n.AddOpenIdConnect(options =>\n{\n...\n});\n\nservices.AddCap(cap =>\n{\ncap.UseDashboard(d =>\n{\nd.AuthorizationPolicy = DashboardAuthorizationPolicy;\n});\ncap.UseInMemoryStorage();\ncap.UseInMemoryMessageQueue();\n});\n
"},{"location":"user-guide/zh/monitoring/dashboard/#authentication-scheme","title":"\u4f8b\u5b50\uff1a\u81ea\u5b9a\u4e49 Authentication Scheme","text":"const string MyDashboardAuthenticationPolicy = \"MyDashboardAuthenticationPolicy\";\n\nservices.AddAuthorization(options =>\n{ options.AddPolicy(MyDashboardAuthenticationPolicy, policy => policy\n.AddAuthenticationSchemes(MyDashboardAuthenticationSchemeDefaults.Scheme)\n.RequireAuthenticatedUser());\n})\n.AddAuthentication()\n.AddScheme<MyDashboardAuthenticationSchemeOptions, MyDashboardAuthenticationHandler>(MyDashboardAuthenticationSchemeDefaults.Scheme,null);\n\nservices.AddCap(cap =>\n{\ncap.UseDashboard(d =>\n{\nd.AuthorizationPolicy = MyDashboardAuthenticationPolicy;\n});\ncap.UseInMemoryStorage();\ncap.UseInMemoryMessageQueue();\n});\n
"},{"location":"user-guide/zh/monitoring/diagnostics/","title":"\u8bca\u65ad(Diagnostics)","text":"Diagnostics \u63d0\u4f9b\u4e00\u7ec4\u529f\u80fd\u4f7f\u6211\u4eec\u80fd\u591f\u5f88\u65b9\u4fbf\u7684\u53ef\u4ee5\u8bb0\u5f55\u5728\u5e94\u7528\u7a0b\u5e8f\u8fd0\u884c\u671f\u95f4\u53d1\u751f\u7684\u5173\u952e\u6027\u64cd\u4f5c\u4ee5\u53ca\u4ed6\u4eec\u7684\u6267\u884c\u65f6\u95f4\u7b49\uff0c\u4f7f\u7ba1\u7406\u5458\u53ef\u4ee5\u67e5\u627e\u7279\u522b\u662f\u751f\u4ea7\u73af\u5883\u4e2d\u51fa\u73b0\u95ee\u9898\u6240\u5728\u7684\u6839\u672c\u539f\u56e0\u3002
"},{"location":"user-guide/zh/monitoring/diagnostics/#tracing","title":"\u8ddf\u8e2a(Tracing)","text":"CAP \u5bf9 .NET DiagnosticSource
\u63d0\u4f9b\u4e86\u652f\u6301\uff0c\u76d1\u542c\u5668\u540d\u79f0\u4e3a CapDiagnosticListener
\u3002
\u4f60\u53ef\u4ee5\u5728 DotNetCore.CAP.Diagnostics.CapDiagnosticListenerNames
\u7c7b\u4e0b\u9762\u627e\u5230CAP\u5df2\u7ecf\u5b9a\u4e49\u7684\u4e8b\u4ef6\u540d\u79f0\u3002
Diagnostics \u63d0\u4f9b\u5bf9\u5916\u63d0\u4f9b\u7684\u4e8b\u4ef6\u4fe1\u606f\u6709\uff1a
Skywalking \u7684 C# \u5ba2\u6237\u7aef\u63d0\u4f9b\u4e86\u5bf9 CAP Diagnostics \u7684\u652f\u6301\uff0c\u4f60\u53ef\u4ee5\u5229\u7528 SkyAPM-dotnet \u6765\u5b9e\u73b0\u5728 Skywalking \u4e2d\u8ffd\u8e2a\u4e8b\u4ef6\u3002
\u5c1d\u8bd5\u9605\u8bfbReadme\u6587\u6863\u6765\u5728\u4f60\u7684\u9879\u76ee\u4e2d\u96c6\u6210\u5b83\u3002
"},{"location":"user-guide/zh/monitoring/diagnostics/#apm","title":"\u5176\u4ed6 APM \u7684\u652f\u6301","text":"\u76ee\u524d\u8fd8\u6ca1\u6709\u5b9e\u73b0\u5bf9\u9664\u4e86 Skywalking \u7684\u5176\u4ed6APM\u7684\u652f\u6301\uff0c\u5982\u679c\u4f60\u60f3\u5728\u5176\u4ed6 APM \u4e2d\u5b9e\u73b0\u5bf9 CAP \u8bca\u65ad\u4e8b\u4ef6\u7684\u652f\u6301\uff0c\u4f60\u53ef\u4ee5\u53c2\u8003\u8fd9\u91cc\u7684\u4ee3\u7801\u6765\u5b9e\u73b0\u5b83\uff1a
https://github.com/SkyAPM/SkyAPM-dotnet/tree/master/src/SkyApm.Diagnostics.CAP
"},{"location":"user-guide/zh/monitoring/diagnostics/#metrics","title":"\u5ea6\u91cf(Metrics)","text":"\u5ea6\u91cf\u662f\u6307\u5bf9\u4e8e\u4e00\u4e2a\u7269\u4f53\u6216\u662f\u4e8b\u4ef6\u7684\u67d0\u4e2a\u6027\u8d28\u7ed9\u4e88\u4e00\u4e2a\u6570\u5b57\uff0c\u4f7f\u5176\u53ef\u4ee5\u548c\u5176\u4ed6\u7269\u4f53\u6216\u662f\u4e8b\u4ef6\u7684\u76f8\u540c\u6027\u8d28\u6bd4\u8f83\u3002\u5ea6\u91cf\u53ef\u4ee5\u662f\u5bf9\u4e00\u7269\u7406\u91cf\uff08\u5982\u957f\u5ea6\u3001\u5c3a\u5bf8\u6216\u5bb9\u91cf\u7b49\uff09\u7684\u4f30\u8ba1\u6216\u6d4b\u5b9a\uff0c\u4e5f\u53ef\u4ee5\u662f\u5176\u4ed6\u8f83\u62bd\u8c61\u7684\u7279\u8d28\u3002
CAP 7.0 \u5bf9 EventSource
\u63d0\u4f9b\u4e86\u652f\u6301\uff0c\u8ba1\u6570\u5668\u540d\u79f0\u4e3a DotNetCore.CAP.EventCounter
\u3002
CAP \u63d0\u4f9b\u4e86\u4ee5\u4e0b\u51e0\u4e2a\u5ea6\u91cf\u6307\u6807\uff1a
dotnet-counters \u662f\u4e00\u4e2a\u6027\u80fd\u76d1\u89c6\u5de5\u5177\uff0c\u7528\u4e8e\u4e34\u65f6\u8fd0\u884c\u72b6\u51b5\u76d1\u89c6\u548c\u521d\u7ea7\u6027\u80fd\u8c03\u67e5\u3002 \u5b83\u53ef\u4ee5\u89c2\u5bdf\u901a\u8fc7 EventCounter API \u6216 Meter API \u53d1\u5e03\u7684\u6027\u80fd\u8ba1\u6570\u5668\u503c\u3002
\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u6765\u76d1\u89c6CAP\u4e2d\u7684\u5ea6\u91cf\u6307\u6807\uff1a
dotnet-counters ps\ndotnet-counters monitor --process-id=25496 --counters=DotNetCore.CAP.EventCounter\n
\u5176\u4e2d process-id \u4e3a CAP \u6240\u5c5e\u7684\u8fdb\u7a0bId\u3002
"},{"location":"user-guide/zh/monitoring/diagnostics/#dashboard","title":"\u5728 Dashboard \u4e2d\u67e5\u770b\u5ea6\u91cf","text":"\u4f60\u53ef\u4ee5\u914d\u7f6e x.UseDashboard()
\u6765\u5f00\u542f\u4eea\u8868\u76d8\u4ee5\u56fe\u8868\u7684\u5f62\u5f0f\u67e5\u770b Metrics \u6307\u6807\u3002 \u5982\u4e0b\u56fe\uff1a
\u5728 Realtime Metric Graph \u4e2d\uff0c\u65f6\u95f4\u8f74\u4f1a\u968f\u7740\u65f6\u95f4\u5b9e\u65f6\u6eda\u52a8\u4ece\u800c\u53ef\u4ee5\u770b\u5230\u53d1\u5e03\u548c\u6d88\u8d39\u6d88\u606f\u6bcf\u79d2\u7684\u901f\u7387\uff0c\u540c\u65f6\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u6d88\u8d39\u8005\u6267\u884c\u8017\u65f6\u4ee5\u201c\u6253\u70b9\u201d\u7684\u65b9\u5f0f\u5728 Y1 \u8f74\u4e0a\uff08Y0\u8f74\u4e3a\u901f\u7387\uff0cY1\u8f74\u4e3a\u6267\u884c\u8017\u65f6\uff09\u3002
"},{"location":"user-guide/zh/monitoring/kubernetes/","title":"Kubernetes","text":"Kubernetes\uff0c\u4e5f\u79f0\u4e3a K8s\uff0c\u662f\u4e00\u4e2a\u5f00\u6e90\u7cfb\u7edf\uff0c\u7528\u4e8e\u81ea\u52a8\u90e8\u7f72\u3001\u6269\u5c55\u548c\u7ba1\u7406\u5bb9\u5668\u5316\u5e94\u7528\u7a0b\u5e8f\u3002
"},{"location":"user-guide/zh/monitoring/kubernetes/#dashboard-kubernetes","title":"Dashboard \u4e2d\u7684 Kubernetes","text":"\u6211\u4eec\u7684 Dashboard \u4ece 7.2.0 \u7248\u672c\u5f00\u59cb\u652f\u6301 Kubernetes \u4f5c\u4e3a\u670d\u52a1\u53d1\u73b0\u3002\u4f60\u53ef\u4ee5\u5207\u6362\u5230Node\u8282\u70b9\u9875\u9762\uff0c\u7136\u540e\u9009\u62e9\u547d\u540d\u7a7a\u95f4\uff0cCAP\u4f1a\u5217\u51fa\u8be5\u547d\u540d\u7a7a\u95f4\u4e0b\u7684\u6240\u6709Services\uff0c\u70b9\u51fb \u5207\u6362 \u6309\u94ae\u540eDashboard\u5c06\u68c0\u6d4b\u8be5\u8282\u70b9\u7684CAP\u670d\u52a1\u662f\u5426\u53ef\u7528\uff0c\u5982\u679c\u53ef\u7528\u5219\u4f1a\u4ee3\u7406\u5230\u5207\u6362\u7684\u8282\u70b9\u8fdb\u884c\u6570\u636e\u67e5\u770b\u3002
\u4ee5\u4e0b\u662f\u4e00\u4e2a\u914d\u7f6e\u793a\u4f8b
services.AddCap(x =>\n{\n// ...\nx.UseDashboard();\nx.UseK8sDiscovery();\n});\n
\u7ec4\u4ef6\u5c06\u4f1a\u81ea\u52a8\u68c0\u6d4b\u662f\u5426\u5904\u4e8e\u96c6\u7fa4\u5185\u90e8\uff0c\u5982\u679c\u5904\u4e8e\u96c6\u7fa4\u5185\u90e8\u5728\u9700\u8981\u8d4b\u4e88Pod Kubernetes Api \u7684\u6743\u9650\u3002\u53c2\u8003\u4e0b\u4e00\u7ae0\u8282\u3002
"},{"location":"user-guide/zh/monitoring/kubernetes/#pod-kubernetes-api","title":"\u5206\u914d Pod \u8bbf\u95ee Kubernetes Api","text":"\u5982\u679c\u4f60\u7684Deployment\u5173\u8054\u7684ServiceAccount\u6ca1\u6709K8s Api\u8bbf\u95ee\u6743\u9650\u7684\u8bdd\uff0c\u5219\u9700\u8981\u8d4b\u4e88 namespaces
, services
\u8d44\u6e90\u7684 get
, list
\u6743\u9650\u3002
\u8fd9\u662f\u4e00\u4e2a\u5b9e\u4f8byaml\uff0c\u9996\u5148\u521b\u5efa\u4e00\u4e2a ServiceAccount \u548c ClusterRole \u5e76\u8bbe\u7f6e\u76f8\u5173\u6743\u9650\uff0c\u7136\u540e\u4f7f\u7528 ClusterRoleBinding \u8fdb\u884c\u7ed1\u5b9a\u3002\u6700\u540e\u5728Deployment\u4e2d\u4f7f\u7528 serviceAccountName: api-access
\u7ee7\u7eed\u6307\u5b9a\u3002
apiVersion: v1\nkind: ServiceAccount\nmetadata:\n name: api-access\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n name: ns-svc-reader\nrules:\n- apiGroups: [\"\"]\n resources: [\"namespaces\", \"services\"]\n verbs: [\"get\", \"watch\", \"list\"]\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n name: read-pods\nsubjects:\n- kind: ServiceAccount\n name: api-access\n namespace: default\nroleRef:\n kind: ClusterRole\n name: ns-svc-reader\n apiGroup: rbac.authorization.k8s.io\n\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: api-access-deployment\nspec:\n replicas: 1\n selector:\n matchLabels:\n app: api-access-app\n template:\n metadata:\n labels:\n app: api-access-app\n spec:\n serviceAccountName: api-access\n containers:\n - name: api-access-container\n image: your_image\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n name: api-access-service\nspec:\n selector:\n app: api-access-app\n ports:\n - protocol: TCP\n port: 80\n targetPort: 80\n
"},{"location":"user-guide/zh/monitoring/kubernetes/#dashboard","title":"\u72ec\u7acb\u4f7f\u7528 Dashboard","text":"\u4f60\u53ef\u4ee5\u72ec\u7acb\u4f7f\u7528 Dashboard \u800c\u4e0d\u9700\u8981\u914d\u7f6eCAP\uff0c\u6b64\u65f6\u76f8\u5f53\u4e8e Dashboard \u53ef\u4f5c\u4e3a\u5355\u72ec\u7684 Pod \u90e8\u7f72\u5230 Kubernetes \u96c6\u7fa4\u4e2d\u4ec5\u7528\u4f5c\u67e5\u770b\u6570\u636e\uff0c\u5f85\u67e5\u770b\u7684\u670d\u52a1\u4e0d\u518d\u9700\u8981\u914d\u7f6e cap.UseK8sDiscovery()
\u914d\u7f6e\u9879\u3002
services.AddCapDashboardStandalone();\n
\u540c\u6837\uff0c\u4f60\u9700\u8981\u4e3a\u6b64Pod\u914d\u7f6e ServiceAccount \u7684\u8bbf\u95ee\u6743\u9650\u3002
"},{"location":"user-guide/zh/monitoring/opentelemetry/","title":"OpenTelemetry","text":"https://opentelemetry.io/
OpenTelemetry\u662f\u5de5\u5177\u3001api\u548csdk\u7684\u96c6\u5408\u3002 \u4f7f\u7528\u5b83\u6765\u68c0\u6d4b\u3001\u751f\u6210\u3001\u6536\u96c6\u548c\u5bfc\u51fa\u9065\u6d4b\u6570\u636e(\u5ea6\u91cf\u3001\u65e5\u5fd7\u548c\u8ddf\u8e2a)\uff0c\u4ee5\u5e2e\u52a9\u60a8\u5206\u6790\u8f6f\u4ef6\u7684\u6027\u80fd\u548c\u884c\u4e3a\u3002
"},{"location":"user-guide/zh/monitoring/opentelemetry/#_1","title":"\u96c6\u6210","text":"\u4f60\u53ef\u4ee5\u5728\u8fd9\u91cc\u627e\u5230\u5173\u4e8e\u5982\u4f55\u5728\u63a7\u5236\u53f0\u5e94\u7528\u6216ASP.NET Core \u4e2d\u4f7f\u7528OpenTelemetry\u3002 \u5728\u8fd9\u91cc\u6211\u4eec\u4e3b\u8981\u63cf\u8ff0\u5982\u4f55\u5c06CAP\u96c6\u6210\u5230OpenTelemetry\u4e2d\u3002
"},{"location":"user-guide/zh/monitoring/opentelemetry/#_2","title":"\u914d\u7f6e","text":"\u5b89\u88c5CAP\u7684OpenTelemetry\u5305\u5230\u9879\u76ee\u4e2d\u3002
dotnet add package DotNetCore.Cap.OpenTelemetry\n
OpenTelemetry \u7684\u8ddf\u8e2a\u6570\u636e\u6765\u81ea\u4e8eDiagnostics\u53d1\u9001\u7684\u8bca\u65ad\u6570\u636e\uff0c\u6dfb\u52a0 CAP Instrumentation \u5230 OpenTelemetry\u7684\u6269\u5c55\u914d\u7f6e\u4e2d\u4f1a\u8fdb\u884c\u81ea\u52a8\u6536\u96c6\u3002
services.AddOpenTelemetryTracing((builder) => builder\n.AddAspNetCoreInstrumentation()\n.AddCapInstrumentation() // <-- \u6dfb\u52a0\u8fd9\u884c\n.AddZipkinExporter()\n);\n
\u4ee5\u4e0b\u662fCAP\u7684\u8ddf\u8e2a\u6570\u636e\u5728 Zipkin \u4e2d\u7684\u4e00\u4e2a\u793a\u610f\u56fe\uff1a
"},{"location":"user-guide/zh/samples/castle.dynamicproxy/","title":"\u548c Castle DynamicProxy \u96c6\u6210","text":"Castle DynamicProxy \u662f\u4e00\u4e2a\u7528\u4e8e\u5728\u8fd0\u884c\u65f6\u52a8\u6001\u751f\u6210\u8f7b\u91cf\u7ea7.NET\u4ee3\u7406\u7684\u5e93\u3002\u4ee3\u7406\u5bf9\u8c61\u5141\u8bb8\u5728\u4e0d\u4fee\u6539\u7c7b\u4ee3\u7801\u7684\u60c5\u51b5\u4e0b\u622a\u53d6\u5bf9\u5bf9\u8c61\u6210\u5458\u7684\u8c03\u7528\u3002\u53ef\u4ee5\u4ee3\u7406\u7c7b\u548c\u63a5\u53e3\uff0c\u4f46\u662f\u53ea\u80fd\u62e6\u622a\u865a\u62df\u6210\u5458\u3002
Castle.DynamicProxy \u53ef\u4ee5\u5e2e\u52a9\u4f60\u65b9\u4fbf\u7684\u521b\u5efa\u4ee3\u7406\u5bf9\u8c61\uff0c\u4ee3\u7406\u5bf9\u8c61\u53ef\u4ee5\u5e2e\u52a9\u6784\u5efa\u7075\u6d3b\u7684\u5e94\u7528\u7a0b\u5e8f\u4f53\u7cfb\u7ed3\u6784\uff0c\u56e0\u4e3a\u5b83\u5141\u8bb8\u5c06\u529f\u80fd\u900f\u660e\u5730\u6dfb\u52a0\u5230\u4ee3\u7801\u4e2d\uff0c\u800c\u65e0\u9700\u5bf9\u5176\u8fdb\u884c\u4fee\u6539\u3002\u4f8b\u5982\uff0c\u53ef\u4ee5\u4ee3\u7406\u4e00\u4e2a\u7c7b\u6765\u6dfb\u52a0\u65e5\u5fd7\u8bb0\u5f55\u6216\u5b89\u5168\u68c0\u67e5\uff0c\u800c\u65e0\u9700\u4f7f\u4ee3\u7801\u77e5\u9053\u5df2\u6dfb\u52a0\u6b64\u529f\u80fd\u3002
\u4e0b\u9762\u53ef\u4ee5\u770b\u5230\u5982\u4f55\u5728 CAP \u4e2d\u96c6\u6210\u4f7f\u7528 Castle.DynamicProxy\u3002
"},{"location":"user-guide/zh/samples/castle.dynamicproxy/#1-nuget","title":"1\u3001\u5b89\u88c5 NuGet \u5305","text":"\u5728 \u96c6\u6210\u4e86 CAP \u7684\u9879\u76ee\u4e2d\u5b89\u88c5\u5305\uff0c\u6709\u5173\u5982\u4f55\u96c6\u6210 CAP \u7684\u6587\u6863\u8bf7\u770b\u8fd9\u91cc\u3002
\u6ce8\u610f\uff0cCastle.DynamicProxy
\u8fd9\u4e2a\u5305\u5df2\u7ecf\u88ab\u5e9f\u5f03\uff0c\u8bf7\u4f7f\u7528\u6700\u65b0\u7684 Castle.Core
\u5305\u3002
<PackageReference Include=\"Castle.Core\" Version=\"4.4.1\" />\n
"},{"location":"user-guide/zh/samples/castle.dynamicproxy/#2-castle","title":"2\u3001\u521b\u5efa\u4e00\u4e2a Castle \u5207\u9762\u62e6\u622a\u5668","text":"\u53ef\u4ee5\u5728\u8fd9\u91cc dynamicproxy.md \u627e\u5230\u76f8\u5173\u7684\u6587\u6863\u3002
\u4e0b\u9762\u4e3a\u793a\u4f8b\u4ee3\u7801\uff0c\u7ee7\u627f Castle \u63d0\u4f9b\u7684 IInterceptor
\u63a5\u53e3\u5373\u53ef\uff1a
[Serializable]\npublic class MyInterceptor : IInterceptor\n{\n public void Intercept(IInvocation invocation)\n {\n Console.WriteLine(\"Before target call\");\n try\n {\n invocation.Proceed();\n }\n catch (Exception)\n {\n Console.WriteLine(\"Target threw an exception!\");\n throw;\n }\n finally\n {\n Console.WriteLine(\"After target call\");\n }\n }\n}\n
\u62e6\u622a\u5668\u6b64\u5904\u547d\u540d\u4e3a MyInterceptor
\uff0c\u4f60\u53ef\u4ee5\u5728\u5176\u4e2d\u5904\u7406\u4f60\u7684\u4e1a\u52a1\u903b\u8f91\uff0c\u6bd4\u5982\u6dfb\u52a0\u65e5\u5fd7\u6216\u5176\u4ed6\u7684\u4e00\u4e9b\u884c\u4e3a\u3002
\u4e3a IServiceCollection
\u521b\u5efa\u6269\u5c55\uff0c\u65b9\u9762\u540e\u7eed\u8c03\u7528\u3002
using Castle.DynamicProxy;\n\npublic static class ServicesExtensions\n{\npublic static void AddProxiedSingleton<TImplementation>(this IServiceCollection services)\nwhere TImplementation : class\n{\nservices.AddSingleton(serviceProvider =>\n{\nvar proxyGenerator = serviceProvider.GetRequiredService<ProxyGenerator>();\nvar interceptors = serviceProvider.GetServices<IInterceptor>().ToArray();\nreturn proxyGenerator.CreateClassProxy<TImplementation>(interceptors);\n});\n}\n}\n
\u6b64\u5904\u6211\u521b\u5efa\u4e86\u4e00\u4e2a Singleton \u58f0\u660e\u5468\u671f\u7684\u6269\u5c55\u65b9\u6cd5\uff0c\u5efa\u8bae\u6240\u6709 CAP \u7684\u8ba2\u9605\u8005\u90fd\u521b\u5efa\u4e3a Singleton \u5373\u53ef\uff0c\u56e0\u4e3a\u5728 CAP \u5185\u90e8\u5b9e\u9645\u6267\u884c\u7684\u65f6\u5019\u4e5f\u4f1a\u521b\u5efa\u4e00\u4e2a scope \u6765\u6267\u884c\uff0c\u6240\u4ee5\u65e0\u9700\u62c5\u5fc3\u8d44\u6e90\u91ca\u653e\u95ee\u9898\u3002
"},{"location":"user-guide/zh/samples/castle.dynamicproxy/#4-cap","title":"4\u3001\u521b\u5efa CAP \u8ba2\u9605\u670d\u52a1","text":"\u521b\u5efa\u4e00\u4e2a CAP \u8ba2\u9605\u7c7b\uff0c\u6ce8\u610f\u4e0d\u80fd\u653e\u5728 Controller \u4e2d\u4e86\u3002
\u6ce8\u610f\uff1a\u65b9\u6cd5\u9700\u8981\u4e3a\u865a\u65b9\u6cd5 virtual\uff0c\u624d\u80fd\u88ab Castle \u91cd\u5199\uff0c\u522b\u641e\u5fd8\u4e86\u52a0\uff01\uff01\uff01
public class CapSubscribeService: ICapSubscribe\n{\n[CapSubscribe(\"sample.rabbitmq.mysql\")]\npublic virtual void Subscriber(DateTime p)\n{\nConsole.WriteLine($@\"{DateTime.Now} Subscriber invoked, Info: {p}\");\n}\n}\n
"},{"location":"user-guide/zh/samples/castle.dynamicproxy/#5-startup","title":"5\u3001\u5728 Startup \u4e2d\u96c6\u6210","text":"public void ConfigureServices(IServiceCollection services)\n{\n// \u6dfb\u52a0 Castle \u7684\u4ee3\u7406\u751f\u6210\u5668\nservices.AddSingleton(new ProxyGenerator());\n\n// \u6dfb\u52a0\u7b2c2\u6b65\u7684\u81ea\u5b9a\u4e49\u7684\u62e6\u622a\u7c7b\uff0c\u58f0\u660e\u5468\u671f\u4e3a\nservices.AddSingleton<IInterceptor, MyInterceptor>();\n\n// \u6b64\u5904\u4e3a\u4e0a\u9762\u7684\u6269\u5c55\u65b9\u6cd5\uff0c \u6dfb\u52a0 CAP \u8ba2\u9605 Service\nservices.AddProxiedSingleton<CapSubscribeService>();\n\nservices.AddCap(x =>\n{\nx.UseMySql(\"\");\nx.UseRabbitMQ(\"\");\nx.UseDashboard();\n});\n\n// ...\n}\n
\u4ee5\u4e0a\u5c31\u5b8c\u6210\u4e86\u6240\u6709\u7684\u96c6\u6210\u5de5\u4f5c\uff0c\u53ef\u4ee5\u5f00\u59cb\u8fdb\u884c\u6d4b\u8bd5\u4e86\uff0c\u6709\u95ee\u9898\u6b22\u8fce\u5230 Github issue \u53cd\u9988\u3002
"},{"location":"user-guide/zh/samples/eshoponcontainers/","title":"eShopOnContainers","text":"eShopOnContainers is a sample application written in C# running on .NET Core using a microservice architecture, Domain Driven Design.
.NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers.
This reference application is cross-platform at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
The architecture proposes a microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the communication protocol between the client apps and the microservices and supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus (a light message broker, to choose between RabbitMQ or Azure Service Bus, underneath) plus other features defined at the roadmap.
"},{"location":"user-guide/zh/samples/eshoponcontainers/#eshoponcontainers-with-cap","title":"eShopOnContainers with CAP","text":"\u4f60\u53ef\u4ee5\u5728\u4e0b\u9762\u7684\u5730\u5740\u770b\u5230\u5982\u4f55\u5728 eShopOnContainers \u4e2d\u4f7f\u7528 CAP\u3002
https://github.com/yang-xiaodong/eShopOnContainers
"},{"location":"user-guide/zh/samples/faq/","title":"FAQ","text":"\u6709\u6ca1\u6709\u5b66\u4e60\u548c\u8ba8\u8bba CAP \u7684\u5373\u65f6\u901a\u8baf\u7fa4\u7ec4\uff08\u4f8b\u5982\u817e\u8baf QQ \u7fa4\uff09\uff1f
\u56de\u7b54\uff1a \u6682\u65f6\u6ca1\u6709\u3002\u4e0e\u5176\u6d6a\u8d39\u5927\u91cf\u65f6\u95f4\u5728\u5373\u65f6\u901a\u8baf\u7fa4\u7ec4\u91cc\uff0c\u6211\u66f4\u5e0c\u671b\u5f00\u53d1\u8005\u80fd\u591f\u57f9\u517b\u72ec\u7acb\u601d\u8003\u80fd\u529b\uff0c\u5e76\u901a\u8fc7\u67e5\u9605\u6587\u6863\u81ea\u884c\u89e3\u51b3\u95ee\u9898\uff0c\u751a\u81f3\u53ef\u4ee5\u5728\u9047\u5230\u9519\u8bef\u65f6\u521b\u5efaissue\u6216\u53d1\u9001\u7535\u5b50\u90ae\u4ef6\u3002
CAP \u662f\u5426\u9700\u8981\u4e3a\u751f\u4ea7\u8005\u548c\u6d88\u8d39\u8005\u5206\u522b\u4f7f\u7528\u4e0d\u540c\u7684\u6570\u636e\u5e93\uff1f
\u56de\u7b54\uff1a\u6ca1\u6709\u5fc5\u8981\u4f7f\u7528\u5b8c\u5168\u4e0d\u540c\u7684\u6570\u636e\u5e93\uff0c\u63a8\u8350\u4e3a\u6bcf\u4e2a\u7a0b\u5e8f\u4f7f\u7528\u4e00\u4e2a\u4e13\u7528\u6570\u636e\u5e93\u3002
\u5426\u5219\uff0c\u8bf7\u53c2\u9605\u4e0b\u9762\u7684\u95ee\u7b54\u90e8\u5206\u3002
\u5982\u4f55\u4f7f\u7528\u76f8\u540c\u7684\u6570\u636e\u5e93\u7528\u4e8e\u4e0d\u540c\u7684\u5e94\u7528\u7a0b\u5e8f\uff1f
\u56de\u7b54\uff1a \u5728 ConfigureServices \u65b9\u6cd5\u4e2d\u5b9a\u4e49\u8868\u540d\u524d\u7f00\u3002
\u4ee3\u7801\u793a\u4f8b\uff1a
public void ConfigureServices(IServiceCollection services)\n{\nservices.AddCap(x =>\n{\nx.UseKafka(\"\");\nx.UseMySql(opt =>\n{\nopt.ConnectionString = \"connection string\";\nopt.TableNamePrefix = \"appone\"; // different table name prefix here\n});\n});\n}\n
CAP \u80fd\u5426\u4e0d\u4f7f\u7528\u6570\u636e\u5e93\u4f5c\u4e3a\u4e8b\u4ef6\u5b58\u50a8\uff1f\u6211\u53ea\u662f\u60f3\u53d1\u9001\u6d88\u606f
\u56de\u7b54\uff1a \u5b8c\u5168\u4e0d\u7528\u662f\u4e0d\u53ef\u80fd\u7684\uff0c\u4f60\u53ef\u4ee5\u4f7f\u7528 InMemoryStorage \u3002
CAP \u7684\u76ee\u7684\u662f\u5728\u5fae\u670d\u52a1\u6216 SOA \u67b6\u6784\u4e2d\u786e\u4fdd\u4e00\u81f4\u6027\u539f\u5219\u3002\u8be5\u89e3\u51b3\u65b9\u6848\u57fa\u4e8e\u6570\u636e\u5e93\u7684 ACID \u7279\u6027\uff0c\u5982\u679c\u6ca1\u6709\u6570\u636e\u5e93\uff0c\u5355\u7eaf\u7684\u6d88\u606f\u961f\u5217\u6d88\u606f\u4f20\u9012\u662f\u6ca1\u6709\u610f\u4e49\u7684\u3002
\u5982\u679c\u6d88\u8d39\u8005\u51fa\u73b0\u5f02\u5e38\uff0c\u80fd\u5426\u56de\u6eda\u751f\u4ea7\u8005\u6267\u884c\u7684\u6570\u636e\u5e93\u8bed\u53e5\uff1f
\u56de\u7b54\uff1a \u65e0\u6cd5\u56de\u6eda\uff0cCAP \u662f\u6700\u7ec8\u4e00\u81f4\u6027\u89e3\u51b3\u65b9\u6848\u3002
\u53ef\u4ee5\u60f3\u8c61\u60a8\u7684\u573a\u666f\u662f\u8c03\u7528\u7b2c\u4e09\u65b9\u652f\u4ed8\u3002\u5982\u679c\u60a8\u6b63\u5728\u8fdb\u884c\u7b2c\u4e09\u65b9\u652f\u4ed8\u64cd\u4f5c\uff0c\u5728\u6210\u529f\u8c03\u7528\u652f\u4ed8\u5b9d\u7684\u63a5\u53e3\u540e\uff0c\u60a8\u7684\u4ee3\u7801\u51fa\u73b0\u9519\u8bef\uff0c\u652f\u4ed8\u5b9d\u4f1a\u56de\u6eda\u5417\uff1f\u5982\u679c\u4e0d\u56de\u6eda\uff0c\u60a8\u8be5\u600e\u4e48\u529e\uff1fCAP \u7684\u60c5\u51b5\u4e0e\u6b64\u7c7b\u4f3c\u3002
"},{"location":"user-guide/zh/samples/github/","title":"Github \u4e0a\u7684\u793a\u4f8b","text":"\u4f60\u53ef\u4ee5\u5728\u4e0b\u9762\u7684\u5730\u5740\u627e\u5230\u76f8\u5173\u793a\u4f8b\u4ee3\u7801\uff1a
https://github.com/dotnetcore/CAP/tree/master/samples
CAP + Aspire + Azure Service Bus + Azure SQL
https://github.com/NikiforovAll/cap-aspire
"},{"location":"user-guide/zh/storage/general/","title":"\u57fa\u672c","text":"CAP \u9700\u8981\u4f7f\u7528\u5177\u6709\u6301\u4e45\u5316\u529f\u80fd\u7684\u5b58\u50a8\u4ecb\u8d28\u6765\u5b58\u50a8\u4e8b\u4ef6\u6d88\u606f\uff0c\u4f8b\u5982\u901a\u8fc7\u6570\u636e\u5e93\u6216\u8005\u5176\u4ed6NoSql\u8bbe\u65bd\u3002CAP \u4f7f\u7528\u8fd9\u79cd\u65b9\u5f0f\u6765\u5e94\u5bf9\u4e00\u5207\u73af\u5883\u6216\u8005\u7f51\u7edc\u5f02\u5e38\u5bfc\u81f4\u6d88\u606f\u4e22\u5931\u7684\u60c5\u51b5\uff0c\u6d88\u606f\u7684\u53ef\u9760\u6027\u662f\u5206\u5e03\u5f0f\u4e8b\u52a1\u7684\u57fa\u77f3\uff0c\u6240\u4ee5\u5728\u4efb\u4f55\u60c5\u51b5\u4e0b\u6d88\u606f\u90fd\u4e0d\u80fd\u4e22\u5931\u3002
"},{"location":"user-guide/zh/storage/general/#_2","title":"\u6301\u4e45\u5316","text":""},{"location":"user-guide/zh/storage/general/#_3","title":"\u53d1\u9001\u524d","text":"\u5728\u6d88\u606f\u8fdb\u5165\u5230\u6d88\u606f\u961f\u5217\u4e4b\u524d\uff0cCAP\u4f7f\u7528\u672c\u5730\u6570\u636e\u5e93\u8868\u5bf9\u6d88\u606f\u8fdb\u884c\u6301\u4e45\u5316\uff0c\u8fd9\u6837\u53ef\u4ee5\u4fdd\u8bc1\u5f53\u6d88\u606f\u961f\u5217\u51fa\u73b0\u5f02\u5e38\u6216\u8005\u7f51\u7edc\u9519\u8bef\u65f6\u5019\u6d88\u606f\u662f\u6ca1\u6709\u4e22\u5931\u7684\u3002
\u4e3a\u4e86\u4fdd\u8bc1\u8fd9\u79cd\u673a\u5236\u7684\u53ef\u9760\u6027\uff0cCAP\u4f7f\u7528\u548c\u4e1a\u52a1\u4ee3\u7801\u76f8\u540c\u7684\u6570\u636e\u5e93\u4e8b\u52a1\u6765\u4fdd\u8bc1\u4e1a\u52a1\u64cd\u4f5c\u548cCAP\u7684\u6d88\u606f\u5728\u6301\u4e45\u5316\u7684\u8fc7\u7a0b\u4e2d\u662f\u5f3a\u4e00\u81f4\u7684\u3002\u4e5f\u5c31\u662f\u8bf4\u5728\u8fdb\u884c\u6d88\u606f\u6301\u4e45\u5316\u7684\u8fc7\u7a0b\u4e2d\uff0c\u4efb\u4f55\u4e00\u65b9\u53d1\u751f\u5f02\u5e38\u60c5\u51b5\u6570\u636e\u5e93\u90fd\u4f1a\u8fdb\u884c\u56de\u6eda\u64cd\u4f5c\u3002
"},{"location":"user-guide/zh/storage/general/#_4","title":"\u53d1\u9001\u540e","text":"\u6d88\u606f\u8fdb\u5165\u5230\u6d88\u606f\u961f\u5217\u4e4b\u540e\uff0cCAP\u4f1a\u542f\u52a8\u6d88\u606f\u961f\u5217\u7684\u6301\u4e45\u5316\u529f\u80fd\uff0c\u6211\u4eec\u9700\u8981\u8bf4\u660e\u4e00\u4e0b\u5728 RabbitMQ \u548c Kafka \u4e2dCAP\u7684\u6d88\u606f\u662f\u5982\u4f55\u6301\u4e45\u5316\u7684\u3002
\u9488\u5bf9\u4e8e RabbitMQ \u4e2d\u7684\u6d88\u606f\u6301\u4e45\u5316\uff0cCAP \u4f7f\u7528\u7684\u662f\u5177\u6709\u6d88\u606f\u6301\u4e45\u5316\u529f\u80fd\u7684\u6d88\u8d39\u8005\u961f\u5217\uff0c\u4f46\u662f\u8fd9\u91cc\u9762\u53ef\u80fd\u6709\u4f8b\u5916\u60c5\u51b5\uff0c\u53c2\u52a0 2.2.1 \u7ae0\u8282\u3002
\u7531\u4e8e Kafka \u5929\u751f\u8bbe\u8ba1\u7684\u5c31\u662f\u4f7f\u7528\u6587\u4ef6\u8fdb\u884c\u7684\u6d88\u606f\u6301\u4e45\u5316\uff0c\u5728\u6240\u4ee5\u5728\u6d88\u606f\u8fdb\u5165\u5230Kafka\u4e4b\u540e\uff0cKafka\u4f1a\u4fdd\u8bc1\u6d88\u606f\u80fd\u591f\u6b63\u786e\u88ab\u6301\u4e45\u5316\u800c\u4e0d\u4e22\u5931\u3002
"},{"location":"user-guide/zh/storage/general/#_5","title":"\u6d88\u606f\u5b58\u50a8","text":""},{"location":"user-guide/zh/storage/general/#_6","title":"\u652f\u6301\u7684\u5b58\u50a8","text":"CAP \u652f\u6301\u4ee5\u4e0b\u51e0\u79cd\u5177\u6709\u4e8b\u52a1\u652f\u6301\u7684\u6570\u636e\u5e93\u505a\u4e3a\u5b58\u50a8\uff1a
\u5728 CAP \u542f\u52a8\u540e\uff0c\u4f1a\u5411\u6301\u4e45\u5316\u4ecb\u8d28\u4e2d\u751f\u6210\u4e24\u4e2a\u8868\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\u540d\u79f0\u4e3a\uff1aCap.Published
Cap.Received
\u3002
Published \u8868\u7ed3\u6784\uff1a
NAME DESCRIPTION TYPE Id Message Id int Version Message Version string Name Topic Name string Content Json Content string Added Added Time DateTime ExpiresAt Expire time DateTime Retries Retry times int StatusName Status Name stringReceived \u8868\u7ed3\u6784\uff1a
NAME DESCRIPTION TYPE Id Message Id int Version Message Version string Name Topic Name string Group Group Name string Content Json Content string Added Added Time DateTime ExpiresAt Expire time DateTime Retries Retry times int StatusName Status Name stringLock \u8868\u7ed3\u6784\uff08\u53ef\u9009\uff09\uff1a
NAME DESCRIPTION TYPE Key Lock Id string Instance Acquired instance of lock string LastLockTime Last acquired lock time DateTime"},{"location":"user-guide/zh/storage/general/#_8","title":"\u5305\u88c5\u5668\u5bf9\u8c61","text":"CAP \u5728\u8fdb\u884c\u6d88\u606f\u53d1\u9001\u5230\u65f6\u5019\uff0c\u4f1a\u5bf9\u539f\u59cb\u6d88\u606f\u5bf9\u8c61\u8fdb\u884c\u4e00\u4e2a\u4e8c\u6b21\u5305\u88c5\u5b58\u50a8\u5230 Content
\u5b57\u6bb5\u4e2d\uff0c\u4ee5\u4e0b\u4e3a\u5305\u88c5 Content \u7684 Message \u5bf9\u8c61\u6570\u636e\u7ed3\u6784\uff1a
\u5176\u4e2d Id \u5b57\u6bb5\uff0cCAP \u91c7\u7528\u7684 MongoDB \u4e2d\u7684 ObjectId \u5206\u5e03\u5f0fId\u751f\u6210\u7b97\u6cd5\u751f\u6210\u3002
"},{"location":"user-guide/zh/storage/general/#_9","title":"\u793e\u533a\u652f\u6301\u7684\u6301\u4e45\u5316","text":"\u611f\u8c22\u793e\u533a\u5bf9CAP\u7684\u652f\u6301\uff0c\u4ee5\u4e0b\u662f\u793e\u533a\u652f\u6301\u7684\u6301\u4e45\u5316\u7684\u5b9e\u73b0
SQLite (@colinin) \uff1ahttps://github.com/colinin/DotNetCore.CAP.Sqlite
LiteDB (@maikebing) \uff1ahttps://github.com/maikebing/CAP.Extensions
SQLite & Oracle (@cocosip) \uff1ahttps://github.com/cocosip/CAP-Extensions
SmartSql (@xiangxiren) \uff1ahttps://github.com/xiangxiren/SmartSql.CAP
\u5185\u5b58\u6d88\u606f\u7684\u6301\u4e45\u5316\u5b58\u50a8\u5e38\u7528\u4e8e\u5f00\u53d1\u548c\u6d4b\u8bd5\u73af\u5883\uff0c\u5982\u679c\u4f7f\u7528\u57fa\u4e8e\u5185\u5b58\u7684\u5b58\u50a8\u5219\u4f60\u4f1a\u5931\u53bb\u672c\u5730\u4e8b\u52a1\u6d88\u606f\u53ef\u9760\u6027\u4fdd\u8bc1\u3002
"},{"location":"user-guide/zh/storage/in-memory-storage/#_1","title":"\u914d\u7f6e","text":"\u5982\u679c\u8981\u4f7f\u7528\u5185\u5b58\u5b58\u50a8\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.InMemoryStorage\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e\u5185\u5b58\u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseInMemoryStorage();\n// x.UseXXX ...\n});\n}\n
\u5185\u5b58\u4e2d\u7684\u53d1\u9001\u6210\u529f\u6d88\u606f\uff0cCAP \u5c06\u4f1a\u6bcf 5\u5206\u949f \u8fdb\u884c\u4e00\u6b21\u6e05\u7406\u3002
"},{"location":"user-guide/zh/storage/in-memory-storage/#publish-with-transaction","title":"Publish with transaction","text":"In-Memory \u5b58\u50a8 \u4e0d\u652f\u6301 \u4e8b\u52a1\u65b9\u5f0f\u53d1\u9001\u6d88\u606f\u3002
"},{"location":"user-guide/zh/storage/mongodb/","title":"MongoDB","text":"MongoDB \u662f\u4e00\u4e2a\u8de8\u5e73\u53f0\u7684\u9762\u5411\u6587\u6863\u578b\u7684\u6570\u636e\u5e93\u7a0b\u5e8f\uff0c\u5b83\u88ab\u5f52\u4e3a NOSQL \u6570\u636e\u5e93\uff0cCAP \u4ece 2.3 \u7248\u672c\u5f00\u59cb\u652f\u6301 MongoDB \u4f5c\u4e3a\u6d88\u606f\u5b58\u50a8\u3002
MongoDB \u4ece 4.0 \u7248\u672c\u5f00\u59cb\u652f\u6301 ACID \u4e8b\u52a1\uff0c\u6240\u4ee5 CAP \u4e5f\u53ea\u652f\u6301 4.0 \u4ee5\u4e0a\u7684 MongoDB\uff0c\u5e76\u4e14 MongoDB \u9700\u8981\u90e8\u7f72\u4e3a\u96c6\u7fa4\uff0c\u56e0\u4e3a MongoDB \u7684 ACID \u4e8b\u52a1\u9700\u8981\u96c6\u7fa4\u624d\u53ef\u4ee5\u4f7f\u7528\u3002
\u6709\u5173\u5f00\u53d1\u73af\u5883\u5982\u4f55\u5feb\u901f\u642d\u5efa MongoDB 4.0+ \u96c6\u7fa4\uff0c\u4f60\u53ef\u4ee5\u6211\u7684\u53c2\u8003 \u8fd9\u7bc7\u6587\u7ae0\u3002
"},{"location":"user-guide/zh/storage/mongodb/#_1","title":"\u914d\u7f6e","text":"\u8981\u4f7f\u7528 MongoDB \u5b58\u50a8\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.MongoDB\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e\u5185\u5b58\u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseMongoDB(opt=>{\n//MongoDBOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/storage/mongodb/#_2","title":"\u914d\u7f6e\u9879","text":"NAME DESCRIPTION TYPE DEFAULT DatabaseName \u6570\u636e\u5e93\u540d\u79f0 string cap DatabaseConnection \u6570\u636e\u5e93\u8fde\u63a5\u5b57\u7b26\u4e32 string mongodb://localhost:27017 ReceivedCollection \u63a5\u6536\u6d88\u606f\u96c6\u5408\u540d\u79f0 string cap.received PublishedCollection \u53d1\u9001\u6d88\u606f\u96c6\u5408\u540d\u79f0 string cap.published"},{"location":"user-guide/zh/storage/mongodb/#_3","title":"\u4f7f\u7528\u4e8b\u52a1\u53d1\u5e03\u6d88\u606f","text":"\u4e0b\u9762\u7684\u793a\u4f8b\u5c55\u793a\u4e86\u5982\u4f55\u5229\u7528 CAP \u548c MongoDB \u8fdb\u884c\u672c\u5730\u4e8b\u52a1\u96c6\u6210\u3002
//NOTE: before your test, your need to create database and collection at first\n//\u6ce8\u610f\uff1aMongoDB \u4e0d\u80fd\u5728\u4e8b\u52a1\u4e2d\u521b\u5efa\u6570\u636e\u5e93\u548c\u96c6\u5408\uff0c\u6240\u4ee5\u4f60\u9700\u8981\u5355\u72ec\u521b\u5efa\u5b83\u4eec\uff0c\u6a21\u62df\u4e00\u6761\u8bb0\u5f55\u63d2\u5165\u5219\u4f1a\u81ea\u52a8\u521b\u5efa \n//var mycollection = _client.GetDatabase(\"test\").GetCollection<BsonDocument>(\"test.collection\");\n//mycollection.InsertOne(new BsonDocument { { \"test\", \"test\" } });\n\nusing (var session = _client.StartTransaction(_capBus, autoCommit: false))\n{\nvar collection = _client.GetDatabase(\"test\").GetCollection<BsonDocument>(\"test.collection\");\ncollection.InsertOne(session, new BsonDocument { { \"hello\", \"world\" } });\n\n_capBus.Publish(\"sample.rabbitmq.mongodb\", DateTime.Now);\n\nsession.CommitTransaction();\n}\n
"},{"location":"user-guide/zh/storage/mysql/","title":"MySQL","text":"MySQL \u662f\u4e00\u4e2a\u5f00\u6e90\u7684\u5173\u7cfb\u578b\u6570\u636e\u5e93\uff0c\u4f60\u53ef\u4ee5\u4f7f\u7528 MySQL \u6765\u4f5c\u4e3a CAP \u6d88\u606f\u7684\u6301\u4e45\u5316\u3002
"},{"location":"user-guide/zh/storage/mysql/#_1","title":"\u914d\u7f6e","text":"\u8981\u4f7f\u7528 MySQL \u5b58\u50a8\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.MySql\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e\u5185\u5b58\u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseMySql(opt=>{\n//MySqlOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/storage/mysql/#_2","title":"\u914d\u7f6e\u9879","text":"NAME DESCRIPTION TYPE DEFAULT TableNamePrefix Cap\u8868\u540d\u524d\u7f00 string cap ConnectionString \u6570\u636e\u5e93\u8fde\u63a5\u5b57\u7b26\u4e32 string null"},{"location":"user-guide/zh/storage/mysql/#_3","title":"\u81ea\u5b9a\u4e49\u8868\u540d\u79f0","text":"\u4f60\u53ef\u4ee5\u901a\u8fc7\u91cd\u5199 IStorageInitializer
\u63a5\u53e3\u83b7\u53d6\u8868\u540d\u79f0\u7684\u65b9\u6cd5\u6765\u505a\u5230\u8fd9\u4e00\u70b9
\u793a\u4f8b\u4ee3\u7801\uff1a
public class MyTableInitializer : MySqlStorageInitializer\n{\npublic override string GetPublishedTableName()\n{\n//\u4f60\u7684 \u53d1\u9001\u6d88\u606f\u8868 \u540d\u79f0\n}\n\npublic override string GetReceivedTableName()\n{\n//\u4f60\u7684 \u63a5\u6536\u6d88\u606f\u8868 \u540d\u79f0\n}\n}\n
\u7136\u540e\u5c06\u4f60\u7684\u5b9e\u73b0\u6ce8\u518c\u5230\u5bb9\u5668\u4e2d services.AddSingleton<IStorageInitializer, MyTableInitializer>();\n
"},{"location":"user-guide/zh/storage/mysql/#_4","title":"\u4f7f\u7528\u4e8b\u52a1\u53d1\u5e03\u6d88\u606f","text":""},{"location":"user-guide/zh/storage/mysql/#adonet","title":"ADO.NET","text":"private readonly ICapPublisher _capBus;\n\nusing (var connection = new MySqlConnection(AppDbContext.ConnectionString))\n{\nusing (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))\n{\n//your business code\nconnection.Execute(\"insert into test(name) values('test')\", transaction: (IDbTransaction)transaction.DbTransaction);\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ntransaction.Commit();\n}\n}\n
"},{"location":"user-guide/zh/storage/mysql/#entityframework","title":"EntityFramework","text":"private readonly ICapPublisher _capBus;\n\nusing (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))\n{\ndbContext.Persons.Add(new Person() { Name = \"ef.transaction\" });\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ndbContext.SaveChanges();\ntrans.Commit();\n}\n
"},{"location":"user-guide/zh/storage/postgresql/","title":"Postgre SQL","text":"PostgreSQL \u662f\u4e00\u4e2a\u5f00\u6e90\u7684\u5173\u7cfb\u578b\u6570\u636e\u5e93\uff0c\u5b83\u5df2\u7ecf\u53d8\u5f97\u8d8a\u6765\u8d8a\u6d41\u884c\uff0c\u4f60\u53ef\u4ee5\u4f7f\u7528 Postgre SQL \u6765\u4f5c\u4e3a CAP \u6d88\u606f\u7684\u6301\u4e45\u5316\u3002
"},{"location":"user-guide/zh/storage/postgresql/#_1","title":"\u914d\u7f6e","text":"\u8981\u4f7f\u7528 PostgreSQL \u5b58\u50a8\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.PostgreSql\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e\u5185\u5b58\u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UsePostgreSql(opt=>{\n//PostgreSqlOptions\n}); // x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/storage/postgresql/#_2","title":"\u914d\u7f6e\u9879","text":"NAME DESCRIPTION TYPE DEFAULT Schema \u6570\u636e\u5e93\u67b6\u6784 string cap ConnectionString \u6570\u636e\u5e93\u8fde\u63a5\u5b57\u7b26\u4e32 string DataSource Data source NpgsqlDataSource"},{"location":"user-guide/zh/storage/postgresql/#_3","title":"\u81ea\u5b9a\u4e49\u8868\u540d\u79f0","text":"\u4f60\u53ef\u4ee5\u901a\u8fc7\u91cd\u5199 IStorageInitializer
\u63a5\u53e3\u83b7\u53d6\u8868\u540d\u79f0\u7684\u65b9\u6cd5\u6765\u505a\u5230\u8fd9\u4e00\u70b9
\u793a\u4f8b\u4ee3\u7801\uff1a
public class MyTableInitializer : PostgreSqlStorageInitializer\n{\npublic override string GetPublishedTableName()\n{\n//\u4f60\u7684 \u53d1\u9001\u6d88\u606f\u8868 \u540d\u79f0\n}\n\npublic override string GetReceivedTableName()\n{\n//\u4f60\u7684 \u63a5\u6536\u6d88\u606f\u8868 \u540d\u79f0\n}\n}\n
\u7136\u540e\u5c06\u4f60\u7684\u5b9e\u73b0\u6ce8\u518c\u5230\u5bb9\u5668\u4e2d services.AddSingleton<IStorageInitializer, MyTableInitializer>();\n
"},{"location":"user-guide/zh/storage/postgresql/#_4","title":"\u4f7f\u7528\u4e8b\u52a1\u53d1\u5e03\u6d88\u606f","text":""},{"location":"user-guide/zh/storage/postgresql/#adonet","title":"ADO.NET","text":"private readonly ICapPublisher _capBus;\n\nusing (var connection = new NpgsqlConnection(\"ConnectionString\"))\n{\nusing (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))\n{\n//your business code\nconnection.Execute(\"insert into test(name) values('test')\", transaction: (IDbTransaction)transaction.DbTransaction);\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ntransaction.Commit();\n}\n}\n
"},{"location":"user-guide/zh/storage/postgresql/#entityframework","title":"EntityFramework","text":"private readonly ICapPublisher _capBus;\n\nusing (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))\n{\ndbContext.Persons.Add(new Person() { Name = \"ef.transaction\" });\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ndbContext.SaveChanges();\ntrans.Commit();\n}\n
"},{"location":"user-guide/zh/storage/sqlserver/","title":"SQL Server","text":"SQL Server \u662f\u7531\u5fae\u8f6f\u5f00\u53d1\u7684\u4e00\u4e2a\u5173\u7cfb\u578b\u6570\u636e\u5e93\uff0c\u4f60\u53ef\u4ee5\u4f7f\u7528 SQL Server \u6765\u4f5c\u4e3a CAP \u6d88\u606f\u7684\u6301\u4e45\u5316\u3002
\u6ce8\u610f
\u6211\u4eec\u76ee\u524d\u4f7f\u7528 Microsoft.Data.SqlClient
\u4f5c\u4e3a\u6570\u636e\u5e93\u9a71\u52a8\u7a0b\u5e8f\uff0c\u5b83\u662fSQL Server \u9a71\u52a8\u7684\u672a\u6765\uff0c\u5e76\u4e14\u5df2\u7ecf\u653e\u5f03\u4e86 System.Data.SqlClient
\uff0c\u6211\u4eec\u5efa\u8bae\u4f60\u5207\u6362\u8fc7\u53bb\u3002
\u8981\u4f7f\u7528 SQL Server \u5b58\u50a8\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.SqlServer\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e\u5185\u5b58\u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseSqlServer(opt=>{\n//SqlServerOptions\n}); // x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/storage/sqlserver/#_2","title":"\u914d\u7f6e\u9879","text":"NAME DESCRIPTION TYPE DEFAULT Schema \u6570\u636e\u5e93\u67b6\u6784 string Cap ConnectionString \u6570\u636e\u5e93\u8fde\u63a5\u5b57\u7b26\u4e32 string"},{"location":"user-guide/zh/storage/sqlserver/#_3","title":"\u81ea\u5b9a\u4e49\u8868\u540d\u79f0","text":"\u4f60\u53ef\u4ee5\u901a\u8fc7\u91cd\u5199 IStorageInitializer
\u63a5\u53e3\u83b7\u53d6\u8868\u540d\u79f0\u7684\u65b9\u6cd5\u6765\u505a\u5230\u8fd9\u4e00\u70b9
\u793a\u4f8b\u4ee3\u7801\uff1a
public class MyTableInitializer : SqlServerStorageInitializer\n{\npublic override string GetPublishedTableName()\n{\n//\u4f60\u7684 \u53d1\u9001\u6d88\u606f\u8868 \u540d\u79f0\n}\n\npublic override string GetReceivedTableName()\n{\n//\u4f60\u7684 \u63a5\u6536\u6d88\u606f\u8868 \u540d\u79f0\n}\n}\n
\u7136\u540e\u5c06\u4f60\u7684\u5b9e\u73b0\u6ce8\u518c\u5230\u5bb9\u5668\u4e2d services.AddSingleton<IStorageInitializer, MyTableInitializer>();\n
"},{"location":"user-guide/zh/storage/sqlserver/#_4","title":"\u4f7f\u7528\u4e8b\u52a1\u53d1\u5e03\u6d88\u606f","text":""},{"location":"user-guide/zh/storage/sqlserver/#adonet","title":"ADO.NET","text":"private readonly ICapPublisher _capBus;\n\nusing (var connection = new SqlConnection(\"ConnectionString\"))\n{\nusing (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))\n{\n//your business code\nconnection.Execute(\"insert into test(name) values('test')\", transaction: (IDbTransaction)transaction.DbTransaction);\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ntransaction.Commit();\n}\n}\n
"},{"location":"user-guide/zh/storage/sqlserver/#entityframework","title":"EntityFramework","text":"private readonly ICapPublisher _capBus;\n\nusing (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))\n{\ndbContext.Persons.Add(new Person() { Name = \"ef.transaction\" });\n\n_capBus.Publish(\"sample.rabbitmq.mysql\", DateTime.Now);\n\ndbContext.SaveChanges();\ntrans.Commit();\n}\n
"},{"location":"user-guide/zh/transport/aws-sqs/","title":"Amazon SQS","text":"AWS SQS \u662f\u4e00\u79cd\u5b8c\u5168\u6258\u7ba1\u7684\u6d88\u606f\u961f\u5217\u670d\u52a1\uff0c\u53ef\u8ba9\u60a8\u5206\u79bb\u548c\u6269\u5c55\u5fae\u670d\u52a1\u3001\u5206\u5e03\u5f0f\u7cfb\u7edf\u548c\u65e0\u670d\u52a1\u5668\u5e94\u7528\u7a0b\u5e8f\u3002
AWS SNS \u662f\u4e00\u79cd\u9ad8\u5ea6\u53ef\u7528\u3001\u6301\u4e45\u3001\u5b89\u5168\u3001\u5b8c\u5168\u6258\u7ba1\u7684\u53d1\u5e03/\u8ba2\u9605\u6d88\u606f\u6536\u53d1\u670d\u52a1\uff0c\u53ef\u4ee5\u8f7b\u677e\u5206\u79bb\u5fae\u670d\u52a1\u3001\u5206\u5e03\u5f0f\u7cfb\u7edf\u548c\u65e0\u670d\u52a1\u5668\u5e94\u7528\u7a0b\u5e8f\u3002
"},{"location":"user-guide/zh/transport/aws-sqs/#cap-aws-sns-sqs","title":"CAP \u5982\u4f55\u4f7f\u7528 AWS SNS & SQS","text":""},{"location":"user-guide/zh/transport/aws-sqs/#sns","title":"SNS","text":"\u7531\u4e8e CAP \u662f\u57fa\u4e8e Topic \u6a21\u5f0f\u5de5\u4f5c\u7684\uff0c\u6240\u4ee5\u9700\u8981\u4f7f\u7528\u5230 AWS SNS\uff0cSNS \u7b80\u5316\u4e86\u6d88\u606f\u7684\u53d1\u5e03\u8ba2\u9605\u67b6\u6784\u3002
\u5728 CAP \u542f\u52a8\u65f6\u4f1a\u5c06\u6240\u6709\u7684\u8ba2\u9605\u540d\u79f0\u6ce8\u518c\u4e3a SNS \u7684 Topic\uff0c\u4f60\u5c06\u4f1a\u5728\u7ba1\u7406\u63a7\u5236\u53f0\u4e2d\u770b\u5230\u6240\u6709\u5df2\u7ecf\u6ce8\u518c\u7684 Topic \u5217\u8868\u3002
\u7531\u4e8e SNS \u4e0d\u652f\u6301\u4f7f\u7528 .
:
\u7b49\u7b26\u53f7\u4f5c\u4e3a Topic \u7684\u540d\u79f0\uff0c\u6240\u4ee5\u6211\u4eec\u8fdb\u884c\u4e86\u66ff\u6362\uff0c\u6211\u4eec\u5c06 .
\u66ff\u6362\u4e3a\u4e86 -
\uff0c\u5c06 :
\u66ff\u6362\u4e3a\u4e86 _
\u6ce8\u610f\u4e8b\u9879
Amazon SNS \u5f53\u524d\u5141\u8bb8\u53d1\u5e03\u7684\u6d88\u606f\u6700\u5927\u5927\u5c0f\u4e3a 256KB
\u4e3e\u4f8b\uff0c\u4f60\u7684\u5f53\u524d\u9879\u76ee\u4e2d\u6709\u4ee5\u4e0b\u4e24\u4e2a\u8ba2\u9605\u8005\u65b9\u6cd5
[CapSubscribe(\"sample.sns.foo\")]\npublic void TestFoo(DateTime value)\n{\n}\n\n[CapSubscribe(\"sample.sns.bar\")]\npublic void TestBar(DateTime value)\n{\n}\n
\u5728 CAP \u542f\u52a8\u540e\uff0c\u5728 AWS SNS \u4e2d\u4f60\u5c06\u770b\u5230
"},{"location":"user-guide/zh/transport/aws-sqs/#sqs","title":"SQS","text":"\u9488\u5bf9\u6bcf\u4e2a\u6d88\u8d39\u8005\u7ec4\uff0cCAP \u5c06\u521b\u5efa\u4e00\u4e2a\u4e0e\u4e4b\u5bf9\u5e94\u7684 SQS \u961f\u5217\uff0c\u961f\u5217\u7684\u540d\u79f0\u4e3a\u914d\u7f6e\u9879\u4e2d DefaultGroup \u7684\u540d\u79f0\uff0c\u7c7b\u578b\u4e3a Standard Queue \u3002
\u8be5 SQS \u961f\u5217\u5c06\u8ba2\u9605 SNS \u4e2d\u7684 Topic \uff0c\u5982\u4e0b\u56fe\uff1a
\u6ce8\u610f\u4e8b\u9879
\u7531\u4e8e AWS SNS \u7684\u9650\u5236\uff0c\u5f53\u4f60\u51cf\u5c11\u8ba2\u9605\u65b9\u6cd5\u65f6\uff0c\u6211\u4eec\u4e0d\u4f1a\u4e3b\u52a8\u5220\u9664 AWS SNS \u6216\u8005 SQS \u4e0a\u7684\u76f8\u5173 Topic \u6216 Queue\uff0c\u4f60\u9700\u8981\u624b\u52a8\u5220\u9664\u4ed6\u4eec\u3002
"},{"location":"user-guide/zh/transport/aws-sqs/#_1","title":"\u914d\u7f6e","text":"\u8981\u4f7f\u7528 AWS SQS \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.AmazonSQS\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e RabbitMQ \u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseAmazonSQS(opt=>\n{\n//AmazonSQSOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/transport/aws-sqs/#amazonsqs-options","title":"AmazonSQS Options","text":"CAP \u76f4\u63a5\u5bf9\u5916\u63d0\u4f9b\u7684 AmazonSQSOptions \u914d\u7f6e\u53c2\u6570\u5982\u4e0b\uff1a
NAME DESCRIPTION TYPE DEFAULT Region AWS \u6240\u5904\u7684\u533a\u57df Amazon.RegionEndpoint Credentials AWS AK SK\u4fe1\u606f Amazon.Runtime.AWSCredentials\u5982\u679c\u4f60\u7684\u9879\u76ee\u8fd0\u884c\u5728 AWS EC2 \u4e2d\uff0c\u5219\u4e0d\u9700\u8981\u8bbe\u7f6e Credentials\uff0c\u76f4\u63a5\u5bf9 EC2 \u5e94\u7528 IAM \u7b56\u7565\u5373\u53ef\u3002
Credentials \u9700\u8981\u5177\u6709\u65b0\u589e\u548c\u8ba2\u9605 SNS Topic\uff0cSQS Queue \u7b49\u6743\u9650\u3002
"},{"location":"user-guide/zh/transport/azure-service-bus/","title":"Azure Service Bus","text":"Azure \u670d\u52a1\u603b\u7ebf\u662f\u4e00\u79cd\u591a\u79df\u6237\u4e91\u6d88\u606f\u670d\u52a1\uff0c\u53ef\u7528\u4e8e\u5728\u5e94\u7528\u7a0b\u5e8f\u548c\u670d\u52a1\u4e4b\u95f4\u53d1\u9001\u4fe1\u606f\u3002 \u5f02\u6b65\u64cd\u4f5c\u53ef\u5b9e\u73b0\u7075\u6d3b\u7684\u4e2d\u8f6c\u6d88\u606f\u4f20\u9001\u3001\u7ed3\u6784\u5316\u7684\u5148\u8fdb\u5148\u51fa (FIFO) \u6d88\u606f\u4f20\u9001\u4ee5\u53ca\u53d1\u5e03/\u8ba2\u9605\u529f\u80fd\u3002
CAP \u652f\u6301\u4f7f\u7528 Azure Service Bus \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\u3002
"},{"location":"user-guide/zh/transport/azure-service-bus/#configuration","title":"Configuration","text":"\u5fc5\u987b\u6761\u4ef6
\u9488\u5bf9 Service Bus \u7684\u5b9a\u4ef7, CAP \u8981\u6c42\u4f7f\u7528 \u201c\u6807\u51c6\u201d \u6216\u8005 \u201c\u9ad8\u7ea7\u201d \u4ee5\u652f\u6301 Topic \u529f\u80fd\u3002
\u8981\u4f7f\u7528 Azure Service Bus \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.AzureServiceBus\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e\u5185\u5b58\u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseAzureServiceBus(opt=>\n{\n//AzureServiceBusOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/transport/azure-service-bus/#azureservicebus-options","title":"AzureServiceBus Options","text":"CAP \u76f4\u63a5\u5bf9\u5916\u63d0\u4f9b\u7684 Azure Service Bus \u914d\u7f6e\u53c2\u6570\u5982\u4e0b\uff1a
NAME DESCRIPTION TYPE DEFAULT ConnectionString Endpoint \u5730\u5740 string EnableSessions Enable Service bus sessions bool false TopicPath Topic entity path string cap ManagementTokenProvider Token provider ITokenProvider null AutoCompleteMessages \u83b7\u53d6\u4e00\u4e2a\u503c\uff0c\u8be5\u503c\u6307\u793a\u5904\u7406\u5668\u662f\u5426\u5e94\u5728\u6d88\u606f\u5904\u7406\u7a0b\u5e8f\u5b8c\u6210\u5904\u7406\u540e\u81ea\u52a8\u5b8c\u6210\u6d88\u606f bool false CustomHeadersBuilder \u4e3a\u6765\u81ea\u5f02\u6784\u7cfb\u7edf\u7684\u4f20\u5165\u6d88\u606f\u6dfb\u52a0\u81ea\u5b9a\u4e49\u5934Func<Message, List<KeyValuePair<string, string>>>?
null Namespace Servicebus \u7684\u547d\u540d\u7a7a\u95f4\uff0c\u4e0e TokenCredential \u5c5e\u6027\u4e00\u8d77\u4f7f\u7528\u65f6\u9700\u8981\u8bbe\u7f6e string null SQLFilters \u6839\u636e\u540d\u79f0\u548c\u8868\u8fbe\u5f0f\u81ea\u5b9a\u4e49 SQL \u8fc7\u6ee4\u5668 List> null"},{"location":"user-guide/zh/transport/azure-service-bus/#sessions","title":"Sessions","text":"\u5f53\u4f7f\u7528 EnableSessions
\u9009\u9879\u542f\u7528 sessions \u540e\uff0c\u6bcf\u4e2a\u53d1\u9001\u7684\u6d88\u606f\u90fd\u4f1a\u5177\u6709\u4e00\u4e2a session id\u3002 \u8981\u63a7\u5236 seesion id \u4f60\u53ef\u4ee5\u5728\u53d1\u9001\u6d88\u606f\u65f6\u5728\u6d88\u606f\u5934\u4e2d\u4f7f\u7528 AzureServiceBusHeaders.SessionId
\u643a\u5e26\u5b83\u3002
ICapPublisher capBus = ...;\nstring yourEventName = ...;\nYourEventType yourEvent = ...;\n\nDictionary<string, string> extraHeaders = new Dictionary<string, string>();\nextraHeaders.Add(AzureServiceBusHeaders.SessionId, <your-session-id>);\n\ncapBus.Publish(yourEventName, yourEvent, extraHeaders);\n
\u5982\u679c\u5934\u4e2d\u6ca1\u6709 session id , \u90a3\u4e48\u6d88\u606f Id \u4ecd\u7136\u4f7f\u7528\u7684 Message Id.
"},{"location":"user-guide/zh/transport/azure-service-bus/#heterogeneous-systems","title":"Heterogeneous Systems","text":"\u6709\u65f6\u60a8\u53ef\u80fd\u60f3\u63a5\u6536\u7531\u5916\u90e8\u7cfb\u7edf\u53d1\u5e03\u7684\u6d88\u606f\u3002 \u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u9700\u8981\u6dfb\u52a0\u4e00\u7ec4\u4e24\u4e2a\u5f3a\u5236\u6807\u5934\u4ee5\u5b9e\u73b0 CAP \u517c\u5bb9\u6027\uff0c\u5982\u4e0b\u6240\u793a\u3002
c.UseAzureServiceBus(asb =>\n{\nasb.ConnectionString = ...\nasb.CustomHeadersBuilder = (msg, sp) =>\n[\n new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),\n new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)\n ];\n});\n
\u91cd\u8981\u63d0\u793a\uff1a\u5982\u679c\u6d88\u606f\u4e2d\u5df2\u5b58\u5728\u540c\u540d\uff08Key\uff09\u7684\u6807\u5934\uff0c\u5219\u4e0d\u4f1a\u6dfb\u52a0\u81ea\u5b9a\u4e49\u6807\u5934\u3002
"},{"location":"user-guide/zh/transport/general/","title":"\u8fd0\u8f93\u5668","text":"\u901a\u8fc7\u8fd0\u8f93\u5c06\u6570\u636e\u4ece\u4e00\u4e2a\u5730\u65b9\u79fb\u52a8\u5230\u53e6\u4e00\u4e2a\u5730\u65b9-\u5728\u91c7\u96c6\u7a0b\u5e8f\u548c\u7ba1\u9053\u4e4b\u95f4\uff0c\u7ba1\u9053\u4e0e\u5b9e\u4f53\u6570\u636e\u5e93\u4e4b\u95f4\uff0c\u751a\u81f3\u5728\u7ba1\u9053\u4e0e\u5916\u90e8\u7cfb\u7edf\u4e4b\u95f4\u3002
"},{"location":"user-guide/zh/transport/general/#_2","title":"\u652f\u6301\u7684\u8fd0\u8f93\u5668","text":"CAP \u652f\u6301\u4ee5\u4e0b\u51e0\u79cd\u8fd0\u8f93\u65b9\u5f0f\uff1a
Azure Service Bus
vs RabbitMQ
: http://geekswithblogs.net/michaelstephenson/archive/2012/08/12/150399.aspx
Kafka
vs RabbitMQ
: https://stackoverflow.com/questions/42151544/is-there-any-reason-to-use-rabbitmq-over-kafka
\u611f\u8c22\u793e\u533a\u5bf9CAP\u7684\u652f\u6301\uff0c\u4ee5\u4e0b\u662f\u793e\u533a\u652f\u6301\u7684\u8fd0\u8f93\u5668\u5b9e\u73b0
ActiveMQ (@Lukas Zhang): https://github.com/lukazh
RedisMQ (@\u6728\u6728): https://github.com/difudotnet/CAP.RedisMQ.Extensions
ZeroMQ (@maikebing): https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.ZeroMQ
MQTT (@john jiang): https://github.com/jinzaz/jinzaz.CAP.MQTT
In Memory Queue \u4e3a\u57fa\u4e8e\u5185\u5b58\u7684\u6d88\u606f\u961f\u5217\uff0c\u8be5\u6269\u5c55\u7531 \u793e\u533a \u8fdb\u884c\u63d0\u4f9b\u3002
"},{"location":"user-guide/zh/transport/in-memory-queue/#_1","title":"\u914d\u7f6e","text":"\u8981\u4f7f\u7528 In Memory Queue \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package Savorboard.CAP.InMemoryMessageQueue\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e\u5185\u5b58\u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseInMemoryMessageQueue();\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/transport/kafka/","title":"Apache Kafka\u00ae","text":"Apache Kafka\u00ae \u662f\u4e00\u4e2a\u5f00\u6e90\u6d41\u5904\u7406\u8f6f\u4ef6\u5e73\u53f0\uff0c\u7531 LinkedIn \u5f00\u53d1\u5e76\u6350\u8d60\u7ed9 Apache Software Foundation\uff0c\u7528 Scala \u548c Java \u7f16\u5199\u3002
CAP \u652f\u6301\u4f7f\u7528 Apache Kafka\u00ae \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\u3002
"},{"location":"user-guide/zh/transport/kafka/#configuration","title":"Configuration","text":"\u8981\u4f7f\u7528 Kafka \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.Kafka\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e Kafka \u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseKafka(opt=>{\n//KafkaOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/transport/kafka/#kafka-options","title":"Kafka Options","text":"CAP \u76f4\u63a5\u5bf9\u5916\u63d0\u4f9b\u7684 Kafka \u914d\u7f6e\u53c2\u6570\u5982\u4e0b\uff1a
NAME DESCRIPTION TYPE DEFAULT Servers Broker \u5730\u5740 string ConnectionPoolSize \u7528\u6237\u540d int 10 CustomHeadersBuilder \u8bbe\u7f6e\u81ea\u5b9a\u4e49\u5934 Function\u6709\u5173 CustomHeadersBuilder
\u7684\u8bf4\u660e\uff1a
\u5982\u679c\u4f60\u60f3\u5728\u6d88\u8d39\u6d88\u606f\u7684\u65f6\u5019\uff0c\u901a\u8fc7\u4ece CapHeader
\u83b7\u53d6 Kafka \u4e2d\u4f8b\u5982 Offset \u6216\u8005 Partition \u7b49\u4fe1\u606f\uff0c\u4f60\u53ef\u4ee5\u901a\u8fc7\u81ea\u5b9a\u4e49\u6b64\u51fd\u6570\u6765\u5b9e\u73b0\u8fd9\u4e00\u70b9\u3002
\u4f8b\u5982\u4ee5\u4e0b\u4ee3\u7801\u4e3a\u4f60\u5c55\u793a\u4e86\u5982\u4f55\u8fdb\u884c\u8bbe\u7f6e\u989d\u5916\u7684\u53c2\u6570\u5230 CapHeader
\u4e2d:
x.UseKafka(opt =>\n{\n//...\n\nopt.CustomHeadersBuilder = (kafkaResult,sp) => new List<KeyValuePair<string, string>>\n{\nnew KeyValuePair<string, string>(\"my.kafka.offset\", kafkaResult.Offset.ToString()),\nnew KeyValuePair<string, string>(\"my.kafka.partition\", kafkaResult.Partition.ToString())\n};\n});\n
\u7136\u540e\u4f60\u53ef\u4ee5\u901a\u8fc7\u8fd9\u4e2a\u65b9\u5f0f\u6765\u83b7\u53d6\u4f60\u6dfb\u52a0\u7684\u5934\u4fe1\u606f:
[CapSubscribe(\"sample.kafka.postgrsql\")]\npublic void HeadersTest(DateTime value, [FromCap]CapHeader header)\n{\nvar offset = header[\"my.kafka.offset\"];\nvar partition = header[\"my.kafka.partition\"];\n}\n
"},{"location":"user-guide/zh/transport/kafka/#kafka-mainconfig-options","title":"Kafka MainConfig Options","text":"\u5982\u679c\u4f60\u9700\u8981 \u66f4\u591a \u539f\u751f Kakfa \u76f8\u5173\u7684\u914d\u7f6e\u9879\uff0c\u53ef\u4ee5\u901a\u8fc7 MainConfig
\u914d\u7f6e\u9879\u8fdb\u884c\u8bbe\u5b9a\uff1a
services.AddCap(capOptions => {\ncapOptions.UseKafka(kafkaOption=>\n{\n// kafka options.\n// kafkaOptions.MainConfig.Add(\"\", \"\");\n});\n});\n
MainConfig \u4e3a\u914d\u7f6e\u5b57\u5178\uff0c\u4f60\u53ef\u4ee5\u901a\u8fc7\u4ee5\u4e0b\u94fe\u63a5\u627e\u5230\u5176\u652f\u6301\u7684\u914d\u7f6e\u9879\u5217\u8868\u3002
https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
"},{"location":"user-guide/zh/transport/nats/","title":"NATS","text":"NATS\u662f\u4e00\u4e2a\u7b80\u5355\u3001\u5b89\u5168\u3001\u9ad8\u6027\u80fd\u7684\u6570\u5b57\u7cfb\u7edf\u3001\u670d\u52a1\u548c\u8bbe\u5907\u901a\u4fe1\u7cfb\u7edf\u3002NATS \u662f CNCF \u7684\u4e00\u90e8\u5206\u3002
Warning
\u81ea CAP 5.2+ \u7684\u7248\u672c\u5df2\u7ecf\u57fa\u4e8e JetStream \u5b9e\u73b0\u76f8\u5173\u529f\u80fd\uff0c\u6240\u4ee5\u9700\u8981\u5728\u670d\u52a1\u7aef\u663e\u5f0f\u542f\u7528\u3002
\u4f60\u9700\u8981\u5728 NATS Server \u542f\u52a8\u65f6\u5019\u6307\u5b9a --jetstream
\u53c2\u6570\u6765\u542f\u7528 JetSteram \u76f8\u5173\u529f\u80fd\uff0c\u624d\u80fd\u6b63\u5e38\u4f7f\u7528CAP.
\u8981\u4f7f\u7528NATS \u4f20\u8f93\u5668\uff0c\u4f60\u9700\u8981\u5b89\u88c5\u4e0b\u9762\u7684NuGet\u5305\uff1a
PM> Install-Package DotNetCore.CAP.NATS\n
\u4f60\u53ef\u4ee5\u901a\u8fc7\u5728 Startup.cs
\u6587\u4ef6\u4e2d\u914d\u7f6e ConfigureServices
\u6765\u6dfb\u52a0\u914d\u7f6e\uff1a
public void ConfigureServices(IServiceCollection services)\n{\nservices.AddCap(capOptions =>\n{\ncapOptions.UseNATS(natsOptions=>{\n//NATS Options\n});\n});\n}\n
"},{"location":"user-guide/zh/transport/nats/#nats_1","title":"NATS \u914d\u7f6e","text":"CAP \u76f4\u63a5\u63d0\u4f9b\u7684\u5173\u4e8e NATS \u7684\u914d\u7f6e\u53c2\u6570\uff1a
NAME DESCRIPTION TYPE DEFAULT Options NATS \u5ba2\u6237\u7aef\u914d\u7f6e Options Options Servers \u670d\u52a1\u5668Urls\u5730\u5740 string NULL ConnectionPoolSize \u8fde\u63a5\u6c60\u6570\u91cf uint 10 DeliverPolicy \u6d88\u8d39\u6d88\u606f\u7684\u7b56\u7565\u70b9\uff08\u26a0\ufe0f\u57288.1.0\u7248\u672c\u79fb\u9664\uff0c\u4f7f\u7528ConsumerOptions
\u66ff\u4ee3\u3002\uff09 enum DeliverPolicy.New StreamOptions \ud83c\udd95 Stream \u914d\u7f6e\u9879 Action NULL ConsumerOptions \ud83c\udd95 Consumer \u914d\u7f6e\u9879 Action NULL"},{"location":"user-guide/zh/transport/nats/#nats-configurationoptions","title":"NATS ConfigurationOptions","text":"\u5982\u679c\u4f60\u9700\u8981 \u66f4\u591a \u539f\u751f\u76f8\u5173\u7684\u914d\u7f6e\u9879\uff0c\u53ef\u4ee5\u901a\u8fc7 Options
\u914d\u7f6e\u9879\u8fdb\u884c\u8bbe\u5b9a\uff1a
services.AddCap(capOptions => {\ncapOptions.UseNATS(natsOptions=>\n{\n// NATS options.\nnatsOptions.Options.Url=\"\";\n});\n});\n
Options
\u662f NATS.Client \u5ba2\u6237\u7aef\u63d0\u4f9b\u7684\u914d\u7f6e\uff0c \u4f60\u53ef\u4ee5\u5728\u8fd9\u4e2a\u94fe\u63a5\u627e\u5230\u66f4\u591a\u8be6\u7ec6\u4fe1\u606f\u3002
Apache Pulsar \u662f\u4e00\u4e2a\u7528\u4e8e\u670d\u52a1\u5668\u5230\u670d\u52a1\u5668\u7684\u6d88\u606f\u7cfb\u7edf\uff0c\u5177\u6709\u591a\u79df\u6237\u3001\u9ad8\u6027\u80fd\u7b49\u4f18\u52bf\u3002 Pulsar \u6700\u521d\u7531 Yahoo \u5f00\u53d1\uff0c\u76ee\u524d\u7531 Apache \u8f6f\u4ef6\u57fa\u91d1\u4f1a\u7ba1\u7406\u3002
CAP \u652f\u6301\u4f7f\u7528 Apache Pulsar \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\u3002
"},{"location":"user-guide/zh/transport/pulsar/#configuration","title":"Configuration","text":"\u8981\u4f7f\u7528 Pulsar \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.Pulsar\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e Pulsar \u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UsePulsar(opt => {\n//Pulsar Options\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/transport/pulsar/#pulsar-options","title":"Pulsar Options","text":"CAP \u76f4\u63a5\u5bf9\u5916\u63d0\u4f9b\u7684 Pulsar \u914d\u7f6e\u53c2\u6570\u5982\u4e0b\uff1a
NAME DESCRIPTION TYPE DEFAULT ServiceUrl Broker \u5730\u5740 string TlsOptions TLS \u914d\u7f6e\u9879 object"},{"location":"user-guide/zh/transport/rabbitmq/","title":"RabbitMQ","text":"RabbitMQ\u662f\u5b9e\u73b0\u4e86\u9ad8\u7ea7\u6d88\u606f\u961f\u5217\u534f\u8bae\uff08AMQP\uff09\u7684\u5f00\u6e90\u6d88\u606f\u4ee3\u7406\u8f6f\u4ef6\uff08\u4ea6\u79f0\u9762\u5411\u6d88\u606f\u7684\u4e2d\u95f4\u4ef6\uff09\u3002RabbitMQ \u670d\u52a1\u5668\u662f\u7528 Erlang \u8bed\u8a00\u7f16\u5199\u7684\uff0c\u800c\u805a\u7c7b\u548c\u6545\u969c\u8f6c\u79fb\u662f\u6784\u5efa\u5728\u5f00\u6e90\u7684\u901a\u8baf\u5e73\u53f0\u6846\u67b6\u4e0a\u7684\u3002\u6240\u6709\u4e3b\u8981\u7684\u7f16\u7a0b\u8bed\u8a00\u5747\u6709\u4e0e\u4ee3\u7406\u63a5\u53e3\u901a\u8baf\u7684\u5ba2\u6237\u7aef\u5e93\u3002
CAP \u652f\u6301\u4f7f\u7528 RabbitMQ \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\u3002
\u6ce8\u610f\u4e8b\u9879
\u5728\u4f7f\u7528RabbitMQ\u65f6\uff0c\u96c6\u6210\u4e86CAP\u7684\u6d88\u8d39\u8005\u5e94\u7528\u5728\u542f\u52a8\u8fc7\u4e00\u6b21\u540e\u4f1a\u81ea\u52a8\u521b\u5efa\u6301\u4e45\u5316\u7684\u961f\u5217\uff0c\u540e\u7eed\u6d88\u606f\u4f1a\u6b63\u5e38\u4f20\u9012\u5230\u961f\u5217\u4e2d\u5e76\u6d88\u8d39\u3002 \u5982\u679c\u4f60\u4ece\u6765\u6ca1\u6709\u542f\u52a8\u8fc7\u6d88\u8d39\u8005\uff0c\u5219\u961f\u5217\u4e0d\u4f1a\u88ab\u81ea\u52a8\u521b\u5efa\uff0c\u6b64\u65f6\u5982\u679c\u5148\u884c\u53d1\u5e03\u6d88\u606f\uff0c\u5728\u6b64\u65f6\u95f4\u6bb5\u7684\u6d88\u606f RabbitMQ Exchange \u6536\u5230\u540e\u4f1a\u76f4\u63a5\u4e22\u5f03\u3002
"},{"location":"user-guide/zh/transport/rabbitmq/#_1","title":"\u914d\u7f6e","text":"\u8981\u4f7f\u7528 RabbitMQ \u4f5c\u4e3a\u6d88\u606f\u4f20\u8f93\u5668\uff0c\u4f60\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u6269\u5c55\u5305\uff1a
Install-Package DotNetCore.CAP.RabbitMQ\n
\u7136\u540e\uff0c\u4f60\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e RabbitMQ \u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\n// ...\n\nservices.AddCap(x =>\n{\nx.UseRabbitMQ(opt=>\n{\n//RabbitMQOptions\n});\n// x.UseXXX ...\n});\n}\n
"},{"location":"user-guide/zh/transport/rabbitmq/#rabbitmq-options","title":"RabbitMQ Options","text":"CAP \u76f4\u63a5\u5bf9\u5916\u63d0\u4f9b\u7684 RabbitMQ \u914d\u7f6e\u53c2\u6570\u5982\u4e0b\uff1a
\u914d\u7f6e\u9879 \u63cf\u8ff0 \u7c7b\u578b \u9ed8\u8ba4\u503c HostName \u5bbf\u4e3b\u5730\u5740\uff0c\u5982\u679c\u8981\u914d\u7f6e\u96c6\u7fa4\u53ef\u4ee5\u4f7f\u7528\u9017\u53f7\u5206\u9694\uff0c\u4f8b\u5982192.168.1.111,192.168.1.112
string localhost UserName \u7528\u6237\u540d string guest Password \u5bc6\u7801 string guest VirtualHost \u865a\u62df\u4e3b\u673a string / Port \u7aef\u53e3\u53f7 int -1 ExchangeName CAP\u9ed8\u8ba4Exchange\u540d\u79f0 string cap.default.topic QueueArguments \u961f\u5217\u989d\u5916\u53c2\u6570 x-arguments QueueArgumentsOptions N/A ConnectionFactoryOptions RabbitMQClient\u539f\u751f\u53c2\u6570 ConnectionFactory N/A CustomHeadersBuilder \u8ba2\u9605\u8005\u81ea\u5b9a\u4e49\u5934\u4fe1\u606f \u89c1\u4e0b\u6587 N/A PublishConfirms \u662f\u5426\u542f\u7528\u53d1\u5e03\u786e\u8ba4 bool false BasicQosOptions \u6307\u5b9a\u6d88\u8d39\u7684Qos BasicQos N/A"},{"location":"user-guide/zh/transport/rabbitmq/#connectionfactory-option","title":"ConnectionFactory Option","text":"\u5982\u679c\u4f60\u9700\u8981 \u66f4\u591a \u539f\u751f ConnectionFactory
\u76f8\u5173\u7684\u914d\u7f6e\u9879\uff0c\u53ef\u4ee5\u901a\u8fc7 ConnectionFactoryOptions
\u914d\u7f6e\u9879\u8fdb\u884c\u8bbe\u5b9a\uff1a
services.AddCap(x =>\n{\nx.UseRabbitMQ(o =>\n{\no.HostName = \"localhost\";\no.ConnectionFactoryOptions = opt => { //rabbitmq client ConnectionFactory config\n};\n});\n});\n
"},{"location":"user-guide/zh/transport/rabbitmq/#customheadersbuilder-option","title":"CustomHeadersBuilder Option","text":"\u5f53\u9700\u8981\u4ece\u5f02\u6784\u7cfb\u7edf\u6216\u8005\u76f4\u63a5\u63a5\u6536\u4eceRabbitMQ \u63a7\u5236\u53f0\u53d1\u9001\u7684\u6d88\u606f\u65f6\uff0c\u7531\u4e8e CAP \u9700\u8981\u5b9a\u4e49\u989d\u5916\u7684\u5934\u4fe1\u606f\u624d\u80fd\u6b63\u5e38\u8ba2\u9605\uff0c\u6240\u4ee5\u6b64\u65f6\u4f1a\u51fa\u73b0\u5f02\u5e38\u3002\u901a\u8fc7\u63d0\u4f9b\u6b64\u53c2\u6570\u6765\u8fdb\u884c\u81ea\u5b9a\u4e49\u5934\u4fe1\u606f\u7684\u8bbe\u7f6e\u6765\u4f7f\u8ba2\u9605\u8005\u6b63\u5e38\u5de5\u4f5c\u3002
\u4f60\u53ef\u4ee5\u5728\u8fd9\u91cc\u627e\u5230\u6709\u5173 \u5934\u4fe1\u606f \u7684\u8bf4\u660e\u3002
\u7528\u6cd5\u5982\u4e0b\uff1a
x.UseRabbitMQ(aa =>\n{\naa.CustomHeadersBuilder = (msg, sp) =>\n[\n new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),\n new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)\n ];\n});\n
"},{"location":"user-guide/zh/transport/rabbitmq/#rabbitmq_1","title":"\u5982\u4f55\u8fde\u63a5 RabbitMQ \u96c6\u7fa4\uff1f","text":"\u4f7f\u7528\u9017\u53f7\u5206\u9694\u8fde\u63a5\u5b57\u7b26\u4e32\u5373\u53ef\uff0c\u5982\u4e0b\uff1a
x=> x.UseRabbitMQ(\"localhost:5672,localhost:5673,localhost:5674\")\n
"},{"location":"user-guide/zh/transport/redis-streams/","title":"Redis Streams","text":"Redis \u662f\u4e00\u4e2a\u5f00\u6e90\uff08BSD\u8bb8\u53ef\uff09\u7684\uff0c\u5185\u5b58\u4e2d\u7684\u6570\u636e\u7ed3\u6784\u5b58\u50a8\u7cfb\u7edf\uff0c\u5b83\u53ef\u4ee5\u7528\u4f5c\u6570\u636e\u5e93\u3001\u7f13\u5b58\u548c\u6d88\u606f\u4e2d\u95f4\u4ef6\u3002
Redis Stream \u662f Redis 5.0 \u5f15\u5165\u7684\u4e00\u79cd\u65b0\u6570\u636e\u7c7b\u578b\uff0c\u5b83\u7528\u4e00\u79cd\u4ec5\u9644\u52a0\u7684\u6570\u636e\u7ed3\u6784\u4ee5\u66f4\u62bd\u8c61\u7684\u65b9\u5f0f\u6a21\u62df\u65e5\u5fd7\u6570\u636e\u7ed3\u6784\u3002
Redis Streams \u53ef\u4ee5\u5728 CAP \u4e2d\u7528\u4f5c\u6d88\u606f\u4f20\u8f93\u5668\u3002
"},{"location":"user-guide/zh/transport/redis-streams/#_1","title":"\u914d\u7f6e","text":"\u8981\u4f7f\u7528 Redis Streams \u4f20\u8f93\u5668\uff0c\u60a8\u9700\u8981\u4ece NuGet \u5b89\u88c5\u4ee5\u4e0b\u5305\uff1a
PM> Install-Package DotNetCore.CAP.RedisStreams\n
\u7136\u540e\uff0c\u60a8\u53ef\u4ee5\u5728 Startup.cs
\u7684 ConfigureServices
\u65b9\u6cd5\u4e2d\u6dfb\u52a0\u57fa\u4e8e Redis Stream \u7684\u914d\u7f6e\u9879\u3002
public void ConfigureServices(IServiceCollection services)\n{\nservices.AddCap(capOptions =>\n{\ncapOptions.UseRedis(redisOptions=>{\n//redisOptions\n});\n});\n}\n
"},{"location":"user-guide/zh/transport/redis-streams/#redis-streams-options","title":"Redis Streams Options","text":"CAP \u76f4\u63a5\u5bf9\u5916\u63d0\u4f9b\u7684 Redis Stream \u914d\u7f6e\u53c2\u6570\u5982\u4e0b\uff1a
NAME DESCRIPTION TYPE DEFAULT Configuration redis\u8fde\u63a5\u914d\u7f6e (StackExchange.Redis) ConfigurationOptions ConfigurationOptions StreamEntriesCount \u8bfb\u53d6\u65f6\u4ece stream \u8fd4\u56de\u7684\u6761\u76ee\u6570 uint 10 ConnectionPoolSize \u8fde\u63a5\u6c60\u6570 uint 10"},{"location":"user-guide/zh/transport/redis-streams/#redis-configuration-options","title":"Redis Configuration Options","text":"\u5982\u679c\u9700\u8981**\u66f4\u591a**\u539f\u751fRedis\u76f8\u5173\u914d\u7f6e\u9009\u9879\uff0c\u60a8\u53ef\u4ee5\u5728 Configuration
\u9009\u9879\u4e2d\u8fdb\u884c\u8bbe\u7f6e :
services.AddCap(capOptions => {\ncapOptions.UseRedis(redisOptions=>\n{\n// redis options.\nredisOptions.Configuration.EndPoints.Add(IPAddress.Loopback, 0);\n});\n});\n
Configuration
\u662f StackExchange.Redis ConfigurationOptions \uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u6b64\u94fe\u63a5\u627e\u5230\u66f4\u591a\u8be6\u7ec6\u4fe1\u606f\u3002
\u7531\u4e8eredis streams \u6ca1\u6709\u81ea\u52a8\u5220\u9664\u6240\u6709\u5df2\u7ecf\u88ab\u6240\u6709\u7ec4\u786e\u8ba4\u7684\u6d88\u606f\u7684\u7279\u6027issue\uff0c\u6240\u4ee5\u4f60\u9700\u8981\u8003\u8651\u662f\u5426\u4f7f\u7528\u811a\u672c\u6765\u6267\u884c\u5b9a\u671f\u5220\u9664\u3002
"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..136d4c970 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,348 @@ + +By default, you can specify configuration when you register CAP services into the DI container for ASP.NET Core project.
+services.AddCap(config=>
+{
+ // config.XXX
+});
+
services
is IServiceCollection
interface, which can be found in the Microsoft.Extensions.DependencyInjection
package.
+you have to configure at least a transport and a storage. If you want to get started quickly you can use the following configuration:
+services.AddCap(capOptions =>
+{
+ capOptions.UseInMemoryQueue(); //Required Savorboard.CAP.InMemoryMessageQueue nuget package.
+ capOptions.UseInmemoryStorage();
+});
+
For specific transport and storage configuration, you can take a look at the configuration options provided by the specific components in the Transports section and the Persistent section.
+The CapOptions
is used to store configuration information. By default they have default values, sometimes you may need to customize them.
++Default: cap.queue.{assembly name}
+
The default consumer group name, corresponds to different names in different Transports, you can customize this value to customize the names in Transports for easy viewing.
+Mapping
+Map to Queue Names in RabbitMQ.
+Map to Consumer Group Id in Apache Kafka.
+Map to Subscription Name in Azure Service Bus.
+Map to Queue Group Name in NATS.
+Map to Consumer Group in Redis Streams.
++Default: Null
+
Add unified prefixes for consumer group. https://github.com/dotnetcore/CAP/pull/780
+++Default: Null
+
Add unified prefixes for topic/queue name. https://github.com/dotnetcore/CAP/pull/780
+++Default: v1
+
It is used to specify a version of a message to isolate messages of different versions of the service. It is often used in A/B testing or multi-service version scenarios. Following are application scenarios that needs versioning:
+Business Iterative and compatible
+Due to the rapid iteration of services, the data structure of the message is not fixed during each service integration process. Sometimes we add or modify certain data structures to accommodate the newly introduced requirements. If you have a brand new system, there's no problem, but if your system is already deployed to a production environment and serves customers, this will cause new features to be incompatible with the old data structure when they go online, and then these changes can cause serious problems. To work around this issue, you can only clean up message queues and persistent messages before starting the application, which is obviously not acceptable for production environments.
+Multiple versions of the server
+Sometimes, the server's server needs to provide multiple sets of interfaces to support different versions of the app. Data structures of the same interface and server interaction of these different versions of the app may be different, so usually server does not provide the same routing addresses to adapt to different versions of App calls.
+Using the same persistent table/collection in different instance
+If you want multiple different instance services to use the same database, in versions prior to 2.4, we could isolate database tables for different instances by specifying different table names. After version 2.4 this can be achived through CAP configuration, by configuring different table name prefixes.
+++Check out the blog to learn more about the Versioning feature: https://www.cnblogs.com/savorboard/p/cap-2-4.html
+
++Default: 60 sec
+
During the message sending process if message transport fails, CAP will try to send the message again. This configuration option is used to configure the interval between each retry.
+During the message sending process if consumption method fails, CAP will try to execute the method again. This configuration option is used to configure the interval between each retry.
+Retry & Interval
+By default if failure occurs on send or consume, retry will start after 4 minutes (FallbackWindowLookbackSeconds) in order to avoid possible problems caused by setting message state delays.
+Failures in the process of sending and consuming messages will be retried 3 times immediately, and will be retried polling after 3 times, at which point the FailedRetryInterval configuration will take effect.
Multi-instance concurrent retries
+We introduced database-based distributed locks in version 7.1.0 to solve the problem of retrying concurrent fetches from the database under multiple instances, you need to explicitly configure UseStorageLock
to true.
++Default: false
+
If set to true, we will use a database-based distributed lock to solve the problem of concurrent fetches data by retry processes with multiple instances. This will generate the cap.lock table in the database.
+++Default: 300 sec
+
The interval of the collector processor deletes expired messages.
+++Default: 1
+
Number of consumer threads, when this value is greater than 1, the order of message execution cannot be guaranteed.
+++Default: 50
+
Maximum number of retries. When this value is reached, retry will stop and the maximum number of retries will be modified by setting this parameter.
+++Default: 240 sec
+
Configure the retry processor to pick up the fallback window lookback time for Scheduled
or Failed
status messages.
++Default: NULL
+
Type: Action<FailedInfo>
Failure threshold callback. This action is called when the retry reaches the value set by FailedRetryCount
, you can receive notification by specifying this parameter to make a manual intervention. For example, send an email or notification.
++Default: 24*3600 sec (1 days)
+
The expiration time (in seconds) of the success message. When the message is sent or consumed successfully, it will be removed from database storage when the time reaches SucceedMessageExpiredAfter
seconds. You can set the expiration time by specifying this value.
++Default: 15*24*3600 sec(15 days)
+
The expiration time (in seconds) of the failed message. When the message is sent or consumed failed, it will be removed from database storage when the time reaches FailedMessageExpiredAfter
seconds. You can set the expiration time by specifying this value.
++Default: false
+
If true
then all consumers within the same group pushes received messages to own dispatching pipeline channel. Each channel has set thread count to ConsumerThreadCount
value.
++Default: false, Before version 7.0 the default behavior is true
+
Renamed to EnableSubscriberParallelExecute
option, Please use the new option.
++Default: false
+
If true
, CAP will prefetch some message from the broker as buffered, then execute the subscriber method. After the execution is done, it will fetch the next batch for execution.
Precautions
+Setting it to true may cause some problems. When the subscription method executes too slowly and takes too long, it will cause the retry thread to pick up messages that have not yet been executed. The retry thread picks up messages from 4 minutes (FallbackWindowLookbackSeconds) ago by default , that is to say, if the message backlog of more than 4 minutes (FallbackWindowLookbackSeconds) on the consumer side will be picked up again and executed again
+++Default:
+Environment.ProcessorCount
With the EnableSubscriberParallelExecute
option enabled, specify the number of parallel task execution threads.
++Default: 1
+
With the EnableSubscriberParallelExecute
option enabled, multiplier used to determine the buffered capacity size in subscriber parallel execution. The buffer capacity is computed by multiplying this factor with the value of SubscriberParallelExecuteThreadCount
, which represents the number of threads allocated for parallel processing.
++Default: false, The (7.2 <= Version < 8.1) the default behavior is true
+
By default, messages sent are first placed into the Channel in memory and then processed linearly. +If set to true, the task of sending messages will be processed in parallel by the .NET thread pool, which will greatly increase the speed of sending.
+ + + + + + + + + + +Subscriber filters are similar to ASP.NET MVC filters and are mainly used to process additional work before and after the subscriber method is executed. Such as transaction management or logging, etc.
+Create a new filter class and inherit the SubscribeFilter
abstract class.
public class MyCapFilter: SubscribeFilter
+{
+ public override Task OnSubscribeExecutingAsync(ExecutingContext context)
+ {
+ // before subscribe method exectuing
+ }
+
+ public override Task OnSubscribeExecutedAsync(ExecutedContext context)
+ {
+ // after subscribe method executed
+ }
+
+ public override Task OnSubscribeExceptionAsync(ExceptionContext context)
+ {
+ // subscribe method execution exception
+ }
+}
+
In some scenarios, if you want to terminate the subscriber method execution, you can throw an exception in OnSubscribeExecutingAsync
, and choose to ignore the exception in OnSubscribeExceptionAsync
.
To ignore exceptions, you can setting context.ExceptionHandled = true
in ExceptionContext
public override Task OnSubscribeExceptionAsync(ExceptionContext context)
+{
+ context.ExceptionHandled = true;
+}
+
Use AddSubscribeFilter<>
to add a filter.
services.AddCap(opt =>
+{
+ // ***
+}.AddSubscribeFilter<MyCapFilter>();
+
Currently, we do not support adding multiple filters.
+ + + + + + + + + + +Imdempotence (which you may read a formal definition of on Wikipedia, when we are talking about messaging, is when a message redelivery can be handled without ending up in an unintended state.
+Before we talk about idempotency, let's talk about the delivery of messages on the consumer side.
+Since CAP doesn't uses MS DTC or other type of 2PC distributed transaction mechanism, there is a problem that the message is strictly delivered at least once. Specifically, in a message-based system, there are three possibilities:
+Exactly once has a (*) next to it, because in the general case, it is simply not possible.
+The At Most Once delivery guarantee covers the case when you are guaranteed to receive all messages either once, or maybe not at all.
+This type of delivery guarantee can arise from your messaging system and your code performing its actions in the following order:
+1. Remove message from queue
+2. Start work transaction
+3. Handle message (your code)
+4. Success?
+ Yes:
+ 1. Commit work transaction
+ No:
+ 1. Roll back work transaction
+ 2. Put message back into the queue
+
In the best case scenario, this is all well and good – your messages will be received, and work transactions will be committed, and you will be happy.
+However, the sun does not always shine, and stuff tends to fail – especially if you do alot of stuff. Consider e.g. what would happen if anything fails after having performed step (1), and then – when you try to execute step (4)/(2) (i.e. put the message back into the queue) – the network was temporarily unavailable, or the message broker restarted, or the host machine decided to reboot because it had installed an update.
+This can be OK if it's what you want, but most things in CAP revolve around the concept of DURABLE messages, i.e. messages whose contents is just as important as the data in your database.
+This delivery guarantee covers the case when you are guaranteed to receive all messages either once, or maybe more times if something has failed.
+It requires a slight change to the order we are executing our steps in, and it requires that the message queue system supports transactions, either in the form of the traditional begin-commit-rollback protocol (MSMQ does this), or in the form of a receive-ack-nack protocol (RabbitMQ, Azure Service Bus, etc. do this).
+Check this out – if we do this:
+1. Grab lease on message in queue
+2. Start work transaction
+3. Handle message (your code)
+4. Success?
+ Yes:
+ 1. Commit work transaction
+ 2. Delete message from queue
+ No:
+ 1. Roll back work transaction
+ 2. Release lease on message
+
and the "lease" we grabbed on the message in step (1) is associated with an appropriate timeout, then we are guaranteed that no matter how wrong things go, we will only actually remove the message from the queue (i.e. execute step (4)/(2)) if we have successfully committed our "work transaction".
+It depends on what you're doing 😄 maybe it's a transaction in a relational database (which traditionally have pretty good support in this regard), maybe it's a transaction in a document database that happens to support transaction (like RavenDB or Postgres), or maybe it's a conceptual transaction in the form of whichever work you happen to carry out as a consequence of handling a message, e.g. update a bunch of documents in MongoDB, move some files around in the file system, or mutate some obscure in-mem data structure.
+The fact that the "work transaction" is just a conceptual thing is what makes it impossible to support the aforementioned Exactly Once delivery guarantee – it's just not generally possible to commit or roll back a "work transaction" and a "queue transaction" (which is what we could call the protocol carried out with the message queue systems) atomically and consistently.
+In CAP, At Least Once delivery guarantee is used.
+Since we have a temporary storage medium (database table), we may be able to do At Most Once, but in order to strictly guarantee that the message will not be lost, we do not provide related functions or configurations.
+The message was successfully written, but the execution of the Consumer method failed.
+There are a lot of reasons why the Consumer method fails. I don't know if the specific scene is blindly retrying or not retrying is an incorrect choice. +For example, if the consumer is debiting service, if the execution of the debit is successful, but fails to write the debit log, the CAP will judge that the consumer failed to execute and try again. If the client does not guarantee idempotency, the framework will retry it, which will inevitably lead to serious consequences for multiple debits.
+The execution of the Consumer method succeeded, but received the same message.
+This scenario is also possible. If the Consumer has been successfully executed at the beginning, but for some reason, such as the Broker recovery, same message has been received, CAP will consider this as a new message after receiving the Broker message. Message will be executed again by the Consumer. Because it is a new message, CAP cannot be idempotent at this time.
+The current data storage mode can not be idempotent.
+Since the table of the CAP message is deleted after 1 hour for the successfully consumed message, if the historical message cannot be idempotent. Historically, if the broker has maintained or manually processed some messages for some reason.
+Industry practices.
+Many event-driven frameworks require users to ensure idempotent operations, such as ENode, RocketMQ, etc...
+From an implementation point of view, CAP can do some less stringent idempotence, but strict idempotent can not be guaranteed.
+Generally, the best way to deal with message redeliveries is to make the processing of each message naturally idempotent.
+Natural idempotence arises when the processing of a message consists of calling an idempotent method on a domain object, like
+obj.MarkAsDeleted();
+
or
+obj.UpdatePeriod(message.NewPeriod);
+
You can use the INSERT ON DUPLICATE KEY UPDATE
provided by the database to easily done.
Another way of making message processing idempotent, is to simply track IDs of processed messages explicitly, and then make your code handle a redelivery.
+Assuming that you are keeping track of message IDs by using an IMessageTracker
that uses the same transactional data store as the rest of your work, your code might look somewhat like this:
readonly IMessageTracker _messageTracker;
+
+public SomeMessageHandler(IMessageTracker messageTracker)
+{
+ _messageTracker = messageTracker;
+}
+
+[CapSubscribe]
+public async Task Handle(SomeMessage message)
+{
+ if (await _messageTracker.HasProcessed(message.Id))
+ {
+ return;
+ }
+
+ // do the work here
+ // ...
+
+ // remember that this message has been processed
+ await _messageTracker.MarkAsProcessed(messageId);
+}
+
As for the implementation of IMessageTracker
, you can use a storage message Id such as Redis or a database and the corresponding processing state.
The chapter refers to the Delivery guarantees of rebus, which I think is described very good. ↩
+The data sent by using the ICapPublisher
interface is called Message
.
TimeoutException thrown in consumer using HTTPClient
+By default, if the consumer throws an OperationCanceledException
(including TaskCanceledException
), we consider this to be normal user behavior and ignore the exception. If you use HTTPClient in the consumer method and configure the request timeout, due to the design issue of HTTP Client, you may need to handle the exception separately and re-throw non OperationCanceledException
, refer to #1368.
Wiki : +Compensating transaction
+In some cases, consumers need to return the execution value to tell the publisher, so that the publisher can implement some compensation actions, usually we called message compensation.
+Usually you can notify the upstream by republishing a new message in the consumer code. CAP provides a simple way to do this. You can specify callbackName
parameter when publishing message, usually this only applies to point-to-point consumption. The following is an example.
For example, in an e-commerce application, the initial status of the order is pending, and the status is marked as succeeded when the product quantity is successfully deducted, otherwise it is failed.
+// ============= Publisher =================
+
+_capBus.Publish("place.order.qty.deducted",
+ contentObj: new { OrderId = 1234, ProductId = 23255, Qty = 1 },
+ callbackName: "place.order.mark.status");
+
+// publisher using `callbackName` to subscribe consumer result
+
+[CapSubscribe("place.order.mark.status")]
+public void MarkOrderStatus(JsonElement param)
+{
+ var orderId = param.GetProperty("OrderId").GetInt32();
+ var isSuccess = param.GetProperty("IsSuccess").GetBoolean();
+
+ if(isSuccess){
+ // mark order status to succeeded
+ }
+ else{
+ // mark order status to failed
+ }
+}
+
+// ============= Consumer ===================
+
+[CapSubscribe("place.order.qty.deducted")]
+public object DeductProductQty(JsonElement param)
+{
+ var orderId = param.GetProperty("OrderId").GetInt32();
+ var productId = param.GetProperty("ProductId").GetInt32();
+ var qty = param.GetProperty("Qty").GetInt32();
+
+ //business logic
+
+ return new { OrderId = orderId, IsSuccess = true };
+}
+
In version 3.0+, we reconstructed the message structure. We used the Header in the message protocol in the message queue to transmit some additional information, so that we can do it in the Body without modifying or packaging the user’s original The message data format and content are sent.
+This approach is reasonable. It helps to better integrate in heterogeneous systems. Compared with previous versions, users do not need to know the message structure used inside CAP to complete the integration work.
+Now we divide the message into Header and Body for transmission.
+The data in the body is the content of the original message sent by the user, that is, the content sent by calling the Publish method. We do not perform any packaging, but send it to the message queue after serialization.
+In the Header, we need to pass some additional information so that the CAP can extract the key features for operation when the message is received.
+The following is the content that needs to be written into the header of the message when sending a message in a heterogeneous system:
+Key | +DataType | +Description | +
---|---|---|
cap-msg-id | +string | +Message Id, Generated by snowflake algorithm, can also be guid | +
cap-msg-name | +string | +The name of the message | +
cap-msg-type | +string | +The type of message, typeof(T).FullName (not required) |
+
cap-senttime | +string | +sending time (not required) | +
cap-kafka-key | +string | +Partitioning by Kafka Key | +
To consume messages sent without CAP headers, both AzureServiceBus, Kafka and RabbitMQ consumers can inject a minimal set of headers using the CustomHeadersBuilder
property as shown below (RabbitMQ example):
+
container.AddCap(x =>
+{
+ x.UseRabbitMQ(z =>
+ {
+ z.ExchangeName = "TestExchange";
+ z.CustomHeadersBuilder = (msg, sp) =>
+ [
+ new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),
+ new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)
+ ];
+ });
+});
+
After adding cap-msg-id
and cap-msg-name
, CAP consumers receive messages sent directly from any external system, like the RabbitMQ management tool when using RabbitMQ as a transport.
To publish messages with CAP headers +
var headers = new Dictionary<string, string?>()
+{
+ {"cap-kafka-key", request.OrderId }
+};
+_publisher.Publish<OrderRequest>("OrderRequest", request,headers);
+
After CAP receives a message, it sends the message to Transport(RabitMq, Kafka...), which is transported by transport.
+When you send message using the ICapPublisher
interface, CAP will dispatch message to the corresponding Transport. Currently, bulk messaging is not supported.
For more information on transports, see Transports section.
+CAP will store the message after receiving it. For more information on storage, see the Storage section.
+Retrying plays an important role in the overall CAP architecture design, CAP retry messages that fail to send or fail to execute. There are several retry strategies used throughout the CAP design process.
+During the message sending process, when the broker crashes or the connection fails or an abnormality occurs, CAP will retry the sending. Retry 3 times for the first time, retry every minute after 4 minutes (FallbackWindowLookbackSeconds), and +1 retry. When the total number of retries reaches 50, CAP will stop retrying.
+You can adjust the total number of retries by setting FailedRetryCount in CapOptions Or use FailedThresholdCallback to receive notifications when the maximum retry count is reached.
+It will stop when the maximum number of times is reached. You can see the reason for the failure in Dashboard and choose whether to manually retry.
+The consumer method is executed when the Consumer receives the message and will retry when an exception occurs. This retry strategy is the same as the send retry.
+We introduced database-based distributed locks in version 7.1.0 to deal with the problem of concurrent data acquisition of database retries under multiple instances, you need to explicitly configure UseStorageLock
option to true.
Whether sending fails or consumption fails, we will store the exception message in the cap-exception field within the message header. You can find it in the Content field's JSON in the database table.
+There is an ExpiresAt
field in the database message table indicating the expiration time of the message. When the message is sent successfully, status will be changed to Successed
, and ExpiresAt
will be set to 1 day later.
Consuming failure will change the message status to Failed
and ExpiresAt
will be set to 15 days later (You can use FailedMessageExpiredAfter configuration items to custom).
By default, the data of the message in the table is deleted every 5 minutes to avoid performance degradation caused by too much data. The cleanup strategy ExpiresAt
is performed when field is not empty and is less than the current time.
That is to say, the message with the status Failed (by default they have been retried 50 times), if you do not have manual intervention for 15 days, it will also be cleaned up.
+You can use CollectorCleaningInterval configuration items to custom the interval time.
+ + + + + + + + + + +We provide the ISerializer
interface to support serialization of messages. By default, json is used to serialize messages and store them in the database.
public class YourSerializer: ISerializer
+{
+ Task<TransportMessage> SerializeAsync(Message message)
+ {
+
+ }
+
+ Task<Message> DeserializeAsync(TransportMessage transportMessage, Type valueType)
+ {
+
+ }
+}
+
Then register your implemented serializer in the container:
+services.AddSingleton<ISerializer, YourSerializer>();
+
+// ---
+services.AddCap
+
CAP does not directly provide out-of-the-box MS DTC or 2PC-based distributed transactions, instead we provide a solution that can be used to solve problems encountered in distributed transactions.
+In a distributed environment, using 2PC or DTC-based distributed transactions can be very expensive due to the overhead involved in communication which affects performance. In addition, since distributed transactions based on 2PC or DTC are also subject to the CAP theorem, it will have to give up availability (A in CAP) when network partitioning occurs.
+++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.1
+
For the processing of distributed transactions, CAP uses the "Eventual Consistency and Compensation" scheme.
+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.
+For example, suppose we need to solve the following task:
+Second task is to ensure, for example, that this user wasn’t banned from our servers for some reason.
+But it could take time, and we’d like to extract it to a separate microservice. It wouldn’t be reasonable to keep the user waiting for so long just to know that he was registered successfully.
+One way to solve it would be with a message-driven approach including compensation. Let’s consider the following architecture:
+The messaging platform could ensure that the messages sent by the microservices are persisted. Then they would be delivered at a later time if the receiver wasn't currently available
+In this architecture, best case scenario would be:
+After we’ve gone through all these steps, the system should be in a consistent state. However, for some period of time, user entity appeared to be in an incomplete state.
+The last step, when the user microservice removes the invalid account, is a compensation phase.
+Now let’s consider some failure scenarios:
+Even if some of the messages were issued multiple times, this wouldn’t affect the consistency of the data in the microservices’ databases.
+By carefully considering all possible failure scenarios, we can ensure that our system would satisfy the conditions of eventual consistency. At the same time, we wouldn’t need to deal with the costly distributed transactions.
+But we have to be aware that ensuring eventual consistency is a complex task. It doesn’t have a single solution for all cases.
+This chapter is quoted from: https://www.baeldung.com/transactions-across-microservices ↩↩
+One of the easiest ways to contribute is to participate in discussions and discuss issues.
+If you have any question or problems, please report them on the CAP repository:
+ +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.
+
CAP is an EventBus and a solution for solving distributed transaction problems in microservices or SOA systems. It helps create a microservices system that is scalable, reliable, and easy to change.
+In Microsoft's eShop microservices sample project, it is recommended to use CAP as the EventBus in the production environment.
+What is 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 events that are being sent and received, other components will never know about the substitution.
+Compared to other Services Bus or Event Bus, CAP has its own characteristics. It does not require users to implement or inherit any interface when sending messages or processing messages. It has very high flexibility. We have always believed that the appointment is greater than the configuration, so the CAP is very simple to use, very friendly to the novice, and lightweight.
+CAP is modular in design and highly scalable. You have many options to choose from, including message queues, storage, serialization, etc. Many elements of the system can be replaced with custom implementations.
+Video: Youtube Tutorial - @CodeOpinion
+ +Article: Introduction and how to use
+Article: New features in version 7.0
+Article: New features in version 6.0
+Article: New features in version 5.0
+Article: New features in version 3.0
+Article: New features in version 2.6
+Article: New features in version 2.5
+Article: New features in version 2.4
+Article: New features in version 2.3
+Article: .NET Core Community The first thousand-star project was born: CAP
+ + + + + + + + + + +Learn how to build a microservices event bus architecture using CAP, which offers advantages over direct integration of message queues, and what out-of-the-box features it provides.
+PM> Install-Package DotNetCore.CAP
+
For quick start, we use memory-based event storage and message transport.
+PM> Install-Package DotNetCore.CAP.InMemoryStorage
+PM> Install-Package Savorboard.CAP.InMemoryMessageQueue
+
In Startup.cs
,add the following configuration:
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddCap(x =>
+ {
+ x.UseInMemoryStorage();
+ x.UseInMemoryMessageQueue();
+ });
+}
+
public class PublishController : Controller
+{
+ [Route("~/send")]
+ public IActionResult SendMessage([FromServices]ICapPublisher capBus)
+ {
+ capBus.Publish("test.show.time", DateTime.Now);
+
+ return Ok();
+ }
+}
+
public class PublishController : Controller
+{
+ [Route("~/send/delay")]
+ public IActionResult SendDelayMessage([FromServices]ICapPublisher capBus)
+ {
+ capBus.PublishDelay(TimeSpan.FromSeconds(100),"test.show.time", DateTime.Now);
+
+ return Ok();
+ }
+}
+
var header = new Dictionary<string, string>()
+{
+ ["my.header.first"] = "first",
+ ["my.header.second"] = "second"
+};
+
+capBus.Publish("test.show.time", DateTime.Now, header);
+
public class ConsumerController : Controller
+{
+ [NonAction]
+ [CapSubscribe("test.show.time")]
+ public void ReceiveMessage(DateTime time)
+ {
+ Console.WriteLine("message time is:" + time);
+ }
+}
+
[CapSubscribe("test.show.time")]
+public void ReceiveMessage(DateTime time, [FromCap]CapHeader header)
+{
+ Console.WriteLine("message time is:" + time);
+ Console.WriteLine("message firset header :" + header["my.header.first"]);
+ Console.WriteLine("message second header :" + header["my.header.second"]);
+}
+
One of the most powerful advantages of asynchronous messaging over direct integrated message queues is reliability, where failures in one part of the system do not propagate or cause the entire system to crash. Messages are stored inside the CAP to ensure the reliability of the message, and strategies such as retry are used to achieve the final consistency of data between services.
+ + + + + + + + + + +Consul is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud.
+CAP's Dashboard uses Consul as a service discovery to get the data of other nodes, and you can switch to the Servers page to see other nodes.
+ +Click the Switch
button to switch to the target node, CAP will use a proxy to get the data of the node you switched to.
The following is a configuration example, you need to configure them on each node.
+services.AddCap(x =>
+{
+ x.UseMySql(Configuration.GetValue<string>("ConnectionString"));
+ x.UseRabbitMQ("localhost");
+ x.UseDashboard();
+ x.UseConsulDiscovery(_ =>
+ {
+ _.DiscoveryServerHostName = "localhost";
+ _.DiscoveryServerPort = 8500;
+ _.CurrentNodeHostName = Configuration.GetValue<string>("ASPNETCORE_HOSTNAME");
+ _.CurrentNodePort = Configuration.GetValue<int>("ASPNETCORE_PORT");
+ _.NodeId = Configuration.GetValue<string>("NodeId");
+ _.NodeName = Configuration.GetValue<string>("NodeName");
+ });
+});
+
Consul 1.6.2:
+consul agent -dev
+
Windows 10, ASP.NET Core 3.1:
+set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5001&& dotnet run --urls=http://localhost:5001 NodeId=1 NodeName=CAP-1 ConnectionString="Server=localhost;Database=aaa;UserId=xxx;Password=xxx;"
+set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5002&& dotnet run --urls=http://localhost:5002 NodeId=2 NodeName=CAP-2 ConnectionString="Server=localhost;Database=bbb;UserId=xxx;Password=xxx;"
+
CAP provides a Dashboard for viewing messages, and features provided by Dashboard make it easy to view and manage messages.
+Usage Limit
+The Dashboard is only supported for use in ASP.NET Core, Not supported for console application
+By default, Dashboard middleware will not be launched. To enable Dashboard functionality you need to add the following code to your configuration:
+services.AddCap(x =>
+{
+ //...
+
+ // Register Dashboard
+ x.UseDashboard();
+});
+
By default, you can open the Dashboard by visiting the url http://localhost:xxx/cap
.
++Default :'/cap'
+
You can change the path of the Dashboard by modifying this configuration option.
+++Default: 2000ms
+
This configuration option is used to configure the Dashboard front end to get the polling time of the status interface (/stats).
+++Default: true
+
Explicitly allows anonymous access for the CAP dashboard API, passing AllowAnonymous to the ASP.NET Core global authorization filter.
+++Default: null.
+
Authorization policy for the Dashboard. Required if AllowAnonymousExplicit
is false.
From version 8.0.0, the CAP Dashboard leverages ASP.NET Core authentication mechanisms allowing extensibility through custom authorization policies and ASP.NET Core authentication and authorization middlewares to authorize Dashboard access. For more details of ASP.NET Core authentication internals, check the official docs.
+You can view the examples below in the sample project Sample.Dashboard.Auth
.
services.AddCap(cap =>
+ {
+ cap.UseDashboard(d =>
+ {
+ d.AllowAnonymousExplicit = true;
+ });
+ cap.UseInMemoryStorage();
+ cap.UseInMemoryMessageQueue();
+ });
+
services
+ .AddAuthorization(options =>
+ {
+ options.AddPolicy(DashboardAuthorizationPolicy, policy => policy
+ .AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)
+ .RequireAuthenticatedUser());
+ })
+ .AddAuthentication(opt => opt.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
+ .AddCookie()
+ .AddOpenIdConnect(options =>
+ {
+ ...
+ });
+
+ services.AddCap(cap =>
+ {
+ cap.UseDashboard(d =>
+ {
+ d.AuthorizationPolicy = DashboardAuthorizationPolicy;
+ });
+ cap.UseInMemoryStorage();
+ cap.UseInMemoryMessageQueue();
+ });
+
const string MyDashboardAuthenticationPolicy = "MyDashboardAuthenticationPolicy";
+
+services.AddAuthorization(options =>
+ {
+ options.AddPolicy(MyDashboardAuthenticationPolicy, policy => policy
+ .AddAuthenticationSchemes(MyDashboardAuthenticationSchemeDefaults.Scheme)
+ .RequireAuthenticatedUser());
+ })
+ .AddAuthentication()
+ .AddScheme<MyDashboardAuthenticationSchemeOptions, MyDashboardAuthenticationHandler>(MyDashboardAuthenticationSchemeDefaults.Scheme,null);
+
+services.AddCap(cap =>
+ {
+ cap.UseDashboard(d =>
+ {
+ d.AuthorizationPolicy = MyDashboardAuthenticationPolicy;
+ });
+ cap.UseInMemoryStorage();
+ cap.UseInMemoryMessageQueue();
+ });
+
Diagnostics provides a set of features that make it easy for us to document critical operations that occurs during the application's operation, their execution time, etc., allowing administrators to find the root cause of problems, especially in production environments.
+The CAP provides support for DiagnosticSource
with a listener name of CapDiagnosticListener
.
Diagnostics provides tracing event information as follows:
+Related objects, you can find at the DotNetCore.CAP.Diagnostics
namespace.
Skywalking's C# client provides support for CAP Diagnostics. You can use SkyAPM-dotnet to tracking.
+Try to read the README to integrate it in your project.
+Example tracking image :
+ + +There is currently no support for APMs other than Skywalking, and if you would like to support CAP diagnostic events in other APM, you can refer to the code here to implement it:
+At present, apart from Skywalking, we have not provided support for other APMs. If you need it, you can refer the code here to implementation, and we also welcome the Pull Request.
+https://github.com/SkyAPM/SkyAPM-dotnet/tree/master/src/SkyApm.Diagnostics.CAP
+Metrics are numerical measurements reported over time, most often used to monitor the health of an application and generate alerts. For example, a web service might track how many requests it receives each second, how many milliseconds it took to respond, and how many of the responses sent an error back to the user.
+CAP 7.0 is support for EventSource
, and the counters name is DotNetCore.CAP.EventCounter
.
CAP provides the following metrics:
+dotnet-counters is a performance monitoring tool for ad-hoc health monitoring and first-level performance investigation. It can observe performance counter values that are published via the EventCounter API or the Meter API.
+Use the following commands to monitor metrics in CAP:
+dotnet-counters ps
+dotnet-counters monitor --process-id=25496 --counters=DotNetCore.CAP.EventCounter
+
process-id: The ID of the CAP process to collect counter data from.
+ +You can configure x.UseDashboard()
to open the dashboard to view Metrics graph charts.
In the Realtime Metric Graph, the time axis will scroll in real time over time so that you can see the rate of publishing and consuming messages per second, And the consumer execution time is "dotted" on the Y1 axis (Y0 axis is the rates, and the Y1 axis is the execution elpsed time).
+ + + + + + + + + + +Kubernetes, also known as K8s, is an open-source system for automating deployment, scaling, and management of containerized applications.
+Our Dashboard has supported Kubernetes as a service discovery from version 7.2.0 onwards. You can switch to the Node page, then select a k8s namespace, and CAP will list all Services under that namespace. After clicking the Switch button, the Dashboard will detect whether the CAP service of that node is available. If it is available, it will proxy to the switched node for data viewing.
+Here is a configuration example:
+services.AddCap(x =>
+{
+ // ...
+ x.UseDashboard();
+ x.UseK8sDiscovery();
+});
+
The component will automatically detect whether it is inside the cluster. If it is inside the cluster, the Pod must be granted Kubernetes Api permissions. Refer to the next section.
+If the ServiceAccount associated with your Deployment does not have access to the K8s Api, you need to grant the namespaces
, services
resources the get
, list
permissions.
Here is an example yaml. First create a ServiceAccount and ClusterRole and set the related permissions, then bind them using ClusterRoleBinding. Finally, use serviceAccountName: api-access
to specify in Deployment.
apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: api-access
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: ns-svc-reader
+rules:
+- apiGroups: [""]
+ resources: ["namespaces", "services"]
+ verbs: ["get", "watch", "list"]
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: read-pods
+subjects:
+- kind: ServiceAccount
+ name: api-access
+ namespace: default
+roleRef:
+ kind: ClusterRole
+ name: ns-svc-reader
+ apiGroup: rbac.authorization.k8s.io
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: api-access-deployment
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: api-access-app
+ template:
+ metadata:
+ labels:
+ app: api-access-app
+ spec:
+ serviceAccountName: api-access
+ containers:
+ - name: api-access-container
+ image: your_image
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: api-access-service
+spec:
+ selector:
+ app: api-access-app
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 80
+
You can use the Dashboard standalone without configuring CAP, in this case, the Dashboard can be deployed as a separate Pod in the Kubernetes cluster just for data viewing. The service to be viewed no longer needs to configure the cap.UseK8sDiscovery()
option.
services.AddCapDashboardStandalone();
+
Similarly, you need to configure the access for the ServiceAccount for this Pod.
+ + + + + + + + + + +OpenTelemetry is a collection of tools, APIs, and SDKs. Use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help you analyze your software’s performance and behavior.
+You can find it here about how to use OpenTelemetry in console applications or ASP.NET Core, at here we mainly describe how to tracing CAP data to OpenTelemetry.
+Install the CAP OpenTelemetry package into the project.
+dotnet add package DotNetCore.Cap.OpenTelemetry
+
The OpenTelemetry data comes from diagnostics, add the instrumentation of CAP to the configuration of OpenTelemetry.
+services.AddOpenTelemetryTracing((builder) => builder
+ .AddAspNetCoreInstrumentation()
+ .AddCapInstrumentation() // <-- Add this line
+ .AddZipkinExporter()
+);
+
If you don't use a framework that does this automatically for you (like aspnetcore), make sure you enable a listener, for example:
+ActivitySource.AddActivityListener(new ActivityListener()
+{
+ ShouldListenTo = _ => true,
+ Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
+ ActivityStarted = activity => Console.WriteLine($"{activity.ParentId}:{activity.Id} - Start"),
+ ActivityStopped = activity => Console.WriteLine($"{activity.ParentId}:{activity.Id} - Stop")
+});
+
CAP supports Context
+Propagation by
+injecting traceparent
and baggage
headers when sending messages and
+restoring the context from those headers when receiving messages.
CAP uses the configured Propagators.DefaultTextMapPropagator propagator, which +is usually set to both TraceContextPropagator and BaggagePropagator by the +dotnet OpenTelemetry +SDK +but can be set in your your client program. For example, to opt out of the +Baggage propagation, you can call:
+OpenTelemetry.Sdk.SetDefaultTextMapPropagator(
+ new TraceContextPropagator());
+
See the dotnet OpenTelemetry.Api +readme +for more details.
+ + + + + + + + + + +eShopOnContainers is a sample application written in C# running on .NET Core using a microservice architecture, Domain Driven Design.
+++.NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers.
+This reference application is cross-platform at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
+The architecture proposes a microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the communication protocol between the client apps and the microservices and supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus (a light message broker, to choose between RabbitMQ or Azure Service Bus, underneath) plus other features defined at the roadmap.
+
You can see how to use caps in eShopOnContainers at the Github repository.
+ + + + + + + + + + + +Any IM group(e.g Tencent QQ group) to learn and chat about CAP?
+None of 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.
+Does it require different databases, one each for producer and consumer in CAP?
+No difference necessary, a recommendation is to use a dedicated database for each program.
+Otherwise, look at Q&A below.
+How to use the same database for different applications?
+Define a table prefix name in ConfigureServices
method.
Code example:
+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
+ });
+ });
+}
+
Can CAP not use the database as event storage? I just want to send the message
+Not yet.
+The purpose of CAP is that ensure consistency principle right in microservice or SOA architectures. The solution is based on ACID features of database, there is no sense about a single client wapper of message queue without database.
+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.
+You can find the sample code at the Github repository:
+https://github.com/dotnetcore/CAP/tree/master/samples
+CAP + Aspire + Azure Service Bus + Azure SQL
+ + + + + + + + + + + +CAP needs to use storage media with persistence capabilities to store event messages in databases or other NoSql facilities. CAP uses this approach to deal with loss of messages in all environments or network anomalies. Reliability of messages is the cornerstone of distributed transactions, so messages cannot be lost under any circumstances.
+Before message enters the message queue, CAP uses the local database table to persist the message, which ensures that the message is not lost when the message queue is abnormal or a network error occurs.
+To ensure the reliability of this mechanism, CAP uses the same database transactions as the business code to ensure that business operations and CAP messages are consistent in the persistence process. That is to say, in the process of message persistence, the database will be rolled back when any one of the exceptions occurs.
+After the message enters the message queue, CAP will start the persistence function of the message queue. We need to explain how CAP message is persisted in RabbitMQ and Kafka.
+For message persistence in RabbitMQ, CAP uses a consumer queue with message persistence, but there may be exceptions here.
+Ready for production?
+By default, queues registered by CAP in RabbitMQ are persistent. When used in a production environment, we recommend that you start all consumers once to create the queues with persistence, which ensures that all queues are created before the message is sent.
+Since Kafka is born with message persistence using files, Kafka will ensure that messages are properly persisted without loss after the message enters Kafka.
+CAP supports the following types of transaction-enabled databases for storage:
+ +After CAP is started, two tables are generated in used storage, by default the name is Cap.Published
and Cap.Received
.
Table structure of Published :
+NAME | +DESCRIPTION | +TYPE | +
---|---|---|
Id | +Message Id | +int | +
Version | +Message Version | +string | +
Name | +Topic Name | +string | +
Content | +Json Content | +string | +
Added | +Added Time | +DateTime | +
ExpiresAt | +Expire time | +DateTime | +
Retries | +Retry times | +int | +
StatusName | +Status Name | +string | +
Table structure of Received :
+NAME | +DESCRIPTION | +TYPE | +
---|---|---|
Id | +Message Id | +int | +
Version | +Message Version | +string | +
Name | +Topic Name | +string | +
Group | +Group Name | +string | +
Content | +Json Content | +string | +
Added | +Added Time | +DateTime | +
ExpiresAt | +Expire time | +DateTime | +
Retries | +Retry times | +int | +
StatusName | +Status Name | +string | +
Table structure of Lock (Optional):
+NAME | +DESCRIPTION | +TYPE | +
---|---|---|
Key | +Lock Id | +string | +
Instance | +Acquired instance of lock | +string | +
LastLockTime | +Last acquired lock time | +DateTime | +
When CAP sends a message, it will store original message object in a second package in the Content
field.
The following is the Wapper Object data structure of Content field.
+NAME | +DESCRIPTION | +TYPE | +
---|---|---|
Id | +Message Id | +string | +
Timestamp | +Message created time | +string | +
Content | +Message content | +string | +
CallbackName | +Consumer callback topic name | +string | +
The Id
field is generate using the mongo objectid algorithm.
Thanks to the community for supporting CAP, the following is the implementation of community-supported storage
+SQLite (@colinin) :https://github.com/colinin/DotNetCore.CAP.Sqlite
+LiteDB (@maikebing) :https://github.com/maikebing/CAP.Extensions
+SQLite & Oracle (@cocosip) :https://github.com/cocosip/CAP-Extensions
+In-memory storage is often used in development and test environments, and if you use memory-based storage you lose the reliability of local transaction messages.
+To use in-memory storage, you need to install following package from NuGet:
+PM> Install-Package DotNetCore.CAP.InMemoryStorage
+
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseInMemoryStorage();
+ // x.UseXXX ...
+ });
+}
+
CAP will clean every 5 minutes Successful messages in memory.
+In-Memory Storage does not support Transaction mode to send messages.
+ + + + + + + + + + +MongoDB is a cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with schema.
+CAP supports MongoDB since version 2.3 .
+MongoDB supports ACID transactions since version 4.0, so CAP only supports MongoDB above 4.0, and MongoDB needs to be deployed as a cluster, because MongoDB's ACID transaction requires a cluster to be used.
+For a quick development of the MongoDB 4.0+ cluster for the development environment, you can refer to this article.
+To use MongoDB storage, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.MongoDB
+
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseMongoDB(opt=>{
+ //MongoDBOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
DatabaseName | +Database name | +string | +cap | +
DatabaseConnection | +Database connection string | +string | +mongodb://localhost:27017 | +
ReceivedCollection | +Database received message collection name | +string | +cap.received | +
PublishedCollection | +Database published message collection name | +string | +cap.published | +
The following example shows how to leverage CAP and MongoDB for local transaction integration.
+//NOTE: Before your test, your need to create database and collection at first.
+// Mongo can't create databases and collections in transactions automatic,
+// so you need to create them separately, simulating a record insert
+// will automatically create.
+
+// var mycollection = _client.GetDatabase("test")
+// .GetCollection<BsonDocument>("test.collection");
+// mycollection.InsertOne(new BsonDocument { { "test", "test" } });
+
+using (var session = _client.StartTransaction(_capBus, autoCommit: false))
+{
+ var collection = _client.GetDatabase("test")
+ .GetCollection<BsonDocument>("test.collection");
+
+ collection.InsertOne(session, new BsonDocument { { "hello", "world" } });
+
+ _capBus.Publish("sample.rabbitmq.mongodb", DateTime.Now);
+
+ session.CommitTransaction();
+}
+
MySQL is an open-source relational database management system. CAP supports MySQL database.
+To use MySQL storage, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.MySql
+
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseMySql(opt=>{
+ //MySqlOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
TableNamePrefix | +CAP table name prefix | +string | +cap | +
ConnectionString | +Database connection string | +string | +null | +
private readonly ICapPublisher _capBus;
+
+using (var connection = new MySqlConnection(AppDbContext.ConnectionString))
+{
+ using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))
+ {
+ //your business code
+ connection.Execute("insert into test(name) values('test')",
+ transaction: (IDbTransaction)transaction.DbTransaction);
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ transaction.Commit();
+ }
+}
+
private readonly ICapPublisher _capBus;
+
+using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))
+{
+ dbContext.Persons.Add(new Person() { Name = "ef.transaction" });
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ dbContext.SaveChanges();
+ trans.Commit();
+}
+
PostgreSQL is an open-source relational database management system. CAP supports PostgreSQL database.
+To use PostgreSQL storage, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.PostgreSql
+
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UsePostgreSql(opt=>{
+ //PostgreSqlOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Schema | +Database schema | +string | +cap | +
ConnectionString | +Database connection string | +string | ++ |
DataSource | +Data source | +NpgsqlDataSource | ++ |
private readonly ICapPublisher _capBus;
+
+using (var connection = new NpgsqlConnection("ConnectionString"))
+{
+ using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))
+ {
+ //your business code
+ connection.Execute("insert into test(name) values('test')",
+ transaction: (IDbTransaction)transaction.DbTransaction);
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ transaction.Commit();
+ }
+}
+
private readonly ICapPublisher _capBus;
+
+using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))
+{
+ dbContext.Persons.Add(new Person() { Name = "ef.transaction" });
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ dbContext.SaveChanges();
+ trans.Commit();
+}
+
SQL Server is a relational database management system developed by Microsoft. CAP supports SQL Server database.
+Warning
+We currently use Microsoft.Data.SqlClient
as the database driver, which is the future of SQL Server drivers, and we have abandoned System.Data.SqlClient
, we suggest that you switch to.
To use SQL Server storage, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.SqlServer
+
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseSqlServer(opt=>{
+ //SqlServerOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Schema | +Database schema | +string | +Cap | +
ConnectionString | +Database connection string | +string | ++ |
private readonly ICapPublisher _capBus;
+
+using (var connection = new SqlConnection("ConnectionString"))
+{
+ using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))
+ {
+ //your business code
+ connection.Execute("insert into test(name) values('test')",
+ transaction: (IDbTransaction)transaction.DbTransaction);
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ transaction.Commit();
+ }
+}
+
private readonly ICapPublisher _capBus;
+
+using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))
+{
+ dbContext.Persons.Add(new Person() { Name = "ef.transaction" });
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ dbContext.SaveChanges();
+ trans.Commit();
+}
+
AWS SQS is a fully managed message queuing service that enables you to decouple and scale microservices, distributed systems, and serverless applications.
+AWS SNS is a highly available, durable, secure, fully managed pub/sub messaging service that enables you to decouple microservices, distributed systems, and serverless applications.
+Because CAP works based on the topic pattern, it needs to use AWS SNS, which simplifies the publish and subscribe architecture of messages.
+When CAP startups, all subscription names will be registered as SNS topics, and you will see a list of all registered topics in the management console.
+SNS does not support use of symbols such as .
:
as the name of the topic, so we replaced it. We replaced .
with -
and :
with _
Precautions
+Amazon SNS currently allows maximum size of published messages to be 256KB
+For example, you have the following two subscriber methods in your current project
+[CapSubscribe("sample.sns.foo")]
+public void TestFoo(DateTime value)
+{
+}
+
+[CapSubscribe("sample.sns.bar")]
+public void TestBar(DateTime value)
+{
+}
+
For each consumer group, CAP will create a corresponding SQS queue, the name of the queue is the name of the DefaultGroup
in the configuration options, and the queue type is Standard.
The SQS queue will subscribe to Topic in SNS, as shown below:
+ +Precautions
+Due to the limitation of AWS SNS, when you remove the subscription method, CAP will not delete topics or queues on AWS SNS or SQS, you need to delete them manually.
+To use AWS SQS as the transport, you need to install the packages from NuGet:
+Install-Package DotNetCore.CAP.AmazonSQS
+
Next, add configuration items to the ConfigureServices
method of Startup.cs
:
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseAmazonSQS(opt=>
+ {
+ //AmazonSQSOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
The SQS configuration parameters provided directly by the CAP:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Region | +AWS Region | +Amazon.RegionEndpoint | ++ |
Credentials | +AWS AK SK Information | +Amazon.Runtime.AWSCredentials | ++ |
If your project runs in AWS EC2, you don't need to set Credentials, you can directly apply IAM policy for EC2.
+Credentials requires the SNS,SQS IAM policy.
+ + + + + + + + + + +Microsoft Azure Service Bus is a fully managed enterprise integration message broker. Service Bus is most commonly used to decouple applications and services from each other, and is a reliable and secure platform for asynchronous data and state transfer.
+Azure services can be used in CAP as a message transporter.
+Requirement
+For the Service Bus pricing layer, CAP requires "standard" or "advanced" to support Topic functionality.
+To use Azure Service Bus as a message transport, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.AzureServiceBus
+
Next, add configuration items to the ConfigureServices
method of Startup.cs
:
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseAzureServiceBus(opt=>
+ {
+ //AzureServiceBusOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
The AzureServiceBus configuration options provided directly by the CAP:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
ConnectionString | +Endpoint address | +string | ++ |
TopicPath | +Topic entity path | +string | +cap | +
EnableSessions | +Enable Service bus sessions | +bool | +false | +
MaxConcurrentSessions | +The maximum number of concurrent sessions that the processor can handle. Not applicable when EnableSessions is false. | +int | +8 | +
SessionIdleTimeout | +The maximum time to wait for a new message before the session is closed. If not specified, 60 seconds will be used by Azure Service Bus. | +TimeSpan | +null | +
SubscriptionAutoDeleteOnIdle | +Automatically delete subscription after a certain idle interval. | +TimeSpan | +TimeSpan.MaxValue | +
SubscriptionMessageLockDuration | +The amount of time the message is locked by a given receiver so that no other receiver receives the same message. | +TimeSpan | +60 seconds | +
SubscriptionDefaultMessageTimeToLive | +The default message time to live value for a subscription. This is the duration after which the message expires. | +TimeSpan | +TimeSpan.MaxValue | +
SubscriptionMaxDeliveryCount | +The maximum number of times a message is delivered to the subscription before it is dead-lettered. | +int | +10 | +
MaxAutoLockRenewalDuration | +The maximum duration within which the lock will be renewed automatically. This value should be greater than the longest message lock duration. | +TimeSpan | +5 minutes | +
ManagementTokenProvider | +Token provider | +ITokenProvider | +null | +
AutoCompleteMessages | +Gets a value that indicates whether the processor should automatically complete messages after the message handler has completed processing | +bool | +true | +
CustomHeadersBuilder | +Adds custom and/or mandatory Headers for incoming messages from heterogeneous systems. | +Func<Message, IServiceProvider, List<KeyValuePair<string, string>>>? |
+null | +
Namespace | +Namespace of Servicebus , Needs to be set when using with TokenCredential Property | +string | +null | +
DefaultCorrelationHeaders | +Adds additional correlation properties to all correlation filters. | +IDictionary |
+Dictionary |
+
SQLFilters | +Custom SQL Filters by name and expression on Topic Subscribtion | +List |
+null | +
When sessions are enabled (see EnableSessions
option above), every message sent will have a session id. To control the session id, include
+an extra header with name AzureServiceBusHeaders.SessionId
when publishing events:
ICapPublisher capBus = ...;
+string yourEventName = ...;
+YourEventType yourEvent = ...;
+
+Dictionary<string, string> extraHeaders = new Dictionary<string, string>();
+extraHeaders.Add(AzureServiceBusHeaders.SessionId, <your-session-id>);
+
+capBus.Publish(yourEventName, yourEvent, extraHeaders);
+
If no session id header is present, the message id will be used as the session id.
+Sometimes you might want to listen to a message that was published by an external system. In this case, you need to add a set of two mandatory headers for CAP compatibility as shown below.
+c.UseAzureServiceBus(asb =>
+{
+ asb.ConnectionString = ...
+ asb.CustomHeadersBuilder = (msg, sp) =>
+ [
+ new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),
+ new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)
+ ];
+});
+
You can set SQL filters on subscribtion level to get desired messages and not to have custom logic on business side. +More about Azure Service Bus SQL FILTERS - Link
+SQLFilters is List Of KeyValuePairc.UseAzureServiceBus(asb =>
+{
+ asb.ConnectionString = ...
+ asb.SQLFilters = new List<KeyValuePair<string, string>> {
+
+ new KeyValuePair<string,string>("IOTFilter","FromIOTHub='true'"),//The message will be handled if ApplicationProperties contains IOTFilter and value is true
+ new KeyValuePair<string,string>("SequenceFilter","sys.enqueuedSequenceNumber >= 300")
+ };
+});
+
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.
+CAP supports several transport methods:
+🏳🌈 | +RabbitMQ | +Kafka | +Azure Service Bus | +In-Memory | +
---|---|---|---|---|
Positioning | +Reliable message transmission | +Real time data processing | +Cloud | +In-Memory, testing | +
Distributed | +✔ | +✔ | +✔ | +❌ | +
Persistence | +✔ | +✔ | +✔ | +❌ | +
Performance | +Medium | +High | +Medium | +High | +
+++
Azure Service Bus
vsRabbitMQ
:
+http://geekswithblogs.net/michaelstephenson/archive/2012/08/12/150399.aspx+
Kafka
vsRabbitMQ
:
+https://stackoverflow.com/questions/42151544/is-there-any-reason-to-use-rabbitmq-over-kafka
Thanks to the community for supporting CAP, the following is the implementation of community-supported transport
+ActiveMQ (@Lukas Zhang): https://github.com/lukazh
+RedisMQ (@木木) https://github.com/difudotnet/CAP.RedisMQ.Extensions
+ZeroMQ (@maikebing): https://github.com/maikebing/CAP.Extensions
+MQTT (@john jiang): https://github.com/jinzaz/jinzaz.CAP.MQTT
+In Memory Queue is a memory-based message queue provided by Community.
+To use In Memory Queue as a message transporter, you need to install the following package from NuGet:
+PM> Install-Package Savorboard.CAP.InMemoryMessageQueue
+
Next, add configuration options to the ConfigureServices
method of Startup.cs
:
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseInMemoryMessageQueue();
+ // x.UseXXX ...
+ });
+}
+
Apache Kafka® is an open-source stream-processing software platform developed by LinkedIn and donated to the Apache Software Foundation, written in Scala and Java.
+Kafka® can be used in CAP as a message transporter.
+To use Kafka transporter, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.Kafka
+
Then you can add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseKafka(opt=>{
+ //KafkaOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
The Kafka configuration parameters provided directly by the CAP:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Servers | +Broker server address | +string | ++ |
ConnectionPoolSize | +connection pool size | +int | +10 | +
CustomHeadersBuilder | +Custom subscribe headers | +Func<> | +N/A | +
When the message sent from a heterogeneous system, because of the CAP needs to define additional headers, so an exception will occur at this time. By providing this parameter to set the custom headersn to make the subscriber works.
+You can find the description of heterogeneous system integration here.
+Sometimes, if you want to get additional context information from Broker, you can also add it through this option. For example, add information such as Offset or Partition.
+Example:
+x.UseKafka(opt =>
+{
+ //...
+
+ opt.CustomHeadersBuilder = (kafkaResult,sp) => new List<KeyValuePair<string, string>>
+ {
+ new KeyValuePair<string, string>("my.kafka.offset", kafkaResult.Offset.ToString()),
+ new KeyValuePair<string, string>("my.kafka.partition", kafkaResult.Partition.ToString())
+ };
+});
+
Then you can get the header you added by this way:
+[CapSubscribe("sample.kafka.postgrsql")]
+public void HeadersTest(DateTime value, [FromCap]CapHeader header)
+{
+ var offset = header["my.kafka.offset"];
+ var partition = header["my.kafka.partition"];
+}
+
If you need more native Kakfa related configuration options, you can set them in the MainConfig
configuration option:
services.AddCap(capOptions =>
+{
+ capOptions.UseKafka(kafkaOption=>
+ {
+ // kafka options.
+ // kafkaOptions.MainConfig.Add("", "");
+ });
+});
+
MainConfig
is a configuration dictionary, you can find a list of supported configuration options through the following link.
https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
+ + + + + + + + + + +NATS is a simple, secure and performant communications system for digital systems, services and devices. NATS is part of the Cloud Native Computing Foundation (CNCF).
+Warning
+Since version 5.2+, CAP's relevant features have been implemented based on JetStream, so it needs to be explicitly enabled on the server.
+You need to enable JetStream by specifying the --jetstream
parameter when starting the NATS Server in order to use CAP properly.
To use NATS transporter, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.NATS
+
Then you can add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddCap(capOptions =>
+ {
+ capOptions.UseNATS(natsOptions=>{
+ //NATS Options
+ });
+ });
+}
+
NATS configuration parameters provided directly by the CAP:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Options | +NATS client configuration | +Options | +Options | +
Servers | +Server url/urls used to connect to the NATs server. | +string | +NULL | +
ConnectionPoolSize | +number of connections pool | +uint | +10 | +
DeliverPolicy | +The point in the stream to receive messages from (⚠️ Removed from version 8.1.0, use ConsumerOptions instead.) |
+enum | +DeliverPolicy.New | +
StreamOptions | +🆕 Stream configuration | +Action | +NULL | +
ConsumerOptions | +🆕 Consumer configuration | +Action | +NULL | +
If you need more native NATS related configuration options, you can set them in the Options
option:
services.AddCap(capOptions =>
+{
+ capOptions.UseNATS(natsOptions=>
+ {
+ // NATS options.
+ natsOptions.Options.Url="";
+ });
+});
+
Options
is a NATS.Client ConfigurationOptions , you can find more details through this link
Apache Pulsar is a cloud-native, distributed messaging and streaming platform originally created at Yahoo! and now a top-level Apache Software Foundation project.
+Pulsar can be used in CAP as a message transporter.
+To use Pulsar transporter, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.Pulsar
+
Then you can add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UsePulsar(opt => {
+ //Pulsar options
+ });
+ // x.UseXXX ...
+ });
+}
+
The Pulsar configuration parameters provided directly by the CAP:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
ServiceUrl | +Broker server address | +string | ++ |
TlsOptions | +Tls configuration | +object | ++ |
RabbitMQ is an open-source message-broker software that originally implemented the Advanced Message Queuing Protocol and has since been extended with a plug-in architecture to support Streaming Text Oriented Messaging Protocol, Message Queuing Telemetry Transport, and other protocols.
+RabbitMQ can be used in CAP as a message transporter.
+Notes
+When using RabbitMQ, the consumer integrated with the CAP application will automatically create a persistent queue after it is started for the first time. Subsequent messages will be normally transmitted to the queue and consumed. +However, if you have never started the consumer, the queue will not be created. In this case, if you publish messages first, RabbitMQ Exchange will discard the messages received directly until the consumer is started and the queue is created.
+To use RabbitMQ transporter, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.RabbitMQ
+
Next, add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseRabbitMQ(opt=>
+ {
+ //RabbitMQOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
The RabbitMQ configuration parameters provided directly by CAP:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
HostName | +Broker host address | +string | +localhost | +
UserName | +Broker user name | +string | +guest | +
Password | +Broker password | +string | +guest | +
VirtualHost | +Broker virtual host | +string | +/ | +
Port | +Port | +int | +-1 | +
ExchangeName | +Default exchange name | +string | +cap.default.topic | +
QueueArguments | +Extra queue x-arguments |
+QueueArgumentsOptions | +N/A | +
ConnectionFactoryOptions | +RabbitMQClient native connection options | +ConnectionFactory | +N/A | +
CustomHeadersBuilder | +Custom subscribe headers | +See the blow | +N/A | +
PublishConfirms | +Enable publish confirms | +bool | +false | +
BasicQosOptions | +Specify Qos of message prefetch | +BasicQos | +N/A | +
If you need more native ConnectionFactory
configuration options, you can set it by 'ConnectionFactoryOptions' option:
services.AddCap(x =>
+{
+ x.UseRabbitMQ(o =>
+ {
+ o.HostName = "localhost";
+ o.ConnectionFactoryOptions = opt => {
+ //rabbitmq client ConnectionFactory config
+ };
+ });
+});
+
When the message sent from the RabbitMQ management console or a heterogeneous system, because of the CAP needs to define additional headers, so an exception will occur at this time. By providing this parameter to set the custom headersn to make the subscriber works.
+You can find the description of Header Information here.
+Example:
+x.UseRabbitMQ(aa =>
+{
+ aa.CustomHeadersBuilder = (msg, sp) =>
+ [
+ new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),
+ new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)
+ ];
+});
+
using comma split connection string, like this:
+x=> x.UseRabbitMQ("localhost:5672,localhost:5673,localhost:5674")
+
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker.
+Redis Stream is a new data type introduced with Redis 5.0, which models a log data structure in a more abstract way with an append only data structure.
+Redis Streams can be used in CAP as a message transporter.
+To use Redis Streams transporter, you need to install the following package from NuGet:
+PM> Install-Package DotNetCore.CAP.RedisStreams
+
Then you can add configuration items to the ConfigureServices
method of Startup.cs
.
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddCap(capOptions =>
+ {
+ capOptions.UseRedis(redisOptions=>{
+ //redisOptions
+ });
+ });
+}
+
Redis configuration parameters provided directly by the CAP:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Configuration | +redis connection configuration (StackExchange.Redis) | +ConfigurationOptions | +ConfigurationOptions | +
StreamEntriesCount | +number of entries returned from a stream while reading | +uint | +10 | +
ConnectionPoolSize | +number of connections pool | +uint | +10 | +
If you need more native Redis related configuration options, you can set them in the Configuration
option:
services.AddCap(capOptions =>
+{
+ capOptions.UseRedis(redisOptions=>
+ {
+ // redis options.
+ redisOptions.Configuration.EndPoints.Add(IPAddress.Loopback, 0);
+ });
+});
+
Configuration
is a StackExchange.Redis ConfigurationOptions , you can find more details through this link
Since redis streams does not have the feature of deletes all messages that already acknowledged by all groups issue , so you need to consider if using a script to perform the deletion regularly.
+ + + + + + + + + + +默认情况下,你在向DI容器中注册CAP服务的时候指定此配置。
+services.AddCap(config=> {
+ // config.XXX
+});
+
其中 services
代表的是 IServiceCollection
接口对象,它位于 Microsoft.Extensions.DependencyInjection
下面。
最简单的回答就是,至少你要配置一个传输器和一个存储,如果你想快速开始你可以使用下面的配置:
+services.AddCap(config =>
+{
+ config.UseInMemoryMessageQueue(); //需要引用 Savorboard.CAP.InMemoryMessageQueue 包
+ config.UseInMemoryStorage();
+});
+
有关具体的传输器配置和存储配置,你可以查看 Transports 章节和 Persistent 章节中具体组件提供的配置项。
+在 AddCap
中 CapOptions
对象是用来存储配置相关信息,默认情况下它们都具有一些默认值,有些时候你可能需要自定义。
默认值:cap.queue.{程序集名称}
+默认的消费者组的名字,在不同的 Transports 中对应不同的名字,可以通过自定义此值来自定义不同 Transports 中的名字,以便于查看。
+Mapping
+在 RabbitMQ 中映射到 Queue Names。
+在 Apache Kafka 中映射到 Consumer Group Id。
+在 Azure Service Bus 中映射到 Subscription Name。
+在 NATS 中映射到 Queue Group Name.
+在 Redis Streams 中映射到 Consumer Group.
默认值:Null
+为订阅 Group 统一添加前缀。 https://github.com/dotnetcore/CAP/pull/780
+默认值: Null
+为 Topic 统一添加前缀。 https://github.com/dotnetcore/CAP/pull/780
+默认值:v1
+用于给消息指定版本来隔离不同版本服务的消息,常用于A/B测试或者多服务版本的场景。以下是其应用场景:
+业务快速迭代,需要向前兼容
+由于业务的快速迭代,在各个服务集成的过程中,消息的数据结构并不是固定不变的,有些时候我们为了适应新引入的需求,会添加或者修改一些数据结构。如果你是一套全新的系统这没有什么问题,但是如果你的系统已经部署到生产环境了并且正在服务客户,这就会导致新的功能在上线的时候和旧的数据结构发生不兼容,那么这些改变可能会导致出现严重的问题,要想解决这个问题,只能把消息队列和持久化的消息全部清空,然后才能启动应用程序,这对于生产环境来说显然是致命的。
+多个版本的服务端
+有些时候,App的服务端需要提供多套接口,来支持不同版本的App,这些不同版本的App相同的接口和服务端交互的数据结构可能是不一样的,所以通常情况下服务端提供不用的路由地址来适配不同版本的App调用。
+不同实例,使用相同的持久化表/集合
+希望多个不同实例的程序可以公用相同的数据库,在 2.4 之前的版本,我们可以通过指定不同的表名来隔离不同实例的数据库表,即在CAP配置的时候通过配置不同的表名前缀来实现。
+++查看博客来了解更多关于 Version 的信息: https://www.cnblogs.com/savorboard/p/cap-2-4.html
+
默认值:60 秒
+在消息发送的时候,如果发送失败,CAP将会对消息进行重试,此配置项用来配置每次重试的间隔时间。
+在消息消费的过程中,如果消费失败,CAP将会对消息进行重试消费,此配置项用来配置每次重试的间隔时间。
+重试 & 间隔
+在默认情况下,重试将在发送和消费消息失败的 FallbackWindowLookbackSeconds(4分钟后) 开始,这是为了避免设置消息状态延迟导致可能出现的问题。
+发送和消费消息的过程中失败会立即重试 3 次,在 3 次以后将进入重试轮询,此时 FailedRetryInterval 配置才会生效。
多实例并发重试
+我们在7.1.0版本中引入了基于数据库的分布式锁以应对在多个实例下对数据库重试的并发数据获取问题,你需要显式配置 UseStorageLock
为 true。
++默认值: false
+
如果设置为true,我们将使用基于数据库的分布式锁以应对重试进程在多个实例下对数据库数据的并发获取问题。这将会在数据库生成 cap.lock 表。
+++默认值:1
+
消费者线程并行处理消息的线程数,当这个值大于1时,将不能保证消息执行的顺序。
+++默认值:300 秒
+
收集器删除已经过期消息的时间间隔。
+++默认值:50
+
重试的最大次数。当达到此设置值时,将不会再继续重试,通过改变此参数来设置重试的最大次数。
+++默认值:240 秒
+
配置重试处理器拾取 Scheduled
或 Failed
状态消息的回退时间窗。
++默认值:NULL
+
类型:Action<FailedInfo>
重试阈值的失败回调。当重试达到 FailedRetryCount 设置的值的时候,将调用此 Action 回调,你可以通过指定此回调来接收失败达到最大的通知,以做出人工介入。例如发送邮件或者短信。
+++默认值:24*3600 秒(1天后)
+
成功消息的过期时间(秒)。 当消息发送或者消费成功时候,在时间达到 SucceedMessageExpiredAfter
秒时候将会从 Persistent 中删除,你可以通过指定此值来设置过期的时间。
++默认值:15*24*3600 秒(15天后)
+
失败消息的过期时间(秒)。 当消息发送或者消费失败时候,在时间达到 FailedMessageExpiredAfter
秒时候将会从 Persistent 中删除,你可以通过指定此值来设置过期的时间。
++默认值: false
+
默认情况下,CAP会将所有消费者组的消息都先放置到内存同一个Channel中,然后线性处理。
+如果设置为 true,则每个消费者组都会根据 ConsumerThreadCount
设置的值创建单独的线程进行处理。
在同时配合使用 EnableConsumerPrefetch
时,请参考 issue #1399 以清晰其预期行为。
++默认值: false, 在 7.0 版本之前默认行为 true
+
该配置项已被重命名为 EnableSubscriberParallelExecute
,请使用新选项。
++默认值: false
+
如果设置为 true
,CAP将提前从Broker拉取一批消息置于内存缓冲区,然后执行订阅方法;当订阅方法执行完成后,拉取下一批消息至于缓冲区然后执行。
注意事项
+设置为 true 可能会产生一些问题,当订阅方法执行过慢耗时太久时,会导致重试线程拾取到还未执行的的消息。重试线程默认拾取4分钟前(FallbackWindowLookbackSeconds 配置项)的消息,也就是说如果消费端积压了超过4分钟(FallbackWindowLookbackSeconds 配置项)的消息就会被重新拾取到再次执行
+++Default:
+Environment.ProcessorCount
当启用 EnableSubscriberParallelExecute
时, 可通过此参数执行并行处理的线程数,默认值为处理器个数。
++Default: 1
+
当启用 EnableSubscriberParallelExecute
时, 通过此参数设置缓冲区和线程数的因子系数,也就是缓冲区大小等于 SubscriberParallelExecuteThreadCount
乘 SubscriberParallelExecuteBufferFactor
.
++默认值: false
+
默认情况下,发送的消息都先放置到内存同一个Channel中,然后线性处理。 +如果设置为 true,则发送消息的任务将由.NET线程池并行处理,这会大大提高发送的速度。
+ + + + + + + + + + +从 5.1.0 版本后,我们引入了对订阅者过滤器的支持,以使在某些场景(如事务处理,日志记录等)中变得容易。
+创建一个过滤器类,并继承 SubscribeFilter
抽象类。
public class MyCapFilter: SubscribeFilter
+{
+ public override Task OnSubscribeExecutingAsync(ExecutingContext context)
+ {
+ // 订阅方法执行前
+ }
+
+ public override Task OnSubscribeExecutedAsync(ExecutedContext context)
+ {
+ // 订阅方法执行后
+ }
+
+ public override Task OnSubscribeExceptionAsync(ExceptionContext context)
+ {
+ // 订阅方法执行异常
+ }
+}
+
在一些场景中,如果想终止订阅者方法执行,可以在 OnSubscribeExecutingAsync
中抛出异常,并且在 OnSubscribeExceptionAsync
中选择忽略该异常。
通过在 ExceptionContext
中设置 context.ExceptionHandled = true
来忽略异常。
public override Task OnSubscribeExceptionAsync(ExceptionContext context)
+{
+ context.ExceptionHandled = true;
+}
+
services.AddCap(opt =>
+{
+ // ***
+}.AddSubscribeFilter<MyCapFilter>();
+
目前, 我们还不支持同时添加多个过滤器。
+过滤器中使用 AsyncLocal 的问题
+我们不建议在过滤器中使用AsyncLocal,因为过滤器的生命周期为Scoped,所以直接定义临时变量即可在整个执行周期内共享变量值。 +然后,如果由于一些你无法控制的原因要使用,由于AsyncLocal的设计问题,则可将异步过滤器作为同步使用,也就是继承的方法构造中不添加 async 关键字。
+幂等性(你可以在Wikipedia读到关于幂等性的定义),当我们谈论幂等时,一般是指可以重复处理传递的消息,而不会产生意外的结果。
+在说幂等性之前,我们先来说下关于消费端的消息交付。
+由于CAP不是使用的 MS DTC 或其他类型的2PC分布式事务机制,所以存在至少消息严格交付一次的问题,具体的说在基于消息的系统中,存在以下三种可能:
+带 * 号表示在实际场景中,很难达到。
+最多一次交付保证,涵盖了保证一次或根本不接收所有消息的情况。
+这种类型的传递保证可能来自你的消息系统,你的代码按以下顺序执行其操作:
+1. 从队列移除消息
+2. 开始一个工作事务
+3. 处理消息 ( 你的代码 )
+4. 是否成功 ?
+ Yes:
+ 1. 提交工作事务
+ No:
+ 1. 回滚工作事务
+ 2. 将消息发回到队列。
+
正常情况下,他们工作的很好,工作事务将被提交。
+然而,有些时候并不能总是成功,比如在 1 之后出现异常,或者是你在将消息放回到队列中出现网络问题由或者宕机重启等情况。
+使用这个协议,你将冒着丢失消息的风险,如果可以接受,那就没有关系。
+这个交付保证包含你收到至少一次的消息,当出现故障时,可能会收到多次消息。
+它需要稍微改变我们执行步骤的顺序,它要求消息队列系统支持事务或ACK机制,比如传统的 begin-commit-rollback 协议(MSMQ是这样),或者是 receive-ack-nack 协议(RabbitMQ,Azure Service Bus等是这样的)。
+大致步骤如下:
+1. 抢占队列中的消息。
+2. 开始一个工作事务
+3. 处理消息 ( 你的代码 )
+4. 是否成功 ?
+ Yes:
+ 1. 提交工作事务
+ 2. 从队列删除消息
+ No:
+ 1. 回滚工作事务
+ 2. 从队列释放抢占的消息
+
当出现失败或者抢占消息超时的时候,我们总是能够再次接收到消息以保证我们工作事务提交成功。
+上面所说的“工作事务”并不是特指关系型数据库中的事务,这里的工作事务是一个概念,也就是说执行代码的原子性。
+比如它可以是传统的RDMS事务,也或者是 MongoDB 事务或者是一个交易等。
+在这里它代表一个执行单元,这个执行单元是一个概念性的事实以支持前面提到的仅交付一次的这种问题。
+通常,不可能做到消息的事务和工作事务来形成原子性进行提交或者回滚。
+在CAP中,我们采用的交付保证为 At Least Once。
+由于我们具有临时存储介质(数据库表),也许可以做到 At Most Once, 但是为了严格保证消息不会丢失,我们没有提供相关功能或配置。
+1、消息写入成功了,但是此时执行Consumer方法失败了
+执行Consumer方法失败的原因有非常多,我如果不知道具体的场景盲目进行重试或者不进行重试都是不正确的选择。 +举个例子:假如消费者为扣款服务,如果是执行扣款成功了,但是在写扣款日志的时候失败了,此时CAP会判断为消费者执行失败,进行重试。如果客户端自己没有保证幂等性,框架对其进行重试,这里势必会造成多次扣款出现严重后果。
+2、执行Consumer方法成功了,但是又收到了同样的消息
+此处场景也是可能存在的,假如开始的时候Consumer已经执行成功了,但是由于某种原因如 Broker 宕机恢复等,又收到了相同的消息,CAP 在收到Broker消息后会认为这个是一个新的消息,会对 Consumer再次执行,由于是新消息,此时 CAP 也是无法做到幂等的。
+3、目前的数据存储模式无法做到幂等
+由于CAP存消息的表对于成功消费的消息会于1个小时后删除,所以如果对于一些历史性消息无法做到幂等操作。 历史性指的是,假如 Broker由于某种原因维护了或者是人工处理的一些消息。
+4、业界做法
+许多基于事件驱动的框架都是要求 用户 来保证幂等性操作的,比如 ENode, RocketMQ 等等...
+从实现的角度来说,CAP可以做一些比较不严格的幂等,但是严格的幂等无法做到的。
+通常情况下,保证消息被执行多次而不会产生意外结果是很自然的一种方式是采用操作对象自带的一些幂等功能。比如:
+数据库提供的 INSERT ON DUPLICATE KEY UPDATE
或者是采取类型的程序判断行为。
另外一种处理幂等性的方式就是在消息传递的过程中传递ID,然后由单独的消息跟踪器来处理。
+比如你使用具有事务数据存储的 IMessageTracker 来跟踪消息ID,你的代码可能看起来像这样:
+readonly IMessageTracker _messageTracker;
+
+public SomeMessageHandler(IMessageTracker messageTracker)
+{
+ _messageTracker = messageTracker;
+}
+
+[CapSubscribe]
+public async Task Handle(SomeMessage message)
+{
+ if (await _messageTracker.HasProcessed(message.Id))
+ {
+ return;
+ }
+
+ // do the work here
+ // ...
+
+ // remember that this message has been processed
+ await _messageTracker.MarkAsProcessed(messageId);
+}
+
至于 IMessageTracker
的实现,可以使用诸如Redis或者数据库等存储消息Id和对应的处理状态。
使用 ICapPublisher
接口发送出去的数据称之为 Message (消息
)。
你可以阅读 quick-start 来学习如何发送和处理消息。
+消费者中使用 HTTPClient 引发的 TimeoutException
+默认情况下,如果消费者抛出 OperationCanceledException
(包括 TaskCanceledException
),我们会认为这是用户的正常行为而对异常进行忽略。如果你在消费者方法中使用 HTTPClient 并且进行了配置了Timeout配置,由于HTTP Client的设计问题,你可能需要单独对异常进行处理并重新引发非OperationCanceledException,参考 #1368
某些情况下,消费者需要返回值以告诉发布者执行结果,以便于发布者实施一些动作,通常情况下这属于补偿范围。
+你可以在消费者执行的代码中通过重新发布一个新消息来通知上游,CAP 提供了一种简单的方式来做到这一点。 你可以在发送的时候指定 callbackName
来得到消费者的执行结果,通常这仅适用于点对点的消费。以下是一个示例。
例如,在一个电商程序中,订单初始状态为 pending,当商品数量成功扣除时将状态标记为 succeeded ,否则为 failed。
+// ============= Publisher =================
+
+_capBus.Publish("place.order.qty.deducted",
+ contentObj: new { OrderId = 1234, ProductId = 23255, Qty = 1 },
+ callbackName: "place.order.mark.status");
+
+// publisher using `callbackName` to subscribe consumer result
+
+[CapSubscribe("place.order.mark.status")]
+public void MarkOrderStatus(JsonElement param)
+{
+ var orderId = param.GetProperty("OrderId").GetInt32();
+ var isSuccess = param.GetProperty("IsSuccess").GetBoolean();
+
+ if(isSuccess){
+ // mark order status to succeeded
+ }
+ else{
+ // mark order status to failed
+ }
+}
+
+// ============= Consumer ===================
+
+[CapSubscribe("place.order.qty.deducted")]
+public object DeductProductQty(JsonElement param)
+{
+ var orderId = param.GetProperty("OrderId").GetInt32();
+ var productId = param.GetProperty("ProductId").GetInt32();
+ var qty = param.GetProperty("Qty").GetInt32();
+
+ //business logic
+
+ return new { OrderId = orderId, IsSuccess = true };
+}
+
在 3.0+ 版本中,我们对消息结构进行了重构,我们利用了消息队列中消息协议中的 Header 来传输一些额外信息,以便于在 Body 中我们可以做到不需要修改或包装使用者的原始消息数据格式和内容进行发送。
+这样的做法是合理的,它有助于在异构系统中进行更好的集成,相对于以前的版本使用者不需要知道CAP内部使用的消息结构就可以完成集成工作。
+现在我们将消息划分为 Header 和 Body 来进行传输。
+Body 中的数据为用户发送的原始消息内容,也就是调用 Publish 方法发送的内容,我们不进行任何包装仅仅是序列化后传递到消息队列。
+在 Header 中,我们需要传递一些额外信息以便于CAP在收到消息时能够提取到关键特征进行操作。
+以下是在异构系统中,需要在发消息的时候向消息的Header 中写入的内容:
+键 | +类型 | +说明 | +
---|---|---|
cap-msg-id | +string | +消息Id, 由雪花算法生成,也可以是 guid | +
cap-msg-name | +string | +消息名称,即 Topic 名字 | +
cap-msg-type | +string | +消息的类型, 即 typeof(T).FullName (非必须) | +
cap-senttime | +stringg | +发送的时间 (非必须) | +
以 Java 系统发送 RabbitMQ 为例:
+Map<String, Object> headers = new HashMap<String, Object>();
+headers.put("cap-msg-id", UUID.randomUUID().toString());
+headers.put("cap-msg-name", routingKey);
+
+channel.basicPublish(exchangeName, routingKey,
+ new AMQP.BasicProperties.Builder()
+ .headers(headers)
+ .build(),
+ messageBodyBytes);
+// messageBodyBytes = "发送的json".getBytes(Charset.forName("UTF-8"))
+// 注意 messageBody 默认为 json 的 byte[],如果采用其他系列化,需要在CAP侧自定义反序列化器
+
CAP 接收到消息之后会将消息发送到 Transport, 由 Transport 进行运输。
+当你使用 ICapPublisher
接口发送时,CAP将会将消息调度到相应的 Transport中去,目前还不支持批量发送消息。
有关 Transports 的更多信息,可以查看 Transports 章节。
+CAP 接收到消息之后会将消息进行 Persistent(持久化), 有关 Persistent 的更多信息,可以查看 Persistent 章节。
+重试在整个CAP架构设计中具有重要作用,CAP 中会针对发送失败或者执行失败的消息进行重试。在整个 CAP 的设计过程中有以下几处采用的重试策略。
+1、 发送重试
+在消息发送过程中,当出现 Broker 宕机或者连接失败的情况亦或者出现异常的情况下,这个时候 CAP 会对发送的重试,第一次重试次数为 3,4分钟后以后每分钟重试一次,进行次数 +1,当总次数达到50次后,CAP将不对其进行重试。
+你可以在 CapOptions 中设置 FailedRetryCount 来调整默认重试的总次数,或使用 FailedThresholdCallback 在达到最大重试次数时收到通知。
+当失败总次数达到默认失败总次数后,就不会进行重试了,你可以在 Dashboard 中查看消息失败的原因,然后进行人工重试处理。
+2、 消费重试
+当 Consumer 接收到消息时,会执行消费者方法,在执行消费者方法出现异常时,会进行重试。这个重试策略和上面的 发送重试 是相同的。
+无论发送失败或者消费失败,我们会将异常消息同时存储到消息 header 中的 cap-exception 字段中,你可以在数据库表的 Content 字段的json中找到。
+数据库消息表中具有一个 ExpiresAt 字段表示消息的过期时间,当消息发送成功或者消费成功后,CAP 会将消息状态为 Successed 的 ExpiresAt 设置为 1天 后过期,会将消息状态为 Failed 的 ExpiresAt 设置为 15天 后过期(可通过 FailedMessageExpiredAfter 配置)。
+CAP 默认情况下会每隔**5分钟**将消息表的数据进行清理删除,避免数据量过多导致性能的降低。清理规则为 ExpiresAt 不为空并且小于当前时间的数据。 也就是说状态为Failed的消息(正常情况他们已经被重试了 50 次),如果你15天没有人工介入处理,同样会被清理掉。你可以通过 CollectorCleaningInterval 配置项来自定义间隔时间。
+ + + + + + + + + + +CAP 提供了 ISerializer
接口来支持对消息进行序列化,默认情况下我们使用 json 来对消息进行序列化处理并存储到数据库中。
public class YourSerializer: ISerializer
+{
+ Task<TransportMessage> SerializeAsync(Message message)
+ {
+
+ }
+
+ Task<Message> DeserializeAsync(TransportMessage transportMessage, Type valueType)
+ {
+
+ }
+}
+
然后将你的实现注册到容器中:
+//注册你的自定义实现
+services.AddSingleton<ISerializer, YourSerializer>();
+
+// ---
+services.AddCap
+
CAP 不直接提供开箱即用的基于 DTC 或者 2PC 的分布式事务,相反我们提供一种可以用于解决在分布式事务遇到的问题的一种解决方案。
+在分布式环境中,由于涉及通讯的开销,使用基于2PC或DTC的分布式事务将非常昂贵,在性能方面也同样如此。另外由于基于2PC或DTC的分布式事务同样受**CAP定理**的约束,当发生网络分区时它将不得不放弃可用性(CAP中的A)。
+针对于分布式事务的处理,CAP 采用的是“异步确保”这种方案。
+异步确保这种方案又叫做本地消息表,这是一种经典的方案,方案最初来源于 eBay,参考资料见段末链接。这种方案目前也是企业中使用最多的方案之一。
+相对于 TCC 或者 2PC/3PC 来说,这个方案对于分布式事务来说是最简单的,而且它是去中心化的。在TCC 或者 2PC 的方案中,必须具有事务协调器来处理每个不同服务之间的状态,而此种方案不需要事务协调器。 +另外 2PC/TCC 这种方案如果服务依赖过多,会带来管理复杂性增加和稳定性风险增大的问题。试想如果我们强依赖 10 个服务,9 个都执行成功了,最后一个执行失败了,那么是不是前面 9 个都要回滚掉?这个成本还是非常高的。
+但是,并不是说 2PC 或者 TCC 这种方案不好,因为每一种方案都有其相对优势的使用场景和优缺点,这里就不做过多介绍了。
+++ + + + + + + + + + +中文:http://www.cnblogs.com/savorboard/p/base-an-acid-alternative.html
+
+英文:http://queue.acm.org/detail.cfm?id=1394128
CAP 是一个EventBus,同时也是一个在微服务或者SOA系统中解决分布式事务问题的一个框架。它有助于创建可扩展,可靠并且易于更改的微服务系统。
+在微软的 eShop 微服务示例项目中,推荐使用 CAP 作为生产环境可用的 EventBus。
+什么是 EventBus?
+事件总线是一种机制,它允许不同的组件彼此通信而不彼此了解。 组件可以将事件发送到Eventbus,而无需知道是谁来接听或有多少其他人来接听。 组件也可以侦听Eventbus上的事件,而无需知道谁发送了事件。 这样,组件可以相互通信而无需相互依赖。 同样,很容易替换一个组件。 只要新组件了解正在发送和接收的事件,其他组件就永远不会知道.
+相对于其他的 Service Bus 或者 Event Bus, CAP 拥有自己的特色,它不要求使用者发送消息或者处理消息的时候实现或者继承任何接口,拥有非常高的灵活性。我们一直坚信约定大于配置,所以CAP使用起来非常简单,对于新手非常友好,并且拥有轻量级。
+CAP 采用模块化设计,具有高度的可扩展性。你有许多选项可以选择,包括消息队列,存储,序列化方式等,系统的许多元素内容可以替换为自定义实现。
+了解如何使用 CAP 构建微服务事件总线架构,它比直接集成消息队列提供了哪些优势,它提供了哪些开箱即用的功能。
+PM> Install-Package DotNetCore.CAP
+
以便于快速启动,我们使用基于内存的事件存储和消息队列。
+PM> Install-Package DotNetCore.CAP.InMemoryStorage
+PM> Install-Package Savorboard.CAP.InMemoryMessageQueue
+
在 Startup.cs
中,添加以下配置:
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddCap(x =>
+ {
+ x.UseInMemoryStorage();
+ x.UseInMemoryMessageQueue();
+ });
+}
+
public class PublishController : Controller
+{
+ [Route("~/send")]
+ public IActionResult SendMessage([FromServices]ICapPublisher capBus)
+ {
+ capBus.Publish("test.show.time", DateTime.Now);
+
+ return Ok();
+ }
+}
+
public class PublishController : Controller
+{
+ [Route("~/send/delay")]
+ public IActionResult SendDelayMessage([FromServices]ICapPublisher capBus)
+ {
+ capBus.PublishDelay(TimeSpan.FromSeconds(100),"test.show.time", DateTime.Now);
+
+ return Ok();
+ }
+}
+
var header = new Dictionary<string, string>()
+{
+ ["my.header.first"] = "first",
+ ["my.header.second"] = "second"
+};
+
+capBus.Publish("test.show.time", DateTime.Now, header);
+
public class ConsumerController : Controller
+{
+ [NonAction]
+ [CapSubscribe("test.show.time")]
+ public void ReceiveMessage(DateTime time)
+ {
+ Console.WriteLine("message time is:" + time);
+ }
+}
+
[CapSubscribe("test.show.time")]
+public void ReceiveMessage(DateTime time, [FromCap]CapHeader header)
+{
+ Console.WriteLine("message time is:" + time);
+ Console.WriteLine("message firset header :" + header["my.header.first"]);
+ Console.WriteLine("message second header :" + header["my.header.second"]);
+}
+
相对于直接集成消息队列,异步消息传递最强大的优势之一是可靠性,系统的一个部分中的故障不会传播,也不会导致整个系统崩溃。 在 CAP 内部会将消息进行存储,以保证消息的可靠性,并配合重试等策略以达到各个服务之间的数据最终一致性。
+ + + + + + + + + + +Consul 是一个分布式服务网格,用于跨任何运行时平台和公共或私有云连接,保护和配置服务。
+CAP的 Dashboard 使用 Consul 作为服务发现来显示其他节点的数据,然后你就在任意节点的 Dashboard 中切换到 Servers 页面看到其他的节点。
+ +通过点击 Switch 按钮来切换到其他的节点看到其他节点的数据,而不必访问很多地址来分别查看。
+以下是一个配置示例, 你需要在每个节点分别配置:
+services.AddCap(x =>
+{
+ x.UseMySql(Configuration.GetValue<string>("ConnectionString"));
+ x.UseRabbitMQ("localhost");
+ x.UseDashboard();
+ x.UseConsulDiscovery(_ =>
+ {
+ _.DiscoveryServerHostName = "localhost";
+ _.DiscoveryServerPort = 8500;
+ _.CurrentNodeHostName = Configuration.GetValue<string>("ASPNETCORE_HOSTNAME");
+ _.CurrentNodePort = Configuration.GetValue<int>("ASPNETCORE_PORT");
+ _.NodeId = Configuration.GetValue<string>("NodeId");
+ _.NodeName = Configuration.GetValue<string>("NodeName");
+ });
+});
+
Consul 1.6.2:
+consul agent -dev
+
Windows 10, ASP.NET Core 3.1:
+set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5001&& dotnet run --urls=http://localhost:5001 NodeId=1 NodeName=CAP-1 ConnectionString="Server=localhost;Database=aaa;UserId=xxx;Password=xxx;"
+set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5002&& dotnet run --urls=http://localhost:5002 NodeId=2 NodeName=CAP-2 ConnectionString="Server=localhost;Database=bbb;UserId=xxx;Password=xxx;"
+
CAP 原生提供了 Dashboard 供查看消息,利用 Dashboard 提供的功能可以很方便的查看和管理消息。
+使用限制
+Dashboard 只支持在 ASP.NET Core 中使用,不支持控制台应用(Console App)
+首先,你需要安装Dashboard的 NuGet 包。
+PM> Install-Package DotNetCore.CAP.Dashboard
+
然后,在配置中添加如下代码:
+services.AddCap(x =>
+{
+ //...
+
+ // Register Dashboard
+ x.UseDashboard();
+});
+
默认情况下,你可以访问 http://localhost:xxx/cap
这个地址打开Dashboard。
默认值:N/A
+当位于代理后时,通过配置此参数可以指定代理请求前缀。
+默认值:'/cap'
+你可以通过修改此配置项来更改Dashboard的访问路径。
+默认值:2000 毫秒
+此配置项用来配置Dashboard 前端 获取状态接口(/stats)的轮询时间
+++Default: true
+
显式允许对 CAP 仪表板 API 进行匿名访问,当启用ASP.NET Core 全局授权筛选器请启用 AllowAnonymous。
+++Default: null.
+
Dashboard 的授权策略。 需设置 AllowAnonymousExplicit
为 false。
从版本 8.0.0 开始,CAP 仪表板利用 ASP.NET Core 身份验证机制,允许通过自定义授权策略和 ASP.NET Core 身份验证和授权中间件进行扩展,以授权仪表板访问。 有关 ASP.NET Core 身份验证内部结构的更多详细信息,请查看官方文档.
+您可以在示例项目 Sample.Dashboard.Auth
中查看示例代码。
services.AddCap(cap =>
+ {
+ cap.UseDashboard(d =>
+ {
+ d.AllowAnonymousExplicit = true;
+ });
+ cap.UseInMemoryStorage();
+ cap.UseInMemoryMessageQueue();
+ });
+
services
+ .AddAuthorization(options =>
+ {
+ options.AddPolicy(DashboardAuthorizationPolicy, policy => policy
+ .AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)
+ .RequireAuthenticatedUser());
+ })
+ .AddAuthentication(opt => opt.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
+ .AddCookie()
+ .AddOpenIdConnect(options =>
+ {
+ ...
+ });
+
+ services.AddCap(cap =>
+ {
+ cap.UseDashboard(d =>
+ {
+ d.AuthorizationPolicy = DashboardAuthorizationPolicy;
+ });
+ cap.UseInMemoryStorage();
+ cap.UseInMemoryMessageQueue();
+ });
+
从 8.0.0 版开始,CAP 控制面板利用 ASP.NET Core 身份验证机制,允许通过自定义授权策略和 ASP.NET Core 身份验证与授权中间件进行扩展。有关 ASP.NET Core 身份验证内部机制的更多详情,请查阅 官方文档。
+您可以在示例项目 Sample.Dashboard.Auth
中查看以下示例。
services.AddCap(cap =>
+ {
+ cap.UseDashboard(d =>
+ {
+ d.AllowAnonymousExplicit = true;
+ });
+ cap.UseInMemoryStorage();
+ cap.UseInMemoryMessageQueue();
+ });
+
services
+ .AddAuthorization(options =>
+ {
+ options.AddPolicy(DashboardAuthorizationPolicy, policy => policy
+ .AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)
+ .RequireAuthenticatedUser());
+ })
+ .AddAuthentication(opt => opt.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
+ .AddCookie()
+ .AddOpenIdConnect(options =>
+ {
+ ...
+ });
+
+ services.AddCap(cap =>
+ {
+ cap.UseDashboard(d =>
+ {
+ d.AuthorizationPolicy = DashboardAuthorizationPolicy;
+ });
+ cap.UseInMemoryStorage();
+ cap.UseInMemoryMessageQueue();
+ });
+
const string MyDashboardAuthenticationPolicy = "MyDashboardAuthenticationPolicy";
+
+services.AddAuthorization(options =>
+ {
+ options.AddPolicy(MyDashboardAuthenticationPolicy, policy => policy
+ .AddAuthenticationSchemes(MyDashboardAuthenticationSchemeDefaults.Scheme)
+ .RequireAuthenticatedUser());
+ })
+ .AddAuthentication()
+ .AddScheme<MyDashboardAuthenticationSchemeOptions, MyDashboardAuthenticationHandler>(MyDashboardAuthenticationSchemeDefaults.Scheme,null);
+
+services.AddCap(cap =>
+ {
+ cap.UseDashboard(d =>
+ {
+ d.AuthorizationPolicy = MyDashboardAuthenticationPolicy;
+ });
+ cap.UseInMemoryStorage();
+ cap.UseInMemoryMessageQueue();
+ });
+
Diagnostics 提供一组功能使我们能够很方便的可以记录在应用程序运行期间发生的关键性操作以及他们的执行时间等,使管理员可以查找特别是生产环境中出现问题所在的根本原因。
+CAP 对 .NET DiagnosticSource
提供了支持,监听器名称为 CapDiagnosticListener
。
你可以在 DotNetCore.CAP.Diagnostics.CapDiagnosticListenerNames
类下面找到CAP已经定义的事件名称。
Diagnostics 提供对外提供的事件信息有:
+Skywalking 的 C# 客户端提供了对 CAP Diagnostics 的支持,你可以利用 SkyAPM-dotnet 来实现在 Skywalking 中追踪事件。
+尝试阅读Readme文档来在你的项目中集成它。
+ + +目前还没有实现对除了 Skywalking 的其他APM的支持,如果你想在其他 APM 中实现对 CAP 诊断事件的支持,你可以参考这里的代码来实现它:
+https://github.com/SkyAPM/SkyAPM-dotnet/tree/master/src/SkyApm.Diagnostics.CAP
+度量是指对于一个物体或是事件的某个性质给予一个数字,使其可以和其他物体或是事件的相同性质比较。度量可以是对一物理量(如长度、尺寸或容量等)的估计或测定,也可以是其他较抽象的特质。
+CAP 7.0 对 EventSource
提供了支持,计数器名称为 DotNetCore.CAP.EventCounter
。
CAP 提供了以下几个度量指标:
+dotnet-counters 是一个性能监视工具,用于临时运行状况监视和初级性能调查。 它可以观察通过 EventCounter API 或 Meter API 发布的性能计数器值。
+使用以下命令来监视CAP中的度量指标:
+dotnet-counters ps
+dotnet-counters monitor --process-id=25496 --counters=DotNetCore.CAP.EventCounter
+
其中 process-id 为 CAP 所属的进程Id。
+ +你可以配置 x.UseDashboard()
来开启仪表盘以图表的形式查看 Metrics 指标。 如下图:
在 Realtime Metric Graph 中,时间轴会随着时间实时滚动从而可以看到发布和消费消息每秒的速率,同时我们可以看到消费者执行耗时以“打点”的方式在 Y1 轴上(Y0轴为速率,Y1轴为执行耗时)。
+ + + + + + + + + + +Kubernetes,也称为 K8s,是一个开源系统,用于自动部署、扩展和管理容器化应用程序。
+我们的 Dashboard 从 7.2.0 版本开始支持 Kubernetes 作为服务发现。你可以切换到Node节点页面,然后选择命名空间,CAP会列出该命名空间下的所有Services,点击 切换 按钮后Dashboard将检测该节点的CAP服务是否可用,如果可用则会代理到切换的节点进行数据查看。
+以下是一个配置示例
+services.AddCap(x =>
+{
+ // ...
+ x.UseDashboard();
+ x.UseK8sDiscovery();
+});
+
组件将会自动检测是否处于集群内部,如果处于集群内部在需要赋予Pod Kubernetes Api 的权限。参考下一章节。
+如果你的Deployment关联的ServiceAccount没有K8s Api访问权限的话,则需要赋予 namespaces
, services
资源的 get
, list
权限。
这是一个实例yaml,首先创建一个 ServiceAccount 和 ClusterRole 并设置相关权限,然后使用 ClusterRoleBinding 进行绑定。最后在Deployment中使用 serviceAccountName: api-access
继续指定。
apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: api-access
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: ns-svc-reader
+rules:
+- apiGroups: [""]
+ resources: ["namespaces", "services"]
+ verbs: ["get", "watch", "list"]
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: read-pods
+subjects:
+- kind: ServiceAccount
+ name: api-access
+ namespace: default
+roleRef:
+ kind: ClusterRole
+ name: ns-svc-reader
+ apiGroup: rbac.authorization.k8s.io
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: api-access-deployment
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: api-access-app
+ template:
+ metadata:
+ labels:
+ app: api-access-app
+ spec:
+ serviceAccountName: api-access
+ containers:
+ - name: api-access-container
+ image: your_image
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: api-access-service
+spec:
+ selector:
+ app: api-access-app
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 80
+
你可以独立使用 Dashboard 而不需要配置CAP,此时相当于 Dashboard 可作为单独的 Pod 部署到 Kubernetes 集群中仅用作查看数据,待查看的服务不再需要配置 cap.UseK8sDiscovery()
配置项。
services.AddCapDashboardStandalone();
+
同样,你需要为此Pod配置 ServiceAccount 的访问权限。
+ + + + + + + + + + +OpenTelemetry是工具、api和sdk的集合。 使用它来检测、生成、收集和导出遥测数据(度量、日志和跟踪),以帮助您分析软件的性能和行为。
+你可以在这里找到关于如何在控制台应用或ASP.NET Core 中使用OpenTelemetry。 +在这里我们主要描述如何将CAP集成到OpenTelemetry中。
+安装CAP的OpenTelemetry包到项目中。
+dotnet add package DotNetCore.Cap.OpenTelemetry
+
OpenTelemetry 的跟踪数据来自于Diagnostics发送的诊断数据,添加 CAP Instrumentation 到 OpenTelemetry的扩展配置中会进行自动收集。
+services.AddOpenTelemetryTracing((builder) => builder
+ .AddAspNetCoreInstrumentation()
+ .AddCapInstrumentation() // <-- 添加这行
+ .AddZipkinExporter()
+);
+
以下是CAP的跟踪数据在 Zipkin 中的一个示意图:
+ + + + + + + + + + + +Castle DynamicProxy 是一个用于在运行时动态生成轻量级.NET代理的库。代理对象允许在不修改类代码的情况下截取对对象成员的调用。可以代理类和接口,但是只能拦截虚拟成员。
+Castle.DynamicProxy 可以帮助你方便的创建代理对象,代理对象可以帮助构建灵活的应用程序体系结构,因为它允许将功能透明地添加到代码中,而无需对其进行修改。例如,可以代理一个类来添加日志记录或安全检查,而无需使代码知道已添加此功能。
+下面可以看到如何在 CAP 中集成使用 Castle.DynamicProxy。
+在 集成了 CAP 的项目中安装包,有关如何集成 CAP 的文档请看这里。
+注意,Castle.DynamicProxy
这个包已经被废弃,请使用最新的 Castle.Core
包。
<PackageReference Include="Castle.Core" Version="4.4.1" />
+
可以在这里 dynamicproxy.md 找到相关的文档。
+下面为示例代码,继承 Castle 提供的 IInterceptor
接口即可:
[Serializable]
+public class MyInterceptor : IInterceptor
+{
+ public void Intercept(IInvocation invocation)
+ {
+ Console.WriteLine("Before target call");
+ try
+ {
+ invocation.Proceed();
+ }
+ catch (Exception)
+ {
+ Console.WriteLine("Target threw an exception!");
+ throw;
+ }
+ finally
+ {
+ Console.WriteLine("After target call");
+ }
+ }
+}
+
拦截器此处命名为 MyInterceptor
,你可以在其中处理你的业务逻辑,比如添加日志或其他的一些行为。
为 IServiceCollection
创建扩展,方面后续调用。
using Castle.DynamicProxy;
+
+public static class ServicesExtensions
+{
+ public static void AddProxiedSingleton<TImplementation>(this IServiceCollection services)
+ where TImplementation : class
+ {
+ services.AddSingleton(serviceProvider =>
+ {
+ var proxyGenerator = serviceProvider.GetRequiredService<ProxyGenerator>();
+ var interceptors = serviceProvider.GetServices<IInterceptor>().ToArray();
+ return proxyGenerator.CreateClassProxy<TImplementation>(interceptors);
+ });
+ }
+}
+
此处我创建了一个 Singleton 声明周期的扩展方法,建议所有 CAP 的订阅者都创建为 Singleton 即可,因为在 CAP 内部实际执行的时候也会创建一个 scope 来执行,所以无需担心资源释放问题。
+创建一个 CAP 订阅类,注意不能放在 Controller 中了。
+注意:方法需要为虚方法 virtual,才能被 Castle 重写,别搞忘了加!!!
+public class CapSubscribeService: ICapSubscribe
+{
+ [CapSubscribe("sample.rabbitmq.mysql")]
+ public virtual void Subscriber(DateTime p)
+ {
+ Console.WriteLine($@"{DateTime.Now} Subscriber invoked, Info: {p}");
+ }
+}
+
public void ConfigureServices(IServiceCollection services)
+{
+ // 添加 Castle 的代理生成器
+ services.AddSingleton(new ProxyGenerator());
+
+ // 添加第2步的自定义的拦截类,声明周期为
+ services.AddSingleton<IInterceptor, MyInterceptor>();
+
+ // 此处为上面的扩展方法, 添加 CAP 订阅 Service
+ services.AddProxiedSingleton<CapSubscribeService>();
+
+ services.AddCap(x =>
+ {
+ x.UseMySql("");
+ x.UseRabbitMQ("");
+ x.UseDashboard();
+ });
+
+ // ...
+}
+
以上就完成了所有的集成工作,可以开始进行测试了,有问题欢迎到 Github issue 反馈。
+ + + + + + + + + + +eShopOnContainers is a sample application written in C# running on .NET Core using a microservice architecture, Domain Driven Design.
+++.NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers.
+This reference application is cross-platform at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
+The architecture proposes a microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the communication protocol between the client apps and the microservices and supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus (a light message broker, to choose between RabbitMQ or Azure Service Bus, underneath) plus other features defined at the roadmap.
+
你可以在下面的地址看到如何在 eShopOnContainers 中使用 CAP。
+ + + + + + + + + + + +有没有学习和讨论 CAP 的即时通讯群组(例如腾讯 QQ 群)?
+回答: 暂时没有。与其浪费大量时间在即时通讯群组里,我更希望开发者能够培养独立思考能力,并通过查阅文档自行解决问题,甚至可以在遇到错误时创建issue或发送电子邮件。
+CAP 是否需要为生产者和消费者分别使用不同的数据库?
+回答:没有必要使用完全不同的数据库,推荐为每个程序使用一个专用数据库。
+否则,请参阅下面的问答部分。
+如何使用相同的数据库用于不同的应用程序?
+回答: 在 ConfigureServices 方法中定义表名前缀。
+代码示例:
+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
+ });
+ });
+}
+
CAP 能否不使用数据库作为事件存储?我只是想发送消息
+回答: 完全不用是不可能的,你可以使用 InMemoryStorage 。
+CAP 的目的是在微服务或 SOA 架构中确保一致性原则。该解决方案基于数据库的 ACID 特性,如果没有数据库,单纯的消息队列消息传递是没有意义的。
+如果消费者出现异常,能否回滚生产者执行的数据库语句?
+回答: 无法回滚,CAP 是最终一致性解决方案。
+可以想象您的场景是调用第三方支付。如果您正在进行第三方支付操作,在成功调用支付宝的接口后,您的代码出现错误,支付宝会回滚吗?如果不回滚,您该怎么办?CAP 的情况与此类似。
+ + + + + + + + + + +你可以在下面的地址找到相关示例代码:
+https://github.com/dotnetcore/CAP/tree/master/samples
+CAP + Aspire + Azure Service Bus + Azure SQL
+ + + + + + + + + + + +CAP 需要使用具有持久化功能的存储介质来存储事件消息,例如通过数据库或者其他NoSql设施。CAP 使用这种方式来应对一切环境或者网络异常导致消息丢失的情况,消息的可靠性是分布式事务的基石,所以在任何情况下消息都不能丢失。
+在消息进入到消息队列之前,CAP使用本地数据库表对消息进行持久化,这样可以保证当消息队列出现异常或者网络错误时候消息是没有丢失的。
+为了保证这种机制的可靠性,CAP使用和业务代码相同的数据库事务来保证业务操作和CAP的消息在持久化的过程中是强一致的。也就是说在进行消息持久化的过程中,任何一方发生异常情况数据库都会进行回滚操作。
+消息进入到消息队列之后,CAP会启动消息队列的持久化功能,我们需要说明一下在 RabbitMQ 和 Kafka 中CAP的消息是如何持久化的。
+针对于 RabbitMQ 中的消息持久化,CAP 使用的是具有消息持久化功能的消费者队列,但是这里面可能有例外情况,参加 2.2.1 章节。
+由于 Kafka 天生设计的就是使用文件进行的消息持久化,在所以在消息进入到Kafka之后,Kafka会保证消息能够正确被持久化而不丢失。
+CAP 支持以下几种具有事务支持的数据库做为存储:
+ +在 CAP 启动后,会向持久化介质中生成两个表,默认情况下名称为:Cap.Published
Cap.Received
。
Published 表结构:
+NAME | +DESCRIPTION | +TYPE | +
---|---|---|
Id | +Message Id | +int | +
Version | +Message Version | +string | +
Name | +Topic Name | +string | +
Content | +Json Content | +string | +
Added | +Added Time | +DateTime | +
ExpiresAt | +Expire time | +DateTime | +
Retries | +Retry times | +int | +
StatusName | +Status Name | +string | +
Received 表结构:
+NAME | +DESCRIPTION | +TYPE | +
---|---|---|
Id | +Message Id | +int | +
Version | +Message Version | +string | +
Name | +Topic Name | +string | +
Group | +Group Name | +string | +
Content | +Json Content | +string | +
Added | +Added Time | +DateTime | +
ExpiresAt | +Expire time | +DateTime | +
Retries | +Retry times | +int | +
StatusName | +Status Name | +string | +
Lock 表结构(可选):
+NAME | +DESCRIPTION | +TYPE | +
---|---|---|
Key | +Lock Id | +string | +
Instance | +Acquired instance of lock | +string | +
LastLockTime | +Last acquired lock time | +DateTime | +
CAP 在进行消息发送到时候,会对原始消息对象进行一个二次包装存储到 Content
字段中,以下为包装 Content 的 Message 对象数据结构:
NAME | +DESCRIPTION | +TYPE | +
---|---|---|
Id | +CAP生成的消息编号 | +string | +
Timestamp | +消息创建时间 | +string | +
Content | +内容 | +string | +
CallbackName | +回调的订阅者名称 | +string | +
其中 Id 字段,CAP 采用的 MongoDB 中的 ObjectId 分布式Id生成算法生成。
+感谢社区对CAP的支持,以下是社区支持的持久化的实现
+SQLite (@colinin) :https://github.com/colinin/DotNetCore.CAP.Sqlite
+LiteDB (@maikebing) :https://github.com/maikebing/CAP.Extensions
+SQLite & Oracle (@cocosip) :https://github.com/cocosip/CAP-Extensions
+SmartSql (@xiangxiren) :https://github.com/xiangxiren/SmartSql.CAP
+内存消息的持久化存储常用于开发和测试环境,如果使用基于内存的存储则你会失去本地事务消息可靠性保证。
+如果要使用内存存储,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.InMemoryStorage
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于内存的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseInMemoryStorage();
+ // x.UseXXX ...
+ });
+}
+
内存中的发送成功消息,CAP 将会每 5分钟 进行一次清理。
+In-Memory 存储 不支持 事务方式发送消息。
+ + + + + + + + + + +MongoDB 是一个跨平台的面向文档型的数据库程序,它被归为 NOSQL 数据库,CAP 从 2.3 版本开始支持 MongoDB 作为消息存储。
+MongoDB 从 4.0 版本开始支持 ACID 事务,所以 CAP 也只支持 4.0 以上的 MongoDB,并且 MongoDB 需要部署为集群,因为 MongoDB 的 ACID 事务需要集群才可以使用。
+有关开发环境如何快速搭建 MongoDB 4.0+ 集群,你可以我的参考 这篇文章。
+要使用 MongoDB 存储,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.MongoDB
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于内存的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseMongoDB(opt=>{
+ //MongoDBOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
DatabaseName | +数据库名称 | +string | +cap | +
DatabaseConnection | +数据库连接字符串 | +string | +mongodb://localhost:27017 | +
ReceivedCollection | +接收消息集合名称 | +string | +cap.received | +
PublishedCollection | +发送消息集合名称 | +string | +cap.published | +
下面的示例展示了如何利用 CAP 和 MongoDB 进行本地事务集成。
+//NOTE: before your test, your need to create database and collection at first
+//注意:MongoDB 不能在事务中创建数据库和集合,所以你需要单独创建它们,模拟一条记录插入则会自动创建
+//var mycollection = _client.GetDatabase("test").GetCollection<BsonDocument>("test.collection");
+//mycollection.InsertOne(new BsonDocument { { "test", "test" } });
+
+using (var session = _client.StartTransaction(_capBus, autoCommit: false))
+{
+ var collection = _client.GetDatabase("test").GetCollection<BsonDocument>("test.collection");
+ collection.InsertOne(session, new BsonDocument { { "hello", "world" } });
+
+ _capBus.Publish("sample.rabbitmq.mongodb", DateTime.Now);
+
+ session.CommitTransaction();
+}
+
MySQL 是一个开源的关系型数据库,你可以使用 MySQL 来作为 CAP 消息的持久化。
+要使用 MySQL 存储,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.MySql
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于内存的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseMySql(opt=>{
+ //MySqlOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
TableNamePrefix | +Cap表名前缀 | +string | +cap | +
ConnectionString | +数据库连接字符串 | +string | +null | +
你可以通过重写 IStorageInitializer
接口获取表名称的方法来做到这一点
示例代码:
+public class MyTableInitializer : MySqlStorageInitializer
+{
+ public override string GetPublishedTableName()
+ {
+ //你的 发送消息表 名称
+ }
+
+ public override string GetReceivedTableName()
+ {
+ //你的 接收消息表 名称
+ }
+}
+
services.AddSingleton<IStorageInitializer, MyTableInitializer>();
+
private readonly ICapPublisher _capBus;
+
+using (var connection = new MySqlConnection(AppDbContext.ConnectionString))
+{
+ using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))
+ {
+ //your business code
+ connection.Execute("insert into test(name) values('test')",
+ transaction: (IDbTransaction)transaction.DbTransaction);
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ transaction.Commit();
+ }
+}
+
private readonly ICapPublisher _capBus;
+
+using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))
+{
+ dbContext.Persons.Add(new Person() { Name = "ef.transaction" });
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ dbContext.SaveChanges();
+ trans.Commit();
+}
+
PostgreSQL 是一个开源的关系型数据库,它已经变得越来越流行,你可以使用 Postgre SQL 来作为 CAP 消息的持久化。
+要使用 PostgreSQL 存储,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.PostgreSql
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于内存的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UsePostgreSql(opt=>{
+ //PostgreSqlOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Schema | +数据库架构 | +string | +cap | +
ConnectionString | +数据库连接字符串 | +string | ++ |
DataSource | +Data source | +NpgsqlDataSource | ++ |
你可以通过重写 IStorageInitializer
接口获取表名称的方法来做到这一点
示例代码:
+public class MyTableInitializer : PostgreSqlStorageInitializer
+{
+ public override string GetPublishedTableName()
+ {
+ //你的 发送消息表 名称
+ }
+
+ public override string GetReceivedTableName()
+ {
+ //你的 接收消息表 名称
+ }
+}
+
services.AddSingleton<IStorageInitializer, MyTableInitializer>();
+
private readonly ICapPublisher _capBus;
+
+using (var connection = new NpgsqlConnection("ConnectionString"))
+{
+ using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))
+ {
+ //your business code
+ connection.Execute("insert into test(name) values('test')",
+ transaction: (IDbTransaction)transaction.DbTransaction);
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ transaction.Commit();
+ }
+}
+
private readonly ICapPublisher _capBus;
+
+using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))
+{
+ dbContext.Persons.Add(new Person() { Name = "ef.transaction" });
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ dbContext.SaveChanges();
+ trans.Commit();
+}
+
SQL Server 是由微软开发的一个关系型数据库,你可以使用 SQL Server 来作为 CAP 消息的持久化。
+注意
+我们目前使用 Microsoft.Data.SqlClient
作为数据库驱动程序,它是SQL Server 驱动的未来,并且已经放弃了 System.Data.SqlClient
,我们建议你切换过去。
要使用 SQL Server 存储,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.SqlServer
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于内存的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseSqlServer(opt=>{
+ //SqlServerOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Schema | +数据库架构 | +string | +Cap | +
ConnectionString | +数据库连接字符串 | +string | ++ |
你可以通过重写 IStorageInitializer
接口获取表名称的方法来做到这一点
示例代码:
+public class MyTableInitializer : SqlServerStorageInitializer
+{
+ public override string GetPublishedTableName()
+ {
+ //你的 发送消息表 名称
+ }
+
+ public override string GetReceivedTableName()
+ {
+ //你的 接收消息表 名称
+ }
+}
+
services.AddSingleton<IStorageInitializer, MyTableInitializer>();
+
private readonly ICapPublisher _capBus;
+
+using (var connection = new SqlConnection("ConnectionString"))
+{
+ using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))
+ {
+ //your business code
+ connection.Execute("insert into test(name) values('test')",
+ transaction: (IDbTransaction)transaction.DbTransaction);
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ transaction.Commit();
+ }
+}
+
private readonly ICapPublisher _capBus;
+
+using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false))
+{
+ dbContext.Persons.Add(new Person() { Name = "ef.transaction" });
+
+ _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now);
+
+ dbContext.SaveChanges();
+ trans.Commit();
+}
+
AWS SQS 是一种完全托管的消息队列服务,可让您分离和扩展微服务、分布式系统和无服务器应用程序。
+AWS SNS 是一种高度可用、持久、安全、完全托管的发布/订阅消息收发服务,可以轻松分离微服务、分布式系统和无服务器应用程序。
+由于 CAP 是基于 Topic 模式工作的,所以需要使用到 AWS SNS,SNS 简化了消息的发布订阅架构。
+在 CAP 启动时会将所有的订阅名称注册为 SNS 的 Topic,你将会在管理控制台中看到所有已经注册的 Topic 列表。
+由于 SNS 不支持使用 .
:
等符号作为 Topic 的名称,所以我们进行了替换,我们将 .
替换为了 -
,将 :
替换为了 _
注意事项
+Amazon SNS 当前允许发布的消息最大大小为 256KB
+举例,你的当前项目中有以下两个订阅者方法
+[CapSubscribe("sample.sns.foo")]
+public void TestFoo(DateTime value)
+{
+}
+
+[CapSubscribe("sample.sns.bar")]
+public void TestBar(DateTime value)
+{
+}
+
在 CAP 启动后,在 AWS SNS 中你将看到
+ +针对每个消费者组,CAP 将创建一个与之对应的 SQS 队列,队列的名称为配置项中 DefaultGroup 的名称,类型为 Standard Queue 。
+该 SQS 队列将订阅 SNS 中的 Topic ,如下图:
+ +注意事项
+由于 AWS SNS 的限制,当你减少订阅方法时,我们不会主动删除 AWS SNS 或者 SQS 上的相关 Topic 或 Queue,你需要手动删除他们。
+要使用 AWS SQS 作为消息传输器,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.AmazonSQS
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于 RabbitMQ 的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseAmazonSQS(opt=>
+ {
+ //AmazonSQSOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
CAP 直接对外提供的 AmazonSQSOptions 配置参数如下:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Region | +AWS 所处的区域 | +Amazon.RegionEndpoint | ++ |
Credentials | +AWS AK SK信息 | +Amazon.Runtime.AWSCredentials | ++ |
如果你的项目运行在 AWS EC2 中,则不需要设置 Credentials,直接对 EC2 应用 IAM 策略即可。
+Credentials 需要具有新增和订阅 SNS Topic,SQS Queue 等权限。
+ + + + + + + + + + +Azure 服务总线是一种多租户云消息服务,可用于在应用程序和服务之间发送信息。 异步操作可实现灵活的中转消息传送、结构化的先进先出 (FIFO) 消息传送以及发布/订阅功能。
+CAP 支持使用 Azure Service Bus 作为消息传输器。
+必须条件
+针对 Service Bus 的定价, CAP 要求使用 “标准” 或者 “高级” 以支持 Topic 功能。
+要使用 Azure Service Bus 作为消息传输器,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.AzureServiceBus
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于内存的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseAzureServiceBus(opt=>
+ {
+ //AzureServiceBusOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
CAP 直接对外提供的 Azure Service Bus 配置参数如下:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
ConnectionString | +Endpoint 地址 | +string | ++ |
EnableSessions | +Enable Service bus sessions | +bool | +false | +
TopicPath | +Topic entity path | +string | +cap | +
ManagementTokenProvider | +Token provider | +ITokenProvider | +null | +
AutoCompleteMessages | +获取一个值,该值指示处理器是否应在消息处理程序完成处理后自动完成消息 | +bool | +false | +
CustomHeadersBuilder | +为来自异构系统的传入消息添加自定义头 | +Func<Message, List<KeyValuePair<string, string>>>? |
+null | +
Namespace | +Servicebus 的命名空间,与 TokenCredential 属性一起使用时需要设置 | +string | +null | +
SQLFilters | +根据名称和表达式自定义 SQL 过滤器 | +List |
+null | +
当使用 EnableSessions
选项启用 sessions 后,每个发送的消息都会具有一个 session id。 要控制 seesion id 你可以在发送消息时在消息头中使用 AzureServiceBusHeaders.SessionId
携带它。
ICapPublisher capBus = ...;
+string yourEventName = ...;
+YourEventType yourEvent = ...;
+
+Dictionary<string, string> extraHeaders = new Dictionary<string, string>();
+extraHeaders.Add(AzureServiceBusHeaders.SessionId, <your-session-id>);
+
+capBus.Publish(yourEventName, yourEvent, extraHeaders);
+
如果头中没有 session id , 那么消息 Id 仍然使用的 Message Id.
+有时您可能想接收由外部系统发布的消息。 在这种情况下,您需要添加一组两个强制标头以实现 CAP 兼容性,如下所示。
+c.UseAzureServiceBus(asb =>
+{
+ asb.ConnectionString = ...
+ asb.CustomHeadersBuilder = (msg, sp) =>
+ [
+ new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),
+ new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)
+ ];
+});
+
++ + + + + + + + + + +重要提示:如果消息中已存在同名(Key)的标头,则不会添加自定义标头。
+
通过运输将数据从一个地方移动到另一个地方-在采集程序和管道之间,管道与实体数据库之间,甚至在管道与外部系统之间。
+CAP 支持以下几种运输方式:
+🏳🌈 | +RabbitMQ | +Kafka | +Azure Service Bus | +In-Memory | +
---|---|---|---|---|
定位 | +可靠消息传输 | +实时数据处理 | +云 | +内存型,测试 | +
分布式 | +✔ | +✔ | +✔ | +❌ | +
持久化 | +✔ | +✔ | +✔ | +❌ | +
性能 | +Medium | +High | +Medium | +High | +
+++
Azure Service Bus
vsRabbitMQ
:
+http://geekswithblogs.net/michaelstephenson/archive/2012/08/12/150399.aspx+
Kafka
vsRabbitMQ
:
+https://stackoverflow.com/questions/42151544/is-there-any-reason-to-use-rabbitmq-over-kafka
感谢社区对CAP的支持,以下是社区支持的运输器实现
+ActiveMQ (@Lukas Zhang): https://github.com/lukazh
+RedisMQ (@木木): https://github.com/difudotnet/CAP.RedisMQ.Extensions
+ZeroMQ (@maikebing): https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.ZeroMQ
+MQTT (@john jiang): https://github.com/jinzaz/jinzaz.CAP.MQTT
+In Memory Queue 为基于内存的消息队列,该扩展由 社区 进行提供。
+要使用 In Memory Queue 作为消息传输器,你需要从 NuGet 安装以下扩展包:
+Install-Package Savorboard.CAP.InMemoryMessageQueue
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于内存的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseInMemoryMessageQueue();
+ // x.UseXXX ...
+ });
+}
+
Apache Kafka® 是一个开源流处理软件平台,由 LinkedIn 开发并捐赠给 Apache Software Foundation,用 Scala 和 Java 编写。
+CAP 支持使用 Apache Kafka® 作为消息传输器。
+要使用 Kafka 作为消息传输器,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.Kafka
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于 Kafka 的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseKafka(opt=>{
+ //KafkaOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
CAP 直接对外提供的 Kafka 配置参数如下:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Servers | +Broker 地址 | +string | ++ |
ConnectionPoolSize | +用户名 | +int | +10 | +
CustomHeadersBuilder | +设置自定义头 | +Function | ++ |
有关 CustomHeadersBuilder
的说明:
如果你想在消费消息的时候,通过从 CapHeader
获取 Kafka 中例如 Offset 或者 Partition 等信息,你可以通过自定义此函数来实现这一点。
例如以下代码为你展示了如何进行设置额外的参数到 CapHeader
中:
x.UseKafka(opt =>
+{
+ //...
+
+ opt.CustomHeadersBuilder = (kafkaResult,sp) => new List<KeyValuePair<string, string>>
+ {
+ new KeyValuePair<string, string>("my.kafka.offset", kafkaResult.Offset.ToString()),
+ new KeyValuePair<string, string>("my.kafka.partition", kafkaResult.Partition.ToString())
+ };
+});
+
然后你可以通过这个方式来获取你添加的头信息:
+[CapSubscribe("sample.kafka.postgrsql")]
+public void HeadersTest(DateTime value, [FromCap]CapHeader header)
+{
+ var offset = header["my.kafka.offset"];
+ var partition = header["my.kafka.partition"];
+}
+
如果你需要 更多 原生 Kakfa 相关的配置项,可以通过 MainConfig
配置项进行设定:
services.AddCap(capOptions =>
+{
+ capOptions.UseKafka(kafkaOption=>
+ {
+ // kafka options.
+ // kafkaOptions.MainConfig.Add("", "");
+ });
+});
+
MainConfig 为配置字典,你可以通过以下链接找到其支持的配置项列表。
+https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
+ + + + + + + + + + +NATS是一个简单、安全、高性能的数字系统、服务和设备通信系统。NATS 是 CNCF 的一部分。
+Warning
+自 CAP 5.2+ 的版本已经基于 JetStream 实现相关功能,所以需要在服务端显式启用。
+你需要在 NATS Server 启动时候指定 --jetstream
参数来启用 JetSteram 相关功能,才能正常使用CAP.
要使用NATS 传输器,你需要安装下面的NuGet包:
+PM> Install-Package DotNetCore.CAP.NATS
+
你可以通过在 Startup.cs
文件中配置 ConfigureServices
来添加配置:
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddCap(capOptions =>
+ {
+ capOptions.UseNATS(natsOptions=>{
+ //NATS Options
+ });
+ });
+}
+
CAP 直接提供的关于 NATS 的配置参数:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Options | +NATS 客户端配置 | +Options | +Options | +
Servers | +服务器Urls地址 | +string | +NULL | +
ConnectionPoolSize | +连接池数量 | +uint | +10 | +
DeliverPolicy | +消费消息的策略点(⚠️在8.1.0版本移除,使用ConsumerOptions 替代。) |
+enum | +DeliverPolicy.New | +
StreamOptions | +🆕 Stream 配置项 | +Action | +NULL | +
ConsumerOptions | +🆕 Consumer 配置项 | +Action | +NULL | +
如果你需要 更多 原生相关的配置项,可以通过 Options
配置项进行设定:
services.AddCap(capOptions =>
+{
+ capOptions.UseNATS(natsOptions=>
+ {
+ // NATS options.
+ natsOptions.Options.Url="";
+ });
+});
+
Options
是 NATS.Client 客户端提供的配置, 你可以在这个链接找到更多详细信息。
Apache Pulsar 是一个用于服务器到服务器的消息系统,具有多租户、高性能等优势。 Pulsar 最初由 Yahoo 开发,目前由 Apache 软件基金会管理。
+CAP 支持使用 Apache Pulsar 作为消息传输器。
+要使用 Pulsar 作为消息传输器,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.Pulsar
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于 Pulsar 的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UsePulsar(opt => {
+ //Pulsar Options
+ });
+ // x.UseXXX ...
+ });
+}
+
CAP 直接对外提供的 Pulsar 配置参数如下:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
ServiceUrl | +Broker 地址 | +string | ++ |
TlsOptions | +TLS 配置项 | +object | ++ |
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ 服务器是用 Erlang 语言编写的,而聚类和故障转移是构建在开源的通讯平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
+CAP 支持使用 RabbitMQ 作为消息传输器。
+注意事项
+在使用RabbitMQ时,集成了CAP的消费者应用在启动过一次后会自动创建持久化的队列,后续消息会正常传递到队列中并消费。 +如果你从来没有启动过消费者,则队列不会被自动创建,此时如果先行发布消息,在此时间段的消息 RabbitMQ Exchange 收到后会直接丢弃。
+要使用 RabbitMQ 作为消息传输器,你需要从 NuGet 安装以下扩展包:
+Install-Package DotNetCore.CAP.RabbitMQ
+
然后,你可以在 Startup.cs
的 ConfigureServices
方法中添加基于 RabbitMQ 的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ // ...
+
+ services.AddCap(x =>
+ {
+ x.UseRabbitMQ(opt=>
+ {
+ //RabbitMQOptions
+ });
+ // x.UseXXX ...
+ });
+}
+
CAP 直接对外提供的 RabbitMQ 配置参数如下:
+配置项 | +描述 | +类型 | +默认值 | +
---|---|---|---|
HostName | +宿主地址,如果要配置集群可以使用逗号分隔,例如 192.168.1.111,192.168.1.112 |
+string | +localhost | +
UserName | +用户名 | +string | +guest | +
Password | +密码 | +string | +guest | +
VirtualHost | +虚拟主机 | +string | +/ | +
Port | +端口号 | +int | +-1 | +
ExchangeName | +CAP默认Exchange名称 | +string | +cap.default.topic | +
QueueArguments | +队列额外参数 x-arguments | +QueueArgumentsOptions | +N/A | +
ConnectionFactoryOptions | +RabbitMQClient原生参数 | +ConnectionFactory | +N/A | +
CustomHeadersBuilder | +订阅者自定义头信息 | +见下文 | +N/A | +
PublishConfirms | +是否启用发布确认 | +bool | +false | +
BasicQosOptions | +指定消费的Qos | +BasicQos | +N/A | +
如果你需要 更多 原生 ConnectionFactory
相关的配置项,可以通过 ConnectionFactoryOptions
配置项进行设定:
services.AddCap(x =>
+{
+ x.UseRabbitMQ(o =>
+ {
+ o.HostName = "localhost";
+ o.ConnectionFactoryOptions = opt => {
+ //rabbitmq client ConnectionFactory config
+ };
+ });
+});
+
当需要从异构系统或者直接接收从RabbitMQ 控制台发送的消息时,由于 CAP 需要定义额外的头信息才能正常订阅,所以此时会出现异常。通过提供此参数来进行自定义头信息的设置来使订阅者正常工作。
+你可以在这里找到有关 头信息 的说明。
+用法如下:
+x.UseRabbitMQ(aa =>
+{
+ aa.CustomHeadersBuilder = (msg, sp) =>
+ [
+ new(DotNetCore.CAP.Messages.Headers.MessageId, sp.GetRequiredService<ISnowflakeId>().NextId().ToString()),
+ new(DotNetCore.CAP.Messages.Headers.MessageName, msg.RoutingKey)
+ ];
+});
+
使用逗号分隔连接字符串即可,如下:
+x=> x.UseRabbitMQ("localhost:5672,localhost:5673,localhost:5674")
+
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
+Redis Stream 是 Redis 5.0 引入的一种新数据类型,它用一种仅附加的数据结构以更抽象的方式模拟日志数据结构。
+Redis Streams 可以在 CAP 中用作消息传输器。
+要使用 Redis Streams 传输器,您需要从 NuGet 安装以下包:
+PM> Install-Package DotNetCore.CAP.RedisStreams
+
然后,您可以在 Startup.cs
的 ConfigureServices
方法中添加基于 Redis Stream 的配置项。
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddCap(capOptions =>
+ {
+ capOptions.UseRedis(redisOptions=>{
+ //redisOptions
+ });
+ });
+}
+
CAP 直接对外提供的 Redis Stream 配置参数如下:
+NAME | +DESCRIPTION | +TYPE | +DEFAULT | +
---|---|---|---|
Configuration | +redis连接配置 (StackExchange.Redis) | +ConfigurationOptions | +ConfigurationOptions | +
StreamEntriesCount | +读取时从 stream 返回的条目数 | +uint | +10 | +
ConnectionPoolSize | +连接池数 | +uint | +10 | +
如果需要**更多**原生Redis相关配置选项,您可以在 Configuration
选项中进行设置 :
services.AddCap(capOptions =>
+{
+ capOptions.UseRedis(redisOptions=>
+ {
+ // redis options.
+ redisOptions.Configuration.EndPoints.Add(IPAddress.Loopback, 0);
+ });
+});
+
Configuration
是 StackExchange.Redis ConfigurationOptions ,您可以通过此链接找到更多详细信息。
由于redis streams 没有自动删除所有已经被所有组确认的消息的特性issue,所以你需要考虑是否使用脚本来执行定期删除。
+ + + + + + + + + + +