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 @@ + + + + + + + + + + + + + + + + + + + + + + CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..f9ff09679 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +cap.dotnetcore.xyz \ No newline at end of file diff --git a/about/contact-us/index.html b/about/contact-us/index.html new file mode 100644 index 000000000..a7d276698 --- /dev/null +++ b/about/contact-us/index.html @@ -0,0 +1,2025 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contact Us - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + +
+
+
+ + + + + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/about/license/index.html b/about/license/index.html new file mode 100644 index 000000000..d7b5688dd --- /dev/null +++ b/about/license/index.html @@ -0,0 +1,1968 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + License - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + +
+
+
+ + + + + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

License

+

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.

+ + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/about/release-notes/index.html b/about/release-notes/index.html new file mode 100644 index 000000000..605677bb1 --- /dev/null +++ b/about/release-notes/index.html @@ -0,0 +1,3546 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Release Notes - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + +
+
+
+ + + + + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Release Notes

+

Version 8.0.0 (Dec 14, 2023)

+

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.

+
    +
  • Streamlined auth via asp.net middlewares. (#1434) Thanks @mviegas
  • +
+

Features:

+
    +
  • Fully Support .NET 8.
  • +
  • Add FallbackWindowLookbackSeconds option to configure the retry processor to pick up the backtrack time window for Scheduled or Failed status messages. (#1455) Thanks @apatozi
  • +
  • Update IConsumerRegister.Default.cs to make dispose thread safe. (#1438) Thanks @blashbul
  • +
  • Compatible with .NET 8's dependency injection KeyedService. (#1436) Thanks @EashShow
  • +
  • Add virtual method to custom delay backtrack time window during delayed publishing large messges. (#1429) Thanks @PoteRii
  • +
+

Bug Fixed:

+
    +
  • Fixed message infinite retry of messages after subscriber is removed. (#1456) Thanks @bschwehn
  • +
  • Fixed open telemetry context lost on consumer retry and Baggage Propagation. (#1452) Thanks @bschwehn
  • +
  • Fixed NATS do not handle reconnect if the nats server is forcibly shutdown and then restarted. (#1449) Thanks @davidterins
  • +
  • Fixed outbox pattern messages does not recovery when using DotNetCore.CAP.InMemoryStorage. (#1439) Thanks @davidterins
  • +
  • Fixed open telemetry subscriber thows null reference when using azure service bus without connection string. (#1432) Thanks @demorgi
  • +
  • Fixed double registration of event handler for azure service bus. (#1427) Thanks @demorgi
  • +
  • Fixed publish delay message not working in sql server transaction. (#1422) Thanks @xiangxiren
  • +
+

Version 7.2.2 (Nov 1, 2023)

+

Features:

+
    +
  • NATS support consumer config DeliverPolicy, default to New. (#1404)
  • +
  • Be able to configure if to subscribe to custom producer topic. (#1409) @demorgi
  • +
+

Bug Fixed:

+
    +
  • Try to fixes RabbitMQ basicConsume TimeOutException. (#1405) @yang-xiaodong
  • +
  • Change MongoDb index from descending to ascending. (#1415) Thanks @ustaserdar
  • +
  • Fixed parent span for "Event Persistence" activity trace. (#1407) Thanks @blashbul
  • +
  • Fixed OpenTelemetry Dynatrace IsRemote flag. (#1402) Thanks @phmonte
  • +
  • Mark Mongo time serialized to local instance time by default. (#1400)
  • +
  • Fixed k8s dashboard meta query error in standalone mode. @yang-xiaodong
  • +
  • Azure Service Bus, consumer fails if subscription has session enabled. (#1396, #1397) Thanks @demorgi
  • +
+

Version 7.2.1 (Sep 8, 2023)

+

Features:

+
    +
  • The options EnableConsumerPrefetch and UseDispatchingPerGroup will work together without interference. (#1399)
  • +
+

Bug Fixed:

+
    +
  • Fixed SqlServer sql case sensitive in dashboard query. (#1389)
  • +
  • Fixed Redis endpoint is null in DotNetCore.CAP.OpenTelemetry. (#1384)
  • +
+

Version 7.2.0 (Jul 30, 2023)

+

Breaking Changes

+
    +
  • Remove ProducerThreadCount configuration option. Now automatically send task managed by the .NET thread pool. (#1380)
  • +
  • Change the SnowflakeId from static singleton to dependency injection singleton. (#1322)
  • +
+

Features:

+
    +
  • Add support for kubernetes discovery in dashboard. (#1362)
  • +
  • Message send task and consumer execute task managed by .net thread pool. (#1380)
  • +
  • Upgrade dependencies of NuGet packages.
  • +
+

Bug Fixed:

+
    +
  • Fixed BasicQosOptions not working as expected for RabbitMQ transport. (#1318)
  • +
  • Revert BeginTransactionAsync support. (#1376)
  • +
  • Fixed SqlServer transaction rollback message still sent out. (#1378)
  • +
  • +
+

Version 7.1.4 (Jun 17, 2023)

+

Features:

+ +

Bug Fixed:

+
    +
  • Keep the originall stack when consumer exception occurs. (#1341) Thanks @tomyangOK
  • +
  • Fixed multiple invocations caused when the retry processor exceeded the FailedRetryInterval. (#1359) Thanks @li-zheng-hao
  • +
  • Fixed thread blocking when enable UseDispatchingPerGroup option. (#1356) Thanks @sampsonye @li-zheng-hao
  • +
  • +
+

Version 7.1.3 (May 17, 2023)

+

Features:

+
    +
  • Allow Explicit to set AllowAnonymous for the dashboard API. (#1335)
  • +
  • Update dashboard UI style
  • +
  • Add Cancellation token for BeginTransactionAsync. (#1317) Thanks @denis-tsv
  • +
+

Bug Fixed:

+ +

Version 7.1.2 (Apr 25, 2023)

+

Bug Fixed:

+
    +
  • Optimizing consumer duplicate detection warning logs. (#1314)
  • +
  • Fixes NATS consumption repeat when multiple consumer threads.
  • +
  • Fixes NATS transport infinity reconnect race condition. (#1311)
  • +
+

Version 7.1.1 (Apr 7, 2023)

+

Features:

+
    +
  • Add support topic config for kafka. (#1303)
  • +
  • Log in to dashboard with JWT authentication. (#1306)
  • +
+

Bug Fixed:

+
    +
  • Fixed sqlserver character string convert to datetime2 exception. (#1302)
  • +
  • Fixed dashboard consul node proxy switch bug. (#1307)
  • +
+

Version 7.1.0 (Mar 5, 2023)

+

Features:

+ +

Bug Fixed:

+ +

Version 7.0.3 (Feb 2, 2023)

+

Features:

+ +

Bug Fixed:

+
    +
  • Fixed dashboard re-execute message throw null exception for SqlServer and Postgres. (#1259) Thanks @coolyuwk
  • +
+

Version 7.0.2 (Jan 9, 2023)

+

Features:

+ +

Bug Fixed:

+
    +
  • Fixed redis streams json serialize exception. (#1254)
  • +
  • Fixed dashboard route in balzor server app. (not support wasm) (#1244)
  • +
+

Version 7.0.1 (2022-12-16)

+

Bug Fixed:

+
    +
  • Fixed dashboard not working in balzor app. (#1244)
  • +
  • Fixed error when published Winform with 'Produce Single File'. (#1245)
  • +
+

Version 7.0.0 (2022-11-27)

+

Breaking Changes:

+
    +
  • SubscribeFilter method to asynchronous.
  • +
  • IConsumerClient interface OnMessage and OnLog is from event to delegate.
  • +
+

Features:

+
    +
  • Performance improvement
  • +
  • Add support publish delay message. (#1237)
  • +
  • Dashbord support viewing and immediately publish for delayed messages.
  • +
  • Add support for metrics diagnostics. (#1230)
  • +
  • Dashboard support real-time metric graph viewing.
  • +
  • Add support manual start/stop CAP process. (#1238)
  • +
  • Add EnableConsumerPrefetch option of consumer. (#1240)
  • +
  • Add PublishConfirms options for RabbitMQ.
  • +
+

Others:

+
    +
  • Change framework target from netstandard to net6.
  • +
  • Upgrade NuGet to the latest version.
  • +
+

Bug Fixed:

+
    +
  • RabbitMQ cluster connection failed without using default ports. (#1232)
  • +
+

Version 6.2.1 (2022-10-15)

+

Bug Fixed:

+
    +
  • Fixed EnvironmentVariableTarget.Machine only supported on windows. (#1220) Thanks @cuibty
  • +
  • Fixed RedisStream TryGetOrCreateStreamGroupAsync to create ConsumerGroup when not found. (#1212) Thanks @mlatoszek
  • +
+

Version 6.2.0 (2022-09-19)

+

Features:

+ +

Bug Fixed:

+
    +
  • SnowflakeId excludes virtual and loopback and non-working NICs. (#1163) Thanks @xiatiandegaga
  • +
  • Fixed the health check could not get the status correctly when RabbitMQ lost connection and quickly recovered. (#1193) Thanks @rpenha
  • +
  • Fixed dashboard gateway proxy request missing QueryString (#1168) Thanks @wwwu
  • +
  • Fixed the disconnect detection of RabbitMQ connection abnormality. (#1178)
  • +
  • Fixed Mongo queries not returning results when a element convention name is registered. (#1193) Thanks @rpenha
  • +
  • Fixed subscriber lookup in scoped lifecycle of factory mode. (#1204) Thanks @sampsonye
  • +
+

Version 6.1.0 (2022-06-10)

+

Features:

+
    +
  • Optimize snowflake algorithm. (#1065) Thanks @Allen-dududu
  • +
  • Add authorization policy option feature to CAP dashboard. (#1113) Thanks @albertopm19
  • +
  • Added support of ScheduledEnqueueTimeUtc for AzureServiceBus transport. (#1137) Thanks @webinex
  • +
  • Add option to configure failed messages expiration term. (#1142) Thanks @dima-zhemkov
  • +
+

Bug Fixed:

+
    +
  • Fixed sequence validation error when both enable Challenge and Auth of dashboard authentication. (#1097)
  • +
  • Used concurrentdictionary since PublishedMessages and ReceivedMessages are public and accessed from various places. (#1104) Thanks @wakiter
  • +
  • Fixed the health check could not get the status correctly when RabbitMQ lost connection and quickly recovered. (#1140)
  • +
  • Fixed date file format bug when retrying query from database. (#1143)
  • +
  • Change reading/creating streams and consumer groups to handle non idempotent operations. (#1150) Thanks @MahmoudSamir101
  • +
+

Version 6.0.1 (2022-02-15)

+

Bug Fixed:

+
    +
  • Fixed kafka consume excepiton for GroupLoadInProress errcode (#1085)
  • +
  • Fixed deserialization exception when message body is empty byte array. (#1087)
  • +
  • Fixed dashboard authentication challenge bug. (#1077)
  • +
+

Version 6.0.0 (2022-01-06)

+

Features:

+
    +
  • Fully support .NET 6.
  • +
  • Add support for OpenTelemetry. (#885)
  • +
  • Improve support for NATS JetStream wildcard topic. (#1047)
  • +
  • Add support customer header options for Azure Service Bus. (#1063) Thanks @Mateus Viegas
  • +
+

Version 5.2.0 (2021-11-12)

+

Features:

+
    +
  • Add support for NATS JetStream. (#983)
  • +
  • Add support for Apache Pulsar. (#610)
  • +
  • Add possibility to process messages for each consumer group indepedently. (#1027)
  • +
+

Bug Fixed:

+
    +
  • Fixed message content of bigint type cannot be displayed correctly in dashboard. (#1028)
  • +
  • Fixed unobserved tasks of async method calls in Amazon SQS. (#1033)
  • +
  • Fixed RabbitMQ federation plugin message header object values cause exceptions. (#1036)
  • +
+

Version 5.1.2 (2021-07-26)

+

Bug Fixed:

+
    +
  • Fixed consumer register cancellation token source null referencee bug. (#952)
  • +
  • Fixed redis streams transport cluster keys cross-hashslot bug. (#944)
  • +
+

Version 5.1.1 (2021-07-09)

+

Features:

+
    +
  • Improve flow control for message cache of in memory. (#935)
  • +
  • Add cancellation token support to subscribers. (#912)
  • +
  • Add pathbase options for dashboard. (#901)
  • +
  • Add custom authorization scheme support for dashboard. (#906)
  • +
+

Bug Fixed:

+
    +
  • Fixed mysql connect timeout expired bug. (#931)
  • +
  • Fixed consul health check path invalid bug. (#921)
  • +
  • Fixed mongo dashboard query bug. (#909)
  • +
+

Version 5.1.0 (2021-06-07)

+

Features:

+
    +
  • Add configure options for json serialization. (#879)
  • +
  • Add Redis Streams transport support. (#817)
  • +
  • New dashboard build with vue. (#880)
  • +
  • Add subscribe filter support. (#894)
  • +
+

Bug Fixed:

+
    +
  • Fixed use CapEFDbTransaction to get dbtransaction extension method bug. (#868)
  • +
  • Fixed pending message has not been deleted from buffer list in SQL Server. (#889)
  • +
  • Fixed dispatcher processing when storage message exception bug. (#900)
  • +
+

Version 5.0.3 (2021-05-14)

+

Bug Fixed:

+
    +
  • Fix the bug of getting db transaction through the IDbContextTransaction for SQLServer. (#867)
  • +
  • Fix RabbitMQ Connection close forced. (#861)
  • +
+

Version 5.0.2 (2021-04-28)

+

Features:

+
    +
  • Add support for Azure Service Bus sessions. (#829)
  • +
  • Add custom message headers support for RabbitMQ consumer. (#818)
  • +
+

Bug Fixed:

+
    +
  • Downgrading Microsoft.Data.SqlClient to 2.0.1. (#839)
  • +
  • DiagnosticObserver does not use null connection. (#845)
  • +
  • Fix null reference in AmazonSQSTransport. (#846)
  • +
+

Version 5.0.1 (2021-04-07)

+

Features:

+
    +
  • Add KafkaOptions.MainConfig to AutoCreateTopic. (#810)
  • +
  • Add support rewriting the default configuration of Kafka consumer. (#822)
  • +
  • Add DefaultChallengeScheme dashboard options to specify dashboard auth challenge scheme. (#815)
  • +
+

Bug Fixed:

+
    +
  • Fixed topic selector in IConsumerServiceSelector. (#806)
  • +
  • Update AWS topic subscription and SQS access policy generation. (#808)
  • +
  • Fixed memory leak when using transction to publish message. (#816)
  • +
  • Fixed SQL content filter on IMonitoringApi.PostgreSql.cs. (#814)
  • +
  • Fixed the expiration time display problem in the dashboard due to time zone issues (#820)
  • +
  • Fixed the creation timing of Kafka automatically creating Topic. (#823)
  • +
  • Fixed Dashboard metric not update. (#819)
  • +
+

Version 5.0.0 (2021-03-23)

+

Features:

+
    +
  • Upgrade to .NET Standard 2.1 and support .NET 5. (#727)
  • +
  • Replace Newtonsoft.Json to System.Text.Json. (#740)
  • +
  • Support NATS Transport. (#595,#743)
  • +
  • Enabling publiser confirms for RabbitMQ. (#730)
  • +
  • Support query subscription from DI implementation factory. (#756)
  • +
  • Add options to create lazy queue for RabbitMQ. (#772)
  • +
  • Support to add custom tags for Consul. (#786)
  • +
  • Support custom group and topic prefiex. (#780)
  • +
  • Renemae DefaultGroup option to DefaultGroupName.
  • +
  • Add auto create topic at startup for Kafka. (#795,#744)
  • +
+

Bug Fixed:

+
    +
  • Fixed retrying process earlier than consumer registration to DI. (#760)
  • +
  • Fixed Amazon SQS missing pagination topics. (#765)
  • +
  • Fixed RabbitMQ MessageTTL option to int type. (#787)
  • +
  • Fixed Dashboard auth. (#793)
  • +
  • Fixed ClientProvidedName could not be renamed for RabbitMQ. (#791)
  • +
  • Fixed EntityFramework transaction will not rollback when exception occurred. (#798)
  • +
+

Version 3.1.2 (2020-12-03)

+

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:

+
    +
  • Fixed InmemoryQueue expired messages are not removed bug. (#691)
  • +
  • Fixed Executor key change lead to possible null reference exception. (#698)
  • +
  • Fixed Postgresql delete expires data logic error. (#714)
  • +
+

Version 3.1.1 (2020-09-23)

+

Features:

+
    +
  • Add consumer parameter with interface suppport. (#669)
  • +
  • Add custom correlation id and message id support. (#668)
  • +
  • Enhanced custom serialization support. (#641)
  • +
+

Bug Fixed:

+
    +
  • Solve the issue of being duplicated executors from different assemblies. (#666)
  • +
  • Added comparer to remove duplicate ConsumerExecutors. (#653)
  • +
  • Add re-enable the auto create topics configuration item for Kafka, it's false by default. now is true. (#635)
  • +
  • Fixed postgresql transaction rollback invoke bug. (#640)
  • +
  • Fixed SQLServer table name customize bug. (#632)
  • +
+

Version 3.1.0 (2020-08-15)

+

Features:

+
    +
  • Add Amazon SQS support. (#597)
  • +
  • Remove Dapper and replace with ADO.NET in storage project. (#583)
  • +
  • Add debug symbols package to nuget.
  • +
  • Upgrade dependent nuget package version to latest.
  • +
  • English docs grammar correction. Thanks @mzorec
  • +
+

Bug Fixed:

+
    +
  • Fix mysql transaction rollback bug. (#598)
  • +
  • Fix dashboard query bug. (#600)
  • +
  • Fix mongo db query bug. (#611)
  • +
  • Fix dashboard browser language detection bug. (#631)
  • +
+

Version 3.0.4 (2020-05-27)

+

Bug Fixed:

+
    +
  • Fix kafka consumer group does not works bug. (#541)
  • +
  • Fix cast object to primitive types failed bug. (#547)
  • +
  • Fix subscriber primitive types convert exception. (#568)
  • +
  • Add conosole app sample.
  • +
  • Upgrade Confluent.Kafka to 1.4.3
  • +
+

Version 3.0.3 (2020-04-01)

+

Bug Fixed:

+
    +
  • Change ISubscribeInvoker interface access modifier to public. (#537)
  • +
  • Fix rabbitmq connection may be reused when close forced. (#533)
  • +
  • Fix dashboard message reexecute button throws exception bug. (#525)
  • +
+

Version 3.0.2 (2020-02-05)

+

Bug Fixed:

+
    +
  • Fixed diagnostics event data object error. (#504 )
  • +
  • Fixed RabbitMQ transport check not working. (#503 )
  • +
  • Fixed Azure Service Bus subscriber error. (#502 )
  • +
+

Version 3.0.1 (2020-01-19)

+

Bug Fixed:

+
    +
  • Fixed Dashboard requeue and reconsume failed bug. (#482 )
  • +
  • Fixed Azure service bus null reference exception. (#483 )
  • +
  • Fixed type cast exception from storage. (#473 )
  • +
  • Fixed SqlServer connection undisponse bug. (#477 )
  • +
+

Version 3.0.0 (2019-12-30)

+

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:

+
    +
  • Supports .NET Core 3.1.
  • +
  • Upgrade dependent packages.
  • +
  • New serialization interface ISerializer to support serialization of message body sent to MQ.
  • +
  • Add new api for ICapPublisher to publish message with headers.
  • +
  • Diagnostics event structure and names improved. #378
  • +
  • Support consumer method to read the message headers. #472
  • +
  • Support rename message storage tables. #435
  • +
  • Support for Kafka to write such as Offset and Partition to the header. #374
  • +
  • Improved the processor retry interval time. #444
  • +
+

Bug Fixed:

+
    +
  • Fixed SqlServer dashboard sql query bug. #470
  • +
  • Fixed Kafka health check bug. #436
  • +
  • Fixed dashboard bugs. #412 #404
  • +
  • Fixed transaction bug for sql server when using EF. #402
  • +
+

Version 2.6.0 (2019-08-29)

+

Features:

+ +

Bug Fixed:

+
    +
  • SQL Server Options Bug.
  • +
  • Fix transaction scope disposed bug. (#365)
  • +
  • Fix thread safe issue of ICapPublisher bug. (#371)
  • +
  • Improved Ctrl+C action raised exception issue.
  • +
  • Fixed asynchronous exception catching bug of sending.
  • +
  • Fix MatchPoundUsingRegex "." not escaped bug (#373)
  • +
+

Version 2.5.1 (2019-06-21)

+

Features:

+
    +
  • Improved logs record.
  • +
  • Upgrade dependent nuget packages version. (MySqlConnector, confluent-kafka-dotnet-1.0 )
  • +
  • NodeId type change to string of DiscoveryOptions for Consul. (#314)
  • +
  • Change the IConsumerServiceSelector interface access modifier to public. (#333)
  • +
  • Improved RabbitMQOptions to provide extensions option to configure the client original configuration. (#350)
  • +
  • Add index for MongoDB CAP collections. (#353)
  • +
+

Bugs Fixed:

+
    +
  • Fixed consumer re-register transport bug. (#329)
  • +
  • Handle messages retrieval failure. (#324)
  • +
  • Fixed DiagnosticListener null reference exception bug. (#335)
  • +
  • Add subscription name validation for the AzureServerBus. (#344)
  • +
  • Fixed thread safety issues of publisher. (#331)
  • +
+

Version 2.5.0 (2019-03-30)

+

Features:

+
    +
  • Support Azure Service Bus. (#307)
  • +
  • Support In-Memory Storage. (#296)
  • +
  • Upgrade Dapper to version 1.60.1
  • +
  • Support read environment variables CAP_WORKERID and CAP_DATACENTERID as the snowflake algorithm workerid and datacenterid.
  • +
+

Bug Fixed:

+
    +
  • Modify MySQL cap table encoding to utf8mb4. (#305)
  • +
  • Move CapSubscribeAttribute class to DotNetCore.CAP project.
  • +
  • Fixed multiple instance snowflake algorithm generating primary key conflicts. (#294)
  • +
+

Version 2.4.2 (2019-01-08)

+

Features:

+
    +
  • Startup the CAP with the .NET Core 2.1 BackgroundService. (#265)
  • +
  • Improved message delivery performance. #261
  • +
+

Bug Fixed:

+
    +
  • Fixed PostgreSql version isolation feature bug. (#256)
  • +
  • Fixed SQL Server sql bug for dashboard search. (#266)
  • +
+

Version 2.4.1 (2018-12-19)

+

Bug Fixed:

+
    +
  • Fixed MongoDB version isolation feature bug. (#253)
  • +
+

Version 2.4.0 (2018-12-08)

+

Features:

+
    +
  • Supported version options. (#220)
  • +
  • Upgrade nuget package to .net core 2.2.
  • +
+

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:

+
    +
  • Fixed different groups of the same topic name in one instance will cause routing bug. (#235)
  • +
  • Fixed message presistence bug. (#240)
  • +
  • Fixed RabbitMQ topic name contains numbers will cause exception bug. (#181)
  • +
+

Version 2.3.1 (2018-10-29)

+

Features:

+
    +
  • Add Source Link Support
  • +
  • Upgrade dependent NuGet packages.
  • +
+

Bug Fixed:

+
    +
  • Fixed dashboard messages requeue error. (#205)
  • +
  • Adjustment snowflake workerId to random id.
  • +
  • Fixed flush unclaer data bug.
  • +
+

Version 2.3.0 (2018-08-30)

+

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:

+
    +
  • Removed app.UseCap() from Startup.cs
  • +
  • Message table primary key data type has been modified to Bigint and non auto-Increment. (#180)
  • +
  • New publisher Api. (#188)
  • +
+

Features:

+
    +
  • MongoDb supported. (#143)
  • +
  • Automatic commit transaction. (#191)
  • +
+

Bug Fixed:

+
    +
  • Fix message still sent if transaction faild bug. (#118)
  • +
  • Multiple events in one transaction. (#171)
  • +
+

Version 2.2.5 (2018-07-19)

+

Features: +- Performance improvement

+

Bug Fixed:

+
    +
  • Fixed message enqueue exception.
  • +
  • Fixed Retry processor bugs.
  • +
  • Fixed Kafka producer exception log without logging when publish message.
  • +
  • Fixed Incorrect local IP address judgment of IPv6. (#140)
  • +
  • Fixed DateTime localization format conversion error to sql. (#139)
  • +
  • Fixed dashboard message page re-requeue and re-executed operate bug. (#158)
  • +
  • Fixed SendAsync or ExecuteAsync recursion retries bug. (#160)
  • +
  • Fixed configuration options of FailedThresholdCallback could not be invoke when the value less then three. (#161)
  • +
+

Version 2.2.4 (2018-06-05)

+

Because version 2.2.3 was not released to nuget, so released 2.2.4.

+

Version 2.2.3 (2018-06-05)

+

Features:

+
    +
  • Improved log output.
  • +
  • Upgrade nuget packages.
  • +
  • Support pattern matching for consumer. (#132)
  • +
+

Bug Fixed:

+
    +
  • Fixed exception thrown when terminate the program with Ctrl+C. (#130)
  • +
+

Version 2.2.2 (2018-04-28)

+

Features:

+
    +
  • Improved log output. #114
  • +
  • Add default timeout configuration for kafka client.
  • +
  • Rename configuration options FailedCallback to FailedThresholdCallback.
  • +
+

Bug Fixed:

+
    +
  • Fixed message enqueue exception.
  • +
  • Fixed retry processor bugs.
  • +
  • Fixed kafka producer exception log without logging when publish message.
  • +
+

Version 2.2.1 (2018-04-18)

+

Bug Fixed:

+
    +
  • Fixed message enqueue bug in v2.2
  • +
+

Version 2.2.0 (2018-04-17)

+

Features:

+
    +
  • Remove database queue mode. (#102)
  • +
  • Support for Diagnostics. (#112)
  • +
  • Upgrade dependent nuget packages.
  • +
+

Bug Fixed:

+
    +
  • Fixed bug of the FailedRetryCount does not increase when raised SubscribeNotFoundException. (#90)
  • +
+

Version 2.1.4 (2018-03-16)

+

Features:

+
    +
  • Remove TableNamePrefix option from MySqlOptions to EFOptions.
  • +
  • Upgrade nuget package
  • +
+

Bug Fixed:

+
    +
  • Fixed the connection bug of getting message from table. (#83)
  • +
  • Fixed entityframework rename table name prefix bug. (#84)
  • +
  • Fixed sql server scripts bug of create table scheme. (#85)
  • +
  • Fixed thread safety issue about KafkaOptions.(#89)
  • +
+

Version 2.1.3 (2018-01-24)

+

Features:

+
    +
  • Upgrade dependent nuget packages version.
  • +
  • NuGet package include xml doc now.
  • +
  • NuGet now contains the CAP symbol files.
  • +
+

Bug Fixed:

+
    +
  • Fixed thread conflict issue when sending messages with PublishAsync. (#80)
  • +
  • Fixed kafka received message sava failed may caused the mssage loss bug. (#78)
  • +
  • Fixed dashboard js syntax issue. (#77)
  • +
+

Version 2.1.2 (2017-12-18)

+

Bug Fixed:

+
    +
  • Fixed and improve the performance of mysql processing messages. (#68) (#36)
  • +
  • Fixed dashboard manually trigger reconsumption bug. (#67)
  • +
  • Fixed mysql 5.5 table initialization bug. (#65)
  • +
  • Fixed mysql message queue executor bug. (#66)
  • +
+

Version 2.1.1 (2017-11-28)

+

Bug Fixed:

+
    +
  • Fixed 'dotnet.exe' process incomplete quit when shutdown application (Ctrl+C). (#64)
  • +
  • Fixed failure to issue as expected of RabbitMQ SubscriberNotFoundException. (#63)
  • +
  • Fixed Sent async message in the loop causes an exception. (#62)
  • +
+

Version 2.1.0 (2017-11-17)

+

Features:

+
    +
  • Interface display optimization of dashboard.
  • +
  • Adds a more friendly display when looks at the message content.
  • +
  • Now you can see the exception infomation in the message conent filed when message send or executed failed.
  • +
  • Optimize LAN to see Dashboard without authentication.
  • +
  • Add IContentSerializer interface, you can customize the serialized message content.
  • +
  • Add IMessagePacker interface, you can customize wapper of the message.
  • +
  • Upgrade the dependent package.
  • +
+

Bug Fixed:

+
    +
  • Fixed dashboard query bugs.
  • +
  • Fixed dashboard multilanguage display bugs.
  • +
  • Fixed RabbitMQ connection pool bug.
  • +
  • Fixed dashboard display bugs on mobile.
  • +
+

Version 2.0.2 (2017-09-29)

+

Bug Fixed:

+
    +
  • Fixed asp.net core 2.0 startup error of MySql and PostgreSql. (#44
  • +
+

Version 2.0.1 (2017-09-16)

+

Bug Fixed:

+
    +
  • DbContext services bug. (#44)
  • +
  • Dependency injection bug. (#45)
  • +
+

Version 2.0.0 (2017-09-01)

+

Features:

+
    +
  • Supported .net standard 2.0.
  • +
  • Supported PostgreSQL 9.5+.
  • +
  • Supported asynchronous function subscriptions.
  • +
  • ICapPublisher api supported callback subsrciber.
  • +
+

Bug Fixed:

+
    +
  • Fixed multiple subscriber subscribe bug. (#38)
  • +
  • Fixed model binde bug. (#17) (#18)
  • +
  • Fixed database connection disposed bug. (#25)
  • +
  • Fixed consumer method injection context bug. (#34)
  • +
+

Version 1.1.0 (2017-08-04)

+

Features:

+
    +
  • Support MySQL database persistent message.
  • +
  • Add message failed call-back in CapOptions.
  • +
  • Remove publish messages API of string name at ICapPublisher.
  • +
+

Bug Fixed:

+
    +
  • Fixed can not send message for string type. (#17)
  • +
  • Fixed model bind for type like datetime guid always failed. (#18)
  • +
+

Version 1.0.1 (2017-07-25)

+

Features:

+
    +
  • ICapPublisher interface added synchronous publish API.
  • +
  • Add infinity retry failed processor.
  • +
+

Version 1.0.0 (2017-07-19)

+
    +
  • Project published
  • +
+ + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 000000000..1cf13b9f9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.51d95adb.min.js b/assets/javascripts/bundle.51d95adb.min.js new file mode 100644 index 000000000..b20ec6835 --- /dev/null +++ b/assets/javascripts/bundle.51d95adb.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Hi=Object.create;var xr=Object.defineProperty;var Pi=Object.getOwnPropertyDescriptor;var $i=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,Ii=Object.getPrototypeOf,Er=Object.prototype.hasOwnProperty,an=Object.prototype.propertyIsEnumerable;var on=(e,t,r)=>t in e?xr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))Er.call(t,r)&&on(e,r,t[r]);if(kt)for(var r of kt(t))an.call(t,r)&&on(e,r,t[r]);return e};var sn=(e,t)=>{var r={};for(var n in e)Er.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&kt)for(var n of kt(e))t.indexOf(n)<0&&an.call(e,n)&&(r[n]=e[n]);return r};var Ht=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Fi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of $i(t))!Er.call(e,o)&&o!==r&&xr(e,o,{get:()=>t[o],enumerable:!(n=Pi(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Hi(Ii(e)):{},Fi(t||!e||!e.__esModule?xr(r,"default",{value:e,enumerable:!0}):r,e));var fn=Ht((wr,cn)=>{(function(e,t){typeof wr=="object"&&typeof cn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(wr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function f(T){var Ke=T.type,We=T.tagName;return!!(We==="INPUT"&&a[Ke]&&!T.readOnly||We==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function c(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function u(T){T.hasAttribute("data-focus-visible-added")&&(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function p(T){T.metaKey||T.altKey||T.ctrlKey||(s(r.activeElement)&&c(r.activeElement),n=!0)}function m(T){n=!1}function d(T){s(T.target)&&(n||f(T.target))&&c(T.target)}function h(T){s(T.target)&&(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(T.target))}function v(T){document.visibilityState==="hidden"&&(o&&(n=!0),B())}function B(){document.addEventListener("mousemove",z),document.addEventListener("mousedown",z),document.addEventListener("mouseup",z),document.addEventListener("pointermove",z),document.addEventListener("pointerdown",z),document.addEventListener("pointerup",z),document.addEventListener("touchmove",z),document.addEventListener("touchstart",z),document.addEventListener("touchend",z)}function re(){document.removeEventListener("mousemove",z),document.removeEventListener("mousedown",z),document.removeEventListener("mouseup",z),document.removeEventListener("pointermove",z),document.removeEventListener("pointerdown",z),document.removeEventListener("pointerup",z),document.removeEventListener("touchmove",z),document.removeEventListener("touchstart",z),document.removeEventListener("touchend",z)}function z(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,re())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),B(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var un=Ht(Sr=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},a=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(re,z){d.append(z,re)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(T){throw new Error("URL unable to set base "+c+" due to "+T)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,B=!0,re=this;["append","delete","set"].forEach(function(T){var Ke=h[T];h[T]=function(){Ke.apply(h,arguments),v&&(B=!1,re.search=h.toString(),B=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var z=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==z&&(z=this.search,B&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},a=i.prototype,s=function(f){Object.defineProperty(a,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){s(f)}),Object.defineProperty(a,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(a,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr)});var Qr=Ht((Lt,Kr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Lt=="object"&&typeof Kr=="object"?Kr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Lt=="object"?Lt.ClipboardJS=r():t.ClipboardJS=r()})(Lt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return ki}});var a=i(279),s=i.n(a),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(O){return!1}}var d=function(O){var w=p()(O);return m("cut"),w},h=d;function v(j){var O=document.documentElement.getAttribute("dir")==="rtl",w=document.createElement("textarea");w.style.fontSize="12pt",w.style.border="0",w.style.padding="0",w.style.margin="0",w.style.position="absolute",w.style[O?"right":"left"]="-9999px";var k=window.pageYOffset||document.documentElement.scrollTop;return w.style.top="".concat(k,"px"),w.setAttribute("readonly",""),w.value=j,w}var B=function(O,w){var k=v(O);w.container.appendChild(k);var F=p()(k);return m("copy"),k.remove(),F},re=function(O){var w=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},k="";return typeof O=="string"?k=B(O,w):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?k=B(O.value,w):(k=p()(O),m("copy")),k},z=re;function T(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?T=function(w){return typeof w}:T=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},T(j)}var Ke=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},w=O.action,k=w===void 0?"copy":w,F=O.container,q=O.target,Le=O.text;if(k!=="copy"&&k!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&T(q)==="object"&&q.nodeType===1){if(k==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(k==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Le)return z(Le,{container:F});if(q)return k==="cut"?h(q):z(q,{container:F})},We=Ke;function Ie(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ie=function(w){return typeof w}:Ie=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},Ie(j)}function Ti(j,O){if(!(j instanceof O))throw new TypeError("Cannot call a class as a function")}function nn(j,O){for(var w=0;w0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof F.action=="function"?F.action:this.defaultAction,this.target=typeof F.target=="function"?F.target:this.defaultTarget,this.text=typeof F.text=="function"?F.text:this.defaultText,this.container=Ie(F.container)==="object"?F.container:document.body}},{key:"listenClick",value:function(F){var q=this;this.listener=c()(F,"click",function(Le){return q.onClick(Le)})}},{key:"onClick",value:function(F){var q=F.delegateTarget||F.currentTarget,Le=this.action(q)||"copy",Rt=We({action:Le,container:this.container,target:this.target(q),text:this.text(q)});this.emit(Rt?"success":"error",{action:Le,text:Rt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(F){return yr("action",F)}},{key:"defaultTarget",value:function(F){var q=yr("target",F);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(F){return yr("text",F)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(F){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return z(F,q)}},{key:"cut",value:function(F){return h(F)}},{key:"isSupported",value:function(){var F=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof F=="string"?[F]:F,Le=!!document.queryCommandSupported;return q.forEach(function(Rt){Le=Le&&!!document.queryCommandSupported(Rt)}),Le}}]),w}(s()),ki=Ri},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,f){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(f))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof m=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(m))return c(m,d,h);if(a.nodeList(m))return u(m,d,h);if(a.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return s(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),a=f.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var f=this;function c(){f.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=s.length;for(f;f{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var is=/["'&<>]/;Jo.exports=as;function as(e){var t=""+e,r=is.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||s(m,d)})})}function s(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof Xe?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){s("next",m)}function u(m){s("throw",m)}function p(m,d){m(d),i.shift(),i.length&&s(i[0][0],i[0][1])}}function mn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof xe=="function"?xe(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,f){a=e[i](a),o(s,f,a.done,a.value)})}}function o(i,a,s,f){Promise.resolve(f).then(function(c){i({value:c,done:s})},a)}}function A(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var $t=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function De(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Fe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=xe(a),f=s.next();!f.done;f=s.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(A(u))try{u()}catch(v){i=v instanceof $t?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=xe(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{dn(h)}catch(v){i=i!=null?i:[],v instanceof $t?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new $t(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)dn(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&De(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&De(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Or=Fe.EMPTY;function It(e){return e instanceof Fe||e&&"closed"in e&&A(e.remove)&&A(e.add)&&A(e.unsubscribe)}function dn(e){A(e)?e():e.unsubscribe()}var Ae={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Or:(this.currentObservers=null,s.push(r),new Fe(function(){n.currentObservers=null,De(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new wn(r,n)},t}(U);var wn=function(e){ne(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Or},t}(E);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ne(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,f=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Ut);var On=function(e){ne(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Wt);var we=new On(Tn);var R=new U(function(e){return e.complete()});function Dt(e){return e&&A(e.schedule)}function kr(e){return e[e.length-1]}function Qe(e){return A(kr(e))?e.pop():void 0}function Se(e){return Dt(kr(e))?e.pop():void 0}function Vt(e,t){return typeof kr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function zt(e){return A(e==null?void 0:e.then)}function Nt(e){return A(e[ft])}function qt(e){return Symbol.asyncIterator&&A(e==null?void 0:e[Symbol.asyncIterator])}function Kt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Ki(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qt=Ki();function Yt(e){return A(e==null?void 0:e[Qt])}function Gt(e){return ln(this,arguments,function(){var r,n,o,i;return Pt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,Xe(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,Xe(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,Xe(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Bt(e){return A(e==null?void 0:e.getReader)}function $(e){if(e instanceof U)return e;if(e!=null){if(Nt(e))return Qi(e);if(pt(e))return Yi(e);if(zt(e))return Gi(e);if(qt(e))return _n(e);if(Yt(e))return Bi(e);if(Bt(e))return Ji(e)}throw Kt(e)}function Qi(e){return new U(function(t){var r=e[ft]();if(A(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Yi(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?_(function(o,i){return e(o,i,n)}):me,Oe(1),r?He(t):zn(function(){return new Xt}))}}function Nn(){for(var e=[],t=0;t=2,!0))}function fe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new E}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,f=s===void 0?!0:s;return function(c){var u,p,m,d=0,h=!1,v=!1,B=function(){p==null||p.unsubscribe(),p=void 0},re=function(){B(),u=m=void 0,h=v=!1},z=function(){var T=u;re(),T==null||T.unsubscribe()};return g(function(T,Ke){d++,!v&&!h&&B();var We=m=m!=null?m:r();Ke.add(function(){d--,d===0&&!v&&!h&&(p=jr(z,f))}),We.subscribe(Ke),!u&&d>0&&(u=new et({next:function(Ie){return We.next(Ie)},error:function(Ie){v=!0,B(),p=jr(re,o,Ie),We.error(Ie)},complete:function(){h=!0,B(),p=jr(re,a),We.complete()}}),$(T).subscribe(u))})(c)}}function jr(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function V(e,t=document){let r=se(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function se(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),N(e===_e()),Y())}function Be(e){return{x:e.offsetLeft,y:e.offsetTop}}function Yn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,we),l(()=>Be(e)),N(Be(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,we),l(()=>rr(e)),N(rr(e)))}var Bn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!zr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),xa?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!zr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ya.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Jn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Zn=typeof WeakMap!="undefined"?new WeakMap:new Bn,eo=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=Ea.getInstance(),n=new Ra(t,r,this);Zn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){eo.prototype[e]=function(){var t;return(t=Zn.get(this))[e].apply(t,arguments)}});var ka=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:eo}(),to=ka;var ro=new E,Ha=I(()=>H(new to(e=>{for(let t of e)ro.next(t)}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function de(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){return Ha.pipe(S(t=>t.observe(e)),x(t=>ro.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(()=>de(e)))),N(de(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var no=new E,Pa=I(()=>H(new IntersectionObserver(e=>{for(let t of e)no.next(t)},{threshold:0}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function sr(e){return Pa.pipe(S(t=>t.observe(e)),x(t=>no.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function oo(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=de(e),o=bt(e);return r>=o.height-n.height-t}),Y())}var cr={drawer:V("[data-md-toggle=drawer]"),search:V("[data-md-toggle=search]")};function io(e){return cr[e].checked}function qe(e,t){cr[e].checked!==t&&cr[e].click()}function je(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),N(t.checked))}function $a(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ia(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(N(!1))}function ao(){let e=b(window,"keydown").pipe(_(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:io("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),_(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!$a(n,r)}return!0}),fe());return Ia().pipe(x(t=>t?R:e))}function Me(){return new URL(location.href)}function ot(e){location.href=e.href}function so(){return new E}function co(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)co(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)co(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function fo(){return location.hash.substring(1)}function uo(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Fa(){return b(window,"hashchange").pipe(l(fo),N(fo()),_(e=>e.length>0),J(1))}function po(){return Fa().pipe(l(e=>se(`[id="${e}"]`)),_(e=>typeof e!="undefined"))}function Nr(e){let t=matchMedia(e);return Zt(r=>t.addListener(()=>r(t.matches))).pipe(N(t.matches))}function lo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(N(e.matches))}function qr(e,t){return e.pipe(x(r=>r?t():R))}function ur(e,t={credentials:"same-origin"}){return ve(fetch(`${e}`,t)).pipe(ce(()=>R),x(r=>r.status!==200?Tt(()=>new Error(r.statusText)):H(r)))}function Ue(e,t){return ur(e,t).pipe(x(r=>r.json()),J(1))}function mo(e,t){let r=new DOMParser;return ur(e,t).pipe(x(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function pr(e){let t=M("script",{src:e});return I(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(x(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),C(()=>document.head.removeChild(t)),Oe(1))))}function ho(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function bo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(ho),N(ho()))}function vo(){return{width:innerWidth,height:innerHeight}}function go(){return b(window,"resize",{passive:!0}).pipe(l(vo),N(vo()))}function yo(){return Q([bo(),go()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(X("size")),o=Q([n,r]).pipe(l(()=>Be(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:a,size:s},{x:f,y:c}])=>({offset:{x:a.x-f,y:a.y-c+i},size:s})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(a=>{let s=document.createElement("script");s.src=i,s.onload=a,document.body.appendChild(s)})),Promise.resolve())}var r=class{constructor(n){this.url=n,this.onerror=null,this.onmessage=null,this.onmessageerror=null,this.m=a=>{a.source===this.w&&(a.stopImmediatePropagation(),this.dispatchEvent(new MessageEvent("message",{data:a.data})),this.onmessage&&this.onmessage(a))},this.e=(a,s,f,c,u)=>{if(s===this.url.toString()){let p=new ErrorEvent("error",{message:a,filename:s,lineno:f,colno:c,error:u});this.dispatchEvent(p),this.onerror&&this.onerror(p)}};let o=new EventTarget;this.addEventListener=o.addEventListener.bind(o),this.removeEventListener=o.removeEventListener.bind(o),this.dispatchEvent=o.dispatchEvent.bind(o);let i=document.createElement("iframe");i.width=i.height=i.frameBorder="0",document.body.appendChild(this.iframe=i),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + + + +
+
+
+ + + + + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

CAP

+

+Docs&Dashboard +AppVeyor +NuGet +NuGet Preview +GitHub license +Member project of .NET Core Community

+

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.

+

Introduction

+

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.

+

Contributing

+

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:

+

+

+

License

+

CAP is licensed under the MIT license.

+ + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 000000000..3fc245440 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"CAP","text":"

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":"
  • Author: @yang-xiaodong
  • Email: yangxiaodong1214@126.com
  • Blogs: https://savorboard.cnblogs.com
"},{"location":"about/contact-us/#ncc-organization","title":"NCC Organization","text":"
  • Email: dotnetcn@outlook.com
  • Twitter: https://twitter.com/ncc_community
  • Weibo: https://weibo.com/dotnetcore
"},{"location":"about/license/","title":"License","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.

  • Streamlined auth via asp.net middlewares. (#1434) Thanks @mviegas

Features:

  • Fully Support .NET 8.
  • Add FallbackWindowLookbackSeconds option to configure the retry processor to pick up the backtrack time window for Scheduled or Failed status messages. (#1455) Thanks @apatozi
  • Update IConsumerRegister.Default.cs to make dispose thread safe. (#1438) Thanks @blashbul
  • Compatible with .NET 8's dependency injection KeyedService. (#1436) Thanks @EashShow
  • Add virtual method to custom delay backtrack time window during delayed publishing large messges. (#1429) Thanks @PoteRii

Bug Fixed:

  • Fixed message infinite retry of messages after subscriber is removed. (#1456) Thanks @bschwehn
  • Fixed open telemetry context lost on consumer retry and Baggage Propagation. (#1452) Thanks @bschwehn
  • Fixed NATS do not handle reconnect if the nats server is forcibly shutdown and then restarted. (#1449) Thanks @davidterins
  • Fixed outbox pattern messages does not recovery when using DotNetCore.CAP.InMemoryStorage. (#1439) Thanks @davidterins
  • Fixed open telemetry subscriber thows null reference when using azure service bus without connection string. (#1432) Thanks @demorgi
  • Fixed double registration of event handler for azure service bus. (#1427) Thanks @demorgi
  • Fixed publish delay message not working in sql server transaction. (#1422) Thanks @xiangxiren
"},{"location":"about/release-notes/#version-722-nov-1-2023","title":"Version 7.2.2 (Nov 1, 2023)","text":"

Features:

  • NATS support consumer config DeliverPolicy, default to New. (#1404)
  • Be able to configure if to subscribe to custom producer topic. (#1409) @demorgi

Bug Fixed:

  • Try to fixes RabbitMQ basicConsume TimeOutException. (#1405) @yang-xiaodong
  • Change MongoDb index from descending to ascending. (#1415) Thanks @ustaserdar
  • Fixed parent span for \"Event Persistence\" activity trace. (#1407) Thanks @blashbul
  • Fixed OpenTelemetry Dynatrace IsRemote flag. (#1402) Thanks @phmonte
  • Mark Mongo time serialized to local instance time by default. (#1400)
  • Fixed k8s dashboard meta query error in standalone mode. @yang-xiaodong
  • Azure Service Bus, consumer fails if subscription has session enabled. (#1396, #1397) Thanks @demorgi
"},{"location":"about/release-notes/#version-721-sep-8-2023","title":"Version 7.2.1 (Sep 8, 2023)","text":"

Features:

  • The options EnableConsumerPrefetch and UseDispatchingPerGroup will work together without interference. (#1399)

Bug Fixed:

  • Fixed SqlServer sql case sensitive in dashboard query. (#1389)
  • Fixed Redis endpoint is null in DotNetCore.CAP.OpenTelemetry. (#1384)
"},{"location":"about/release-notes/#version-720-jul-30-2023","title":"Version 7.2.0 (Jul 30, 2023)","text":"

Breaking Changes

  • Remove ProducerThreadCount configuration option. Now automatically send task managed by the .NET thread pool. (#1380)
  • Change the SnowflakeId from static singleton to dependency injection singleton. (#1322)

Features:

  • Add support for kubernetes discovery in dashboard. (#1362)
  • Message send task and consumer execute task managed by .net thread pool. (#1380)
  • Upgrade dependencies of NuGet packages.

Bug Fixed:

  • Fixed BasicQosOptions not working as expected for RabbitMQ transport. (#1318)
  • Revert BeginTransactionAsync support. (#1376)
  • Fixed SqlServer transaction rollback message still sent out. (#1378)
"},{"location":"about/release-notes/#version-714-jun-17-2023","title":"Version 7.1.4 (Jun 17, 2023)","text":"

Features:

  • Add suppport AutoDeleteOnIdle option for Azure Service Bus. (#1350) Thanks @StevenDevooght

Bug Fixed:

  • Keep the originall stack when consumer exception occurs. (#1341) Thanks @tomyangOK
  • Fixed multiple invocations caused when the retry processor exceeded the FailedRetryInterval. (#1359) Thanks @li-zheng-hao
  • Fixed thread blocking when enable UseDispatchingPerGroup option. (#1356) Thanks @sampsonye @li-zheng-hao
"},{"location":"about/release-notes/#version-713-may-17-2023","title":"Version 7.1.3 (May 17, 2023)","text":"

Features:

  • Allow Explicit to set AllowAnonymous for the dashboard API. (#1335)
  • Update dashboard UI style
  • Add Cancellation token for BeginTransactionAsync. (#1317) Thanks @denis-tsv

Bug Fixed:

  • Fixed postgresql AcquireLockAsync sql error. (#1320) Thanks @guochen2
  • Fixed redis transport order pool connections non-lazy created connections. (#1332) Thanks @MahmoudSamir101
  • Fixed mysql 8.0 storage skip locked not available bug. (#1330) Thanks @yang-xiaodong
"},{"location":"about/release-notes/#version-712-apr-25-2023","title":"Version 7.1.2 (Apr 25, 2023)","text":"

Bug Fixed:

  • Optimizing consumer duplicate detection warning logs. (#1314)
  • Fixes NATS consumption repeat when multiple consumer threads.
  • Fixes NATS transport infinity reconnect race condition. (#1311)
"},{"location":"about/release-notes/#version-711-apr-7-2023","title":"Version 7.1.1 (Apr 7, 2023)","text":"

Features:

  • Add support topic config for kafka. (#1303)
  • Log in to dashboard with JWT authentication. (#1306)

Bug Fixed:

  • Fixed sqlserver character string convert to datetime2 exception. (#1302)
  • Fixed dashboard consul node proxy switch bug. (#1307)
"},{"location":"about/release-notes/#version-710-mar-5-2023","title":"Version 7.1.0 (Mar 5, 2023)","text":"

Features:

  • Add option to support distributed locks for retry processor. (#1272) Thanks @li-zheng-hao
  • Add option to support set BasicQos for RabbitMQ. (#1267) Thanks @nunorelvao
  • Add option to set queue type for RabbitMQ. (#1281) Thanks @PaulCousinsTTEducation
  • Add support publish to mutiple topics for Azure Service Bus. (#1283) Thanks @jonekdahl @mviegas

Bug Fixed:

  • Fixed dashboard re-execute message throw null exception for MongoDB. (#1279) Thanks @cagataykiziltan
"},{"location":"about/release-notes/#version-703-feb-2-2023","title":"Version 7.0.3 (Feb 2, 2023)","text":"

Features:

  • Add SQL Filters option on topic subscribtion for AzureServiceBus. (#1263) Thanks @giorgilekveishvili-meama
  • Add EF BeginTransaction extensions overload with isolationlevel and async version. (#1266) @xshaheen

Bug Fixed:

  • Fixed dashboard re-execute message throw null exception for SqlServer and Postgres. (#1259) Thanks @coolyuwk
"},{"location":"about/release-notes/#version-702-jan-9-2023","title":"Version 7.0.2 (Jan 9, 2023)","text":"

Features:

  • Change AzureServiceBus nuget package from Microsoft.Azure.ServiceBus to Azure.Messaging.ServiceBus. (https://github.com/dotnetcore/CAP/pull/1253)

Bug Fixed:

  • Fixed redis streams json serialize exception. (#1254)
  • Fixed dashboard route in balzor server app. (not support wasm) (#1244)
"},{"location":"about/release-notes/#version-701-2022-12-16","title":"Version 7.0.1 (2022-12-16)","text":"

Bug Fixed:

  • Fixed dashboard not working in balzor app. (#1244)
  • Fixed error when published Winform with 'Produce Single File'. (#1245)
"},{"location":"about/release-notes/#version-700-2022-11-27","title":"Version 7.0.0 (2022-11-27)","text":"

Breaking Changes:

  • SubscribeFilter method to asynchronous.
  • IConsumerClient interface OnMessage and OnLog is from event to delegate.

Features:

  • Performance improvement
  • Add support publish delay message. (#1237)
  • Dashbord support viewing and immediately publish for delayed messages.
  • Add support for metrics diagnostics. (#1230)
  • Dashboard support real-time metric graph viewing.
  • Add support manual start/stop CAP process. (#1238)
  • Add EnableConsumerPrefetch option of consumer. (#1240)
  • Add PublishConfirms options for RabbitMQ.

Others:

  • Change framework target from netstandard to net6.
  • Upgrade NuGet to the latest version.

Bug Fixed:

  • RabbitMQ cluster connection failed without using default ports. (#1232)
"},{"location":"about/release-notes/#version-621-2022-10-15","title":"Version 6.2.1 (2022-10-15)","text":"

Bug Fixed:

  • Fixed EnvironmentVariableTarget.Machine only supported on windows. (#1220) Thanks @cuibty
  • Fixed RedisStream TryGetOrCreateStreamGroupAsync to create ConsumerGroup when not found. (#1212) Thanks @mlatoszek
"},{"location":"about/release-notes/#version-620-2022-09-19","title":"Version 6.2.0 (2022-09-19)","text":"

Features:

  • Add Chinese support for dashboard localization. (#1157) Thanks @tetris1128
  • Make DbTransaction property virtual for extend of CapTransactionBase. (#1179) @yang-xiaodong
  • Add logs for duplicate subscriber in same group. (#1186) @yang-xiaodong
  • Record the Instance Id in the executed received messages. (#1187) @yang-xiaodong

Bug Fixed:

  • SnowflakeId excludes virtual and loopback and non-working NICs. (#1163) Thanks @xiatiandegaga
  • Fixed the health check could not get the status correctly when RabbitMQ lost connection and quickly recovered. (#1193) Thanks @rpenha
  • Fixed dashboard gateway proxy request missing QueryString (#1168) Thanks @wwwu
  • Fixed the disconnect detection of RabbitMQ connection abnormality. (#1178)
  • Fixed Mongo queries not returning results when a element convention name is registered. (#1193) Thanks @rpenha
  • Fixed subscriber lookup in scoped lifecycle of factory mode. (#1204) Thanks @sampsonye
"},{"location":"about/release-notes/#version-610-2022-06-10","title":"Version 6.1.0 (2022-06-10)","text":"

Features:

  • Optimize snowflake algorithm. (#1065) Thanks @Allen-dududu
  • Add authorization policy option feature to CAP dashboard. (#1113) Thanks @albertopm19
  • Added support of ScheduledEnqueueTimeUtc for AzureServiceBus transport. (#1137) Thanks @webinex
  • Add option to configure failed messages expiration term. (#1142) Thanks @dima-zhemkov

Bug Fixed:

  • Fixed sequence validation error when both enable Challenge and Auth of dashboard authentication. (#1097)
  • Used concurrentdictionary since PublishedMessages and ReceivedMessages are public and accessed from various places. (#1104) Thanks @wakiter
  • Fixed the health check could not get the status correctly when RabbitMQ lost connection and quickly recovered. (#1140)
  • Fixed date file format bug when retrying query from database. (#1143)
  • Change reading/creating streams and consumer groups to handle non idempotent operations. (#1150) Thanks @MahmoudSamir101
"},{"location":"about/release-notes/#version-601-2022-02-15","title":"Version 6.0.1 (2022-02-15)","text":"

Bug Fixed:

  • Fixed kafka consume excepiton for GroupLoadInProress errcode (#1085)
  • Fixed deserialization exception when message body is empty byte array. (#1087)
  • Fixed dashboard authentication challenge bug. (#1077)
"},{"location":"about/release-notes/#version-600-2022-01-06","title":"Version 6.0.0 (2022-01-06)","text":"

Features:

  • Fully support .NET 6.
  • Add support for OpenTelemetry. (#885)
  • Improve support for NATS JetStream wildcard topic. (#1047)
  • Add support customer header options for Azure Service Bus. (#1063) Thanks @Mateus Viegas
"},{"location":"about/release-notes/#version-520-2021-11-12","title":"Version 5.2.0 (2021-11-12)","text":"

Features:

  • Add support for NATS JetStream. (#983)
  • Add support for Apache Pulsar. (#610)
  • Add possibility to process messages for each consumer group indepedently. (#1027)

Bug Fixed:

  • Fixed message content of bigint type cannot be displayed correctly in dashboard. (#1028)
  • Fixed unobserved tasks of async method calls in Amazon SQS. (#1033)
  • Fixed RabbitMQ federation plugin message header object values cause exceptions. (#1036)
"},{"location":"about/release-notes/#version-512-2021-07-26","title":"Version 5.1.2 (2021-07-26)","text":"

Bug Fixed:

  • Fixed consumer register cancellation token source null referencee bug. (#952)
  • Fixed redis streams transport cluster keys cross-hashslot bug. (#944)
"},{"location":"about/release-notes/#version-511-2021-07-09","title":"Version 5.1.1 (2021-07-09)","text":"

Features:

  • Improve flow control for message cache of in memory. (#935)
  • Add cancellation token support to subscribers. (#912)
  • Add pathbase options for dashboard. (#901)
  • Add custom authorization scheme support for dashboard. (#906)

Bug Fixed:

  • Fixed mysql connect timeout expired bug. (#931)
  • Fixed consul health check path invalid bug. (#921)
  • Fixed mongo dashboard query bug. (#909)
"},{"location":"about/release-notes/#version-510-2021-06-07","title":"Version 5.1.0 (2021-06-07)","text":"

Features:

  • Add configure options for json serialization. (#879)
  • Add Redis Streams transport support. (#817)
  • New dashboard build with vue. (#880)
  • Add subscribe filter support. (#894)

Bug Fixed:

  • Fixed use CapEFDbTransaction to get dbtransaction extension method bug. (#868)
  • Fixed pending message has not been deleted from buffer list in SQL Server. (#889)
  • Fixed dispatcher processing when storage message exception bug. (#900)
"},{"location":"about/release-notes/#version-503-2021-05-14","title":"Version 5.0.3 (2021-05-14)","text":"

Bug Fixed:

  • Fix the bug of getting db transaction through the IDbContextTransaction for SQLServer. (#867)
  • Fix RabbitMQ Connection close forced. (#861)
"},{"location":"about/release-notes/#version-502-2021-04-28","title":"Version 5.0.2 (2021-04-28)","text":"

Features:

  • Add support for Azure Service Bus sessions. (#829)
  • Add custom message headers support for RabbitMQ consumer. (#818)

Bug Fixed:

  • Downgrading Microsoft.Data.SqlClient to 2.0.1. (#839)
  • DiagnosticObserver does not use null connection. (#845)
  • Fix null reference in AmazonSQSTransport. (#846)
"},{"location":"about/release-notes/#version-501-2021-04-07","title":"Version 5.0.1 (2021-04-07)","text":"

Features:

  • Add KafkaOptions.MainConfig to AutoCreateTopic. (#810)
  • Add support rewriting the default configuration of Kafka consumer. (#822)
  • Add DefaultChallengeScheme dashboard options to specify dashboard auth challenge scheme. (#815)

Bug Fixed:

  • Fixed topic selector in IConsumerServiceSelector. (#806)
  • Update AWS topic subscription and SQS access policy generation. (#808)
  • Fixed memory leak when using transction to publish message. (#816)
  • Fixed SQL content filter on IMonitoringApi.PostgreSql.cs. (#814)
  • Fixed the expiration time display problem in the dashboard due to time zone issues (#820)
  • Fixed the creation timing of Kafka automatically creating Topic. (#823)
  • Fixed Dashboard metric not update. (#819)
"},{"location":"about/release-notes/#version-500-2021-03-23","title":"Version 5.0.0 (2021-03-23)","text":"

Features:

  • Upgrade to .NET Standard 2.1 and support .NET 5. (#727)
  • Replace Newtonsoft.Json to System.Text.Json. (#740)
  • Support NATS Transport. (#595,#743)
  • Enabling publiser confirms for RabbitMQ. (#730)
  • Support query subscription from DI implementation factory. (#756)
  • Add options to create lazy queue for RabbitMQ. (#772)
  • Support to add custom tags for Consul. (#786)
  • Support custom group and topic prefiex. (#780)
  • Renemae DefaultGroup option to DefaultGroupName.
  • Add auto create topic at startup for Kafka. (#795,#744)

Bug Fixed:

  • Fixed retrying process earlier than consumer registration to DI. (#760)
  • Fixed Amazon SQS missing pagination topics. (#765)
  • Fixed RabbitMQ MessageTTL option to int type. (#787)
  • Fixed Dashboard auth. (#793)
  • Fixed ClientProvidedName could not be renamed for RabbitMQ. (#791)
  • Fixed EntityFramework transaction will not rollback when exception occurred. (#798)
"},{"location":"about/release-notes/#version-312-2020-12-03","title":"Version 3.1.2 (2020-12-03)","text":"

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:

  • Fixed InmemoryQueue expired messages are not removed bug. (#691)
  • Fixed Executor key change lead to possible null reference exception. (#698)
  • Fixed Postgresql delete expires data logic error. (#714)
"},{"location":"about/release-notes/#version-311-2020-09-23","title":"Version 3.1.1 (2020-09-23)","text":"

Features:

  • Add consumer parameter with interface suppport. (#669)
  • Add custom correlation id and message id support. (#668)
  • Enhanced custom serialization support. (#641)

Bug Fixed:

  • Solve the issue of being duplicated executors from different assemblies. (#666)
  • Added comparer to remove duplicate ConsumerExecutors. (#653)
  • Add re-enable the auto create topics configuration item for Kafka, it's false by default. now is true. (#635)
  • Fixed postgresql transaction rollback invoke bug. (#640)
  • Fixed SQLServer table name customize bug. (#632)
"},{"location":"about/release-notes/#version-310-2020-08-15","title":"Version 3.1.0 (2020-08-15)","text":"

Features:

  • Add Amazon SQS support. (#597)
  • Remove Dapper and replace with ADO.NET in storage project. (#583)
  • Add debug symbols package to nuget.
  • Upgrade dependent nuget package version to latest.
  • English docs grammar correction. Thanks @mzorec

Bug Fixed:

  • Fix mysql transaction rollback bug. (#598)
  • Fix dashboard query bug. (#600)
  • Fix mongo db query bug. (#611)
  • Fix dashboard browser language detection bug. (#631)
"},{"location":"about/release-notes/#version-304-2020-05-27","title":"Version 3.0.4 (2020-05-27)","text":"

Bug Fixed:

  • Fix kafka consumer group does not works bug. (#541)
  • Fix cast object to primitive types failed bug. (#547)
  • Fix subscriber primitive types convert exception. (#568)
  • Add conosole app sample.
  • Upgrade Confluent.Kafka to 1.4.3
"},{"location":"about/release-notes/#version-303-2020-04-01","title":"Version 3.0.3 (2020-04-01)","text":"

Bug Fixed:

  • Change ISubscribeInvoker interface access modifier to public. (#537)
  • Fix rabbitmq connection may be reused when close forced. (#533)
  • Fix dashboard message reexecute button throws exception bug. (#525)
"},{"location":"about/release-notes/#version-302-2020-02-05","title":"Version 3.0.2 (2020-02-05)","text":"

Bug Fixed:

  • Fixed diagnostics event data object error. (#504 )
  • Fixed RabbitMQ transport check not working. (#503 )
  • Fixed Azure Service Bus subscriber error. (#502 )
"},{"location":"about/release-notes/#version-301-2020-01-19","title":"Version 3.0.1 (2020-01-19)","text":"

Bug Fixed:

  • Fixed Dashboard requeue and reconsume failed bug. (#482 )
  • Fixed Azure service bus null reference exception. (#483 )
  • Fixed type cast exception from storage. (#473 )
  • Fixed SqlServer connection undisponse bug. (#477 )
"},{"location":"about/release-notes/#version-300-2019-12-30","title":"Version 3.0.0 (2019-12-30)","text":"

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:

  • Supports .NET Core 3.1.
  • Upgrade dependent packages.
  • New serialization interface ISerializer to support serialization of message body sent to MQ.
  • Add new api for ICapPublisher to publish message with headers.
  • Diagnostics event structure and names improved. #378
  • Support consumer method to read the message headers. #472
  • Support rename message storage tables. #435
  • Support for Kafka to write such as Offset and Partition to the header. #374
  • Improved the processor retry interval time. #444

Bug Fixed:

  • Fixed SqlServer dashboard sql query bug. #470
  • Fixed Kafka health check bug. #436
  • Fixed dashboard bugs. #412 #404
  • Fixed transaction bug for sql server when using EF. #402
"},{"location":"about/release-notes/#version-260-2019-08-29","title":"Version 2.6.0 (2019-08-29)","text":"

Features:

  • Improvement Diagnostic support. Thanks @gfx687
  • Improvement documention. https://cap.dotnetcore.xyz
  • Improvement ConsumerInvoker implementation. Thanks @hetaoos
  • Support multiple consumer threads. (#295)
  • Change DashboardMiddleware to async. (#390) Thanks @liuzhenyulive

Bug Fixed:

  • SQL Server Options Bug.
  • Fix transaction scope disposed bug. (#365)
  • Fix thread safe issue of ICapPublisher bug. (#371)
  • Improved Ctrl+C action raised exception issue.
  • Fixed asynchronous exception catching bug of sending.
  • Fix MatchPoundUsingRegex \".\" not escaped bug (#373)
"},{"location":"about/release-notes/#version-251-2019-06-21","title":"Version 2.5.1 (2019-06-21)","text":"

Features:

  • Improved logs record.
  • Upgrade dependent nuget packages version. (MySqlConnector, confluent-kafka-dotnet-1.0 )
  • NodeId type change to string of DiscoveryOptions for Consul. (#314)
  • Change the IConsumerServiceSelector interface access modifier to public. (#333)
  • Improved RabbitMQOptions to provide extensions option to configure the client original configuration. (#350)
  • Add index for MongoDB CAP collections. (#353)

Bugs Fixed:

  • Fixed consumer re-register transport bug. (#329)
  • Handle messages retrieval failure. (#324)
  • Fixed DiagnosticListener null reference exception bug. (#335)
  • Add subscription name validation for the AzureServerBus. (#344)
  • Fixed thread safety issues of publisher. (#331)
"},{"location":"about/release-notes/#version-250-2019-03-30","title":"Version 2.5.0 (2019-03-30)","text":"

Features:

  • Support Azure Service Bus. (#307)
  • Support In-Memory Storage. (#296)
  • Upgrade Dapper to version 1.60.1
  • Support read environment variables CAP_WORKERID and CAP_DATACENTERID as the snowflake algorithm workerid and datacenterid.

Bug Fixed:

  • Modify MySQL cap table encoding to utf8mb4. (#305)
  • Move CapSubscribeAttribute class to DotNetCore.CAP project.
  • Fixed multiple instance snowflake algorithm generating primary key conflicts. (#294)
"},{"location":"about/release-notes/#version-242-2019-01-08","title":"Version 2.4.2 (2019-01-08)","text":"

Features:

  • Startup the CAP with the .NET Core 2.1 BackgroundService. (#265)
  • Improved message delivery performance. #261

Bug Fixed:

  • Fixed PostgreSql version isolation feature bug. (#256)
  • Fixed SQL Server sql bug for dashboard search. (#266)
"},{"location":"about/release-notes/#version-241-2018-12-19","title":"Version 2.4.1 (2018-12-19)","text":"

Bug Fixed:

  • Fixed MongoDB version isolation feature bug. (#253)
"},{"location":"about/release-notes/#version-240-2018-12-08","title":"Version 2.4.0 (2018-12-08)","text":"

Features:

  • Supported version options. (#220)
  • Upgrade nuget package to .net core 2.2.

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:

  • Fixed different groups of the same topic name in one instance will cause routing bug. (#235)
  • Fixed message presistence bug. (#240)
  • Fixed RabbitMQ topic name contains numbers will cause exception bug. (#181)
"},{"location":"about/release-notes/#version-231-2018-10-29","title":"Version 2.3.1 (2018-10-29)","text":"

Features:

  • Add Source Link Support
  • Upgrade dependent NuGet packages.

Bug Fixed:

  • Fixed dashboard messages requeue error. (#205)
  • Adjustment snowflake workerId to random id.
  • Fixed flush unclaer data bug.
"},{"location":"about/release-notes/#version-230-2018-08-30","title":"Version 2.3.0 (2018-08-30)","text":"

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:

  • Removed app.UseCap() from Startup.cs
  • Message table primary key data type has been modified to Bigint and non auto-Increment. (#180)
  • New publisher Api. (#188)

Features:

  • MongoDb supported. (#143)
  • Automatic commit transaction. (#191)

Bug Fixed:

  • Fix message still sent if transaction faild bug. (#118)
  • Multiple events in one transaction. (#171)
"},{"location":"about/release-notes/#version-225-2018-07-19","title":"Version 2.2.5 (2018-07-19)","text":"

Features: - Performance improvement

Bug Fixed:

  • Fixed message enqueue exception.
  • Fixed Retry processor bugs.
  • Fixed Kafka producer exception log without logging when publish message.
  • Fixed Incorrect local IP address judgment of IPv6. (#140)
  • Fixed DateTime localization format conversion error to sql. (#139)
  • Fixed dashboard message page re-requeue and re-executed operate bug. (#158)
  • Fixed SendAsync or ExecuteAsync recursion retries bug. (#160)
  • Fixed configuration options of FailedThresholdCallback could not be invoke when the value less then three. (#161)
"},{"location":"about/release-notes/#version-224-2018-06-05","title":"Version 2.2.4 (2018-06-05)","text":"

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:

  • Improved log output.
  • Upgrade nuget packages.
  • Support pattern matching for consumer. (#132)

Bug Fixed:

  • Fixed exception thrown when terminate the program with Ctrl+C. (#130)
"},{"location":"about/release-notes/#version-222-2018-04-28","title":"Version 2.2.2 (2018-04-28)","text":"

Features:

  • Improved log output. #114
  • Add default timeout configuration for kafka client.
  • Rename configuration options FailedCallback to FailedThresholdCallback.

Bug Fixed:

  • Fixed message enqueue exception.
  • Fixed retry processor bugs.
  • Fixed kafka producer exception log without logging when publish message.
"},{"location":"about/release-notes/#version-221-2018-04-18","title":"Version 2.2.1 (2018-04-18)","text":"

Bug Fixed:

  • Fixed message enqueue bug in v2.2
"},{"location":"about/release-notes/#version-220-2018-04-17","title":"Version 2.2.0 (2018-04-17)","text":"

Features:

  • Remove database queue mode. (#102)
  • Support for Diagnostics. (#112)
  • Upgrade dependent nuget packages.

Bug Fixed:

  • Fixed bug of the FailedRetryCount does not increase when raised SubscribeNotFoundException. (#90)
"},{"location":"about/release-notes/#version-214-2018-03-16","title":"Version 2.1.4 (2018-03-16)","text":"

Features:

  • Remove TableNamePrefix option from MySqlOptions to EFOptions.
  • Upgrade nuget package

Bug Fixed:

  • Fixed the connection bug of getting message from table. (#83)
  • Fixed entityframework rename table name prefix bug. (#84)
  • Fixed sql server scripts bug of create table scheme. (#85)
  • Fixed thread safety issue about KafkaOptions.(#89)
"},{"location":"about/release-notes/#version-213-2018-01-24","title":"Version 2.1.3 (2018-01-24)","text":"

Features:

  • Upgrade dependent nuget packages version.
  • NuGet package include xml doc now.
  • NuGet now contains the CAP symbol files.

Bug Fixed:

  • Fixed thread conflict issue when sending messages with PublishAsync. (#80)
  • Fixed kafka received message sava failed may caused the mssage loss bug. (#78)
  • Fixed dashboard js syntax issue. (#77)
"},{"location":"about/release-notes/#version-212-2017-12-18","title":"Version 2.1.2 (2017-12-18)","text":"

Bug Fixed:

  • Fixed and improve the performance of mysql processing messages. (#68) (#36)
  • Fixed dashboard manually trigger reconsumption bug. (#67)
  • Fixed mysql 5.5 table initialization bug. (#65)
  • Fixed mysql message queue executor bug. (#66)
"},{"location":"about/release-notes/#version-211-2017-11-28","title":"Version 2.1.1 (2017-11-28)","text":"

Bug Fixed:

  • Fixed 'dotnet.exe' process incomplete quit when shutdown application (Ctrl+C). (#64)
  • Fixed failure to issue as expected of RabbitMQ SubscriberNotFoundException. (#63)
  • Fixed Sent async message in the loop causes an exception. (#62)
"},{"location":"about/release-notes/#version-210-2017-11-17","title":"Version 2.1.0 (2017-11-17)","text":"

Features:

  • Interface display optimization of dashboard.
  • Adds a more friendly display when looks at the message content.
  • Now you can see the exception infomation in the message conent filed when message send or executed failed.
  • Optimize LAN to see Dashboard without authentication.
  • Add IContentSerializer interface, you can customize the serialized message content.
  • Add IMessagePacker interface, you can customize wapper of the message.
  • Upgrade the dependent package.

Bug Fixed:

  • Fixed dashboard query bugs.
  • Fixed dashboard multilanguage display bugs.
  • Fixed RabbitMQ connection pool bug.
  • Fixed dashboard display bugs on mobile.
"},{"location":"about/release-notes/#version-202-2017-09-29","title":"Version 2.0.2 (2017-09-29)","text":"

Bug Fixed:

  • Fixed asp.net core 2.0 startup error of MySql and PostgreSql. (#44
"},{"location":"about/release-notes/#version-201-2017-09-16","title":"Version 2.0.1 (2017-09-16)","text":"

Bug Fixed:

  • DbContext services bug. (#44)
  • Dependency injection bug. (#45)
"},{"location":"about/release-notes/#version-200-2017-09-01","title":"Version 2.0.0 (2017-09-01)","text":"

Features:

  • Supported .net standard 2.0.
  • Supported PostgreSQL 9.5+.
  • Supported asynchronous function subscriptions.
  • ICapPublisher api supported callback subsrciber.

Bug Fixed:

  • Fixed multiple subscriber subscribe bug. (#38)
  • Fixed model binde bug. (#17) (#18)
  • Fixed database connection disposed bug. (#25)
  • Fixed consumer method injection context bug. (#34)
"},{"location":"about/release-notes/#version-110-2017-08-04","title":"Version 1.1.0 (2017-08-04)","text":"

Features:

  • Support MySQL database persistent message.
  • Add message failed call-back in CapOptions.
  • Remove publish messages API of string name at ICapPublisher.

Bug Fixed:

  • Fixed can not send message for string type. (#17)
  • Fixed model bind for type like datetime guid always failed. (#18)
"},{"location":"about/release-notes/#version-101-2017-07-25","title":"Version 1.0.1 (2017-07-25)","text":"

Features:

  • ICapPublisher interface added synchronous publish API.
  • Add infinity retry failed processor.
"},{"location":"about/release-notes/#version-100-2017-07-19","title":"Version 1.0.0 (2017-07-19)","text":"
  • Project published
"},{"location":"user-guide/en/cap/configuration/","title":"Configuration","text":"

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.

"},{"location":"user-guide/en/cap/configuration/#defaultgroupname","title":"DefaultGroupName","text":"

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.

"},{"location":"user-guide/en/cap/configuration/#usestoragelock","title":"UseStorageLock","text":"

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.

"},{"location":"user-guide/en/cap/configuration/#failedthresholdcallback","title":"FailedThresholdCallback","text":"

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.

"},{"location":"user-guide/en/cap/configuration/#succeedmessageexpiredafter","title":"SucceedMessageExpiredAfter","text":"

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.

"},{"location":"user-guide/en/cap/configuration/#failedmessageexpiredafter","title":"FailedMessageExpiredAfter","text":"

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.

"},{"location":"user-guide/en/cap/configuration/#usedispatchingpergroup","title":"UseDispatchingPerGroup","text":"

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.

"},{"location":"user-guide/en/cap/configuration/#obsolete-enableconsumerprefetch","title":"[Obsolete] EnableConsumerPrefetch","text":"

Default: false\uff0c Before version 7.0 the default behavior is true

Renamed to EnableSubscriberParallelExecute option, Please use the new option.

"},{"location":"user-guide/en/cap/configuration/#enablesubscriberparallelexecute","title":"EnableSubscriberParallelExecute","text":"

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.

"},{"location":"user-guide/en/cap/configuration/#subscriberparallelexecutebufferfactor","title":"SubscriberParallelExecuteBufferFactor","text":"

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.

"},{"location":"user-guide/en/cap/configuration/#enablepublishparallelsend","title":"EnablePublishParallelSend","text":"

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(*)
  • At Most Once
  • At Least Once

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":"
  1. 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.

  2. 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.

  3. 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.

  4. 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.

"},{"location":"user-guide/en/cap/idempotence/#explicitly-handling-redeliveries","title":"Explicitly handling redeliveries","text":"

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.

  1. The chapter refers to the Delivery guarantees of rebus, which I think is described very good.\u00a0\u21a9

"},{"location":"user-guide/en/cap/messaging/","title":"Message","text":"

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.

"},{"location":"user-guide/en/cap/messaging/#compensating-transaction","title":"Compensating transaction","text":"

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.

"},{"location":"user-guide/en/cap/serialization/#custom-serialization","title":"Custom Serialization","text":"
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:

  • register a user profile
  • do some automated background check that the user can actually access the system

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 user microservice tasked with registering a user profile
  • the validation microservice tasked with doing a background check
  • the messaging platform that supports persistent queues

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:

  • the user microservice registers a user, saving information about him in its local database
  • the user microservice marks this user with a flag. It could signify that this user hasn\u2019t yet been validated and doesn\u2019t have access to full system functionality
  • a confirmation of registration is sent to the user with a warning that not all functionality of the system is accessible right away
  • the user microservice sends a message to the validation microservice to do the background check of a user
  • the validation microservice runs the background check and sends a message to the user microservice with the results of the check
  • if the results are positive, the user microservice unblocks the user
  • if the results are negative, the user microservice deletes the user account

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:

  • if the validation microservice is not accessible, then the messaging platform with its persistent queue functionality ensures that the validation microservice would receive this message at some later time
  • suppose the messaging platform fails, then the user microservice tries to send the message again at some later time, for example, by scheduled batch-processing of all users that were not yet validated
  • if the validation microservice receives the message, validates the user but can\u2019t send the answer back due to the messaging platform failure, the validation microservice also retries sending the message at some later time
  • if one of the messages got lost, or some other failure happened, the user microservice finds all non-validated users by scheduled batch-processing and sends requests for validation again

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.

  1. This chapter is quoted from: https://www.baeldung.com/transactions-across-microservices \u21a9\u21a9

"},{"location":"user-guide/en/getting-started/contributing/","title":"Contribution","text":"

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

"},{"location":"user-guide/en/getting-started/introduction/","title":"Introduction","text":"

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.

"},{"location":"user-guide/en/monitoring/dashboard/#dashboard-configuration","title":"Dashboard Configuration","text":"
  • PathMatch

Default \uff1a'/cap'

You can change the path of the Dashboard by modifying this configuration option.

  • StatsPollingInterval

Default: 2000ms

This configuration option is used to configure the Dashboard front end to get the polling time of the status interface (/stats).

  • AllowAnonymousExplicit

Default: true

Explicitly allows anonymous access for the CAP dashboard API, passing AllowAnonymous to the ASP.NET Core global authorization filter.

  • AuthorizationPolicy

Default: null.

Authorization policy for the Dashboard. Required if AllowAnonymousExplicit is false.

"},{"location":"user-guide/en/monitoring/dashboard/#custom-authentication","title":"Custom Authentication","text":"

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.

"},{"location":"user-guide/en/monitoring/dashboard/#example-anonymous-access","title":"Example: Anonymous Access","text":"
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:

  • Before the message is persisted
  • After the message is persisted
  • Message persistence exception
  • Before the message is sent to MQ
  • After the message is sent to MQ
  • The message sends an exception to MQ.
  • Messages saved from MQ consumption before saving
  • After the message is saved from MQ consumption
  • Before the subscriber method is executed
  • After the subscriber method is executed
  • Subscriber method execution exception

Related objects, you can find at the DotNetCore.CAP.Diagnostics namespace.

"},{"location":"user-guide/en/monitoring/diagnostics/#tracing-with-apache-skywalking","title":"Tracing with Apache Skywalking","text":"

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:

  • Publish rate pre seconds
  • Consume rate pre seconds
  • Invoke Subscriber rate pre seconds
  • Subscriber elpased time mean pre seconds
"},{"location":"user-guide/en/monitoring/diagnostics/#monitor-with-dotnet-counters","title":"Monitor with dotnet-counters","text":"

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:

  • SQL Server
  • MySQL
  • PostgreSql
  • MongoDB
  • In-Memory Storage

After CAP is started, two tables are generated in used storage, by default the name is Cap.Published and Cap.Received.

"},{"location":"user-guide/en/storage/general/#storage-data-structure","title":"Storage Data Structure","text":"

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"},{"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 string

The Id field is generate using the mongo objectid algorithm.

"},{"location":"user-guide/en/storage/general/#community-supported-extensions","title":"Community-supported extensions","text":"

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

"},{"location":"user-guide/en/storage/in-memory-storage/","title":"In-Memory Storage","text":"

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.

"},{"location":"user-guide/en/storage/sqlserver/#configuration","title":"Configuration","text":"

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.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.

"},{"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:

  • RabbitMQ
  • Kafka
  • Azure Service Bus
  • Amazon SQS
  • NATS
  • In-Memory Queue
  • Redis Streams
  • Apache Pulsar
"},{"location":"user-guide/en/transport/general/#how-to-select-a-transport","title":"How to select a transport","text":"\ud83c\udff3\u200d\ud83c\udf08 RabbitMQ Kafka Azure Service Bus In-Memory Positioning Reliable message transmission Real time data processing Cloud In-Memory, testing Distributed \u2714 \u2714 \u2714 \u274c Persistence \u2714 \u2714 \u2714 \u274c Performance Medium High Medium High

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

"},{"location":"user-guide/en/transport/general/#community-supported-transport-extensions","title":"Community-supported transport extensions","text":"

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

"},{"location":"user-guide/en/transport/in-memory-queue/","title":"In-Memory Queue","text":"

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

Versions of CAP below 5.2.0 are implement based on Request/Response mode, and now we are based on JetStream implementation. see https://github.com/dotnetcore/CAP/issues/983 for more information.

"},{"location":"user-guide/en/transport/nats/#configuration","title":"Configuration","text":"

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, use ConsumerOptions 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

"},{"location":"user-guide/en/transport/pulsar/","title":"Apache Pulsar","text":"

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 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"},{"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

"},{"location":"user-guide/en/transport/redis-streams/#streams-cleanup-notes","title":"Streams Cleanup Notes","text":"

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

"},{"location":"user-guide/zh/cap/configuration/#_2","title":"\u4ec0\u4e48\u662f\u6700\u4f4e\u914d\u7f6e\uff1f","text":"

\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

"},{"location":"user-guide/zh/cap/configuration/#defaultgroupname","title":"DefaultGroupName","text":"

\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

"},{"location":"user-guide/zh/cap/configuration/#usestoragelock","title":"UseStorageLock","text":"

\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

"},{"location":"user-guide/zh/cap/configuration/#failedthresholdcallback","title":"FailedThresholdCallback","text":"

\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

"},{"location":"user-guide/zh/cap/configuration/#failedmessageexpiredafter","title":"FailedMessageExpiredAfter","text":"

\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

"},{"location":"user-guide/zh/cap/configuration/#usedispatchingpergroup","title":"UseDispatchingPerGroup","text":"

\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

"},{"location":"user-guide/zh/cap/configuration/#enableconsumerprefetch","title":"[\u5df2\u8fc7\u65f6] EnableConsumerPrefetch","text":"

\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

"},{"location":"user-guide/zh/cap/configuration/#enablesubscriberparallelexecute","title":"EnableSubscriberParallelExecute","text":"

\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

"},{"location":"user-guide/zh/cap/configuration/#subscriberparallelexecutebufferfactor","title":"SubscriberParallelExecuteBufferFactor","text":"

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.

"},{"location":"user-guide/zh/cap/configuration/#enablepublishparallelsend","title":"EnablePublishParallelSend","text":"

\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

  • Exactly Once(*) \uff08\u4ec5\u6709\u4e00\u6b21\uff09
  • At Most Once \uff08\u6700\u591a\u4e00\u6b21\uff09
  • At Least Once \uff08\u6700\u5c11\u4e00\u6b21\uff09

\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

"},{"location":"user-guide/zh/cap/idempotence/#_6","title":"\u663e\u5f0f\u5904\u7406\u5e42\u7b49\u6d88\u606f","text":"

\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

"},{"location":"user-guide/zh/cap/messaging/","title":"\u6d88\u606f","text":"

\u4f7f\u7528 ICapPublisher \u63a5\u53e3\u53d1\u9001\u51fa\u53bb\u7684\u6570\u636e\u79f0\u4e4b\u4e3a Message (\u6d88\u606f)\u3002

"},{"location":"user-guide/zh/cap/messaging/#_2","title":"\u53d1\u9001 & \u5904\u7406\u6d88\u606f","text":"

\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

"},{"location":"user-guide/zh/cap/messaging/#_3","title":"\u8865\u507f\u4e8b\u52a1","text":"

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

"},{"location":"user-guide/zh/cap/serialization/#_2","title":"\u81ea\u5b9a\u4e49\u5e8f\u5217\u5316","text":"
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

"},{"location":"user-guide/zh/getting-started/introduction/","title":"\u4ecb\u7ecd","text":"

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

"},{"location":"user-guide/zh/monitoring/dashboard/#dashboard_2","title":"Dashboard \u914d\u7f6e\u9879","text":"
  • PathBase

\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

  • PathMatch

\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

  • StatsPollingInterval

\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

  • AllowAnonymousExplicit

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

  • AuthorizationPolicy

Default: null.

Dashboard \u7684\u6388\u6743\u7b56\u7565\u3002 \u9700\u8bbe\u7f6e AllowAnonymousExplicit\u4e3a false\u3002

"},{"location":"user-guide/zh/monitoring/dashboard/#_1","title":"\u81ea\u5b9a\u4e49\u8ba4\u8bc1","text":"

\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

"},{"location":"user-guide/zh/monitoring/dashboard/#example","title":"Example: \u533f\u540d\u8bbf\u95ee","text":"
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

"},{"location":"user-guide/zh/monitoring/dashboard/#anonymous-access","title":"\u4f8b\u5b50\uff1aAnonymous Access \u533f\u540d\u8bbf\u95ee","text":"
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

  • \u6d88\u606f\u6301\u4e45\u5316\u4e4b\u524d
  • \u6d88\u606f\u6301\u4e45\u5316\u4e4b\u540e
  • \u6d88\u606f\u6301\u4e45\u5316\u5f02\u5e38
  • \u6d88\u606f\u5411MQ\u53d1\u9001\u4e4b\u524d
  • \u6d88\u606f\u5411MQ\u53d1\u9001\u4e4b\u540e
  • \u6d88\u606f\u5411MQ\u53d1\u9001\u5f02\u5e38
  • \u6d88\u606f\u4eceMQ\u6d88\u8d39\u4fdd\u5b58\u4e4b\u524d
  • \u6d88\u606f\u4eceMQ\u6d88\u8d39\u4fdd\u5b58\u4e4b\u540e
  • \u8ba2\u9605\u8005\u65b9\u6cd5\u6267\u884c\u4e4b\u524d
  • \u8ba2\u9605\u8005\u65b9\u6cd5\u6267\u884c\u4e4b\u540e
  • \u8ba2\u9605\u8005\u65b9\u6cd5\u6267\u884c\u5f02\u5e38
"},{"location":"user-guide/zh/monitoring/diagnostics/#skywalking-apm-cap","title":"\u5728 Skywalking APM \u4e2d\u8ffd\u8e2a CAP \u4e8b\u4ef6","text":"

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

  • \u6bcf\u79d2\u53d1\u5e03\u901f\u5ea6
  • \u6bcf\u79d2\u6d88\u8d39\u901f\u5ea6
  • \u6bcf\u79d2\u8c03\u7528\u8ba2\u9605\u8005\u901f\u5ea6
  • \u6bcf\u79d2\u6267\u884c\u8ba2\u9605\u8005\u5e73\u5747\u8017\u65f6
"},{"location":"user-guide/zh/monitoring/diagnostics/#dotnet-counters","title":"\u4f7f\u7528 dotnet-counters \u67e5\u770b\u5ea6\u91cf","text":"

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

"},{"location":"user-guide/zh/samples/castle.dynamicproxy/#3-iservicecollection","title":"3\u3001\u521b\u5efa IServiceCollection \u7684\u6269\u5c55\u7c7b","text":"

\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

  • SQL Server
  • MySQL
  • PostgreSql
  • MongoDB
  • In-Memory Storage

\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

"},{"location":"user-guide/zh/storage/general/#_7","title":"\u5b58\u50a8\u683c\u5f0f","text":"

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 string

Received \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 string

Lock \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

NAME DESCRIPTION TYPE Id CAP\u751f\u6210\u7684\u6d88\u606f\u7f16\u53f7 string Timestamp \u6d88\u606f\u521b\u5efa\u65f6\u95f4 string Content \u5185\u5bb9 string CallbackName \u56de\u8c03\u7684\u8ba2\u9605\u8005\u540d\u79f0 string

\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

"},{"location":"user-guide/zh/storage/in-memory-storage/","title":"In-Memory Storage","text":"

\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

"},{"location":"user-guide/zh/storage/sqlserver/#_1","title":"\u914d\u7f6e","text":"

\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\u5934 Func<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

  • RabbitMQ
  • Kafka
  • Azure Service Bus
  • Amazon SQS
  • NATS
  • In-Memory Queue
  • Redis Streams
  • Apache Pulsar
"},{"location":"user-guide/zh/transport/general/#_3","title":"\u600e\u4e48\u9009\u62e9\u8fd0\u8f93\u5668","text":"\ud83c\udff3\u200d\ud83c\udf08 RabbitMQ Kafka Azure Service Bus In-Memory \u5b9a\u4f4d \u53ef\u9760\u6d88\u606f\u4f20\u8f93 \u5b9e\u65f6\u6570\u636e\u5904\u7406 \u4e91 \u5185\u5b58\u578b\uff0c\u6d4b\u8bd5 \u5206\u5e03\u5f0f \u2714 \u2714 \u2714 \u274c \u6301\u4e45\u5316 \u2714 \u2714 \u2714 \u274c \u6027\u80fd Medium High Medium High

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

"},{"location":"user-guide/zh/transport/general/#_4","title":"\u793e\u533a\u652f\u6301\u7684\u8fd0\u8f93\u5668","text":"

\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

"},{"location":"user-guide/zh/transport/in-memory-queue/","title":"In-Memory Queue","text":"

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

CAP 5.2.0 \u4ee5\u4e0b\u7684\u7248\u672c\u57fa\u4e8e Request/Response \u5b9e\u73b0, \u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u57fa\u4e8e JetStream \u5b9e\u73b0\u3002 \u67e5\u770b https://github.com/dotnetcore/CAP/issues/983 \u4e86\u89e3\u66f4\u591a\u3002

"},{"location":"user-guide/zh/transport/nats/#_1","title":"\u914d\u7f6e","text":"

\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

"},{"location":"user-guide/zh/transport/pulsar/","title":"Apache Pulsar","text":"

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\u5982 192.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

"},{"location":"user-guide/zh/transport/redis-streams/#_2","title":"\u6d41\u6e05\u7406\u6ce8\u610f\u4e8b\u9879","text":"

\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..72379c140 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,348 @@ + + + + http://cap.dotnetcore.xyz/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/about/contact-us/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/about/license/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/about/release-notes/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/cap/configuration/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/cap/filter/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/cap/idempotence/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/cap/messaging/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/cap/serialization/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/cap/transactions/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/getting-started/contributing/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/getting-started/introduction/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/getting-started/quick-start/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/monitoring/consul/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/monitoring/dashboard/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/monitoring/diagnostics/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/monitoring/kubernetes/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/monitoring/opentelemetry/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/samples/eshoponcontainers/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/samples/faq/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/samples/github/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/storage/general/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/storage/in-memory-storage/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/storage/mongodb/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/storage/mysql/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/storage/postgresql/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/storage/sqlserver/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/transport/aws-sqs/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/transport/azure-service-bus/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/transport/general/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/transport/in-memory-queue/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/transport/kafka/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/transport/nats/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/transport/pulsar/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/transport/rabbitmq/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/en/transport/redis-streams/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/cap/configuration/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/cap/filter/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/cap/idempotence/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/cap/messaging/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/cap/serialization/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/cap/transactions/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/getting-started/contributing/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/getting-started/introduction/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/getting-started/quick-start/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/monitoring/consul/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/monitoring/dashboard/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/monitoring/diagnostics/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/monitoring/kubernetes/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/monitoring/opentelemetry/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/samples/castle.dynamicproxy/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/samples/eshoponcontainers/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/samples/faq/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/samples/github/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/storage/general/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/storage/in-memory-storage/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/storage/mongodb/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/storage/mysql/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/storage/postgresql/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/storage/sqlserver/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/transport/aws-sqs/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/transport/azure-service-bus/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/transport/general/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/transport/in-memory-queue/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/transport/kafka/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/transport/nats/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/transport/pulsar/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/transport/rabbitmq/ + 2024-05-02 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/transport/redis-streams/ + 2024-05-02 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 000000000..fce22e176 Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/user-guide/en/cap/configuration/index.html b/user-guide/en/cap/configuration/index.html new file mode 100644 index 000000000..1ab21b970 --- /dev/null +++ b/user-guide/en/cap/configuration/index.html @@ -0,0 +1,2477 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Configuration

+

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.

+

What is minimum configuration required for CAP

+

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.

+

Custom configuration

+

The CapOptions is used to store configuration information. By default they have default values, sometimes you may need to customize them.

+

DefaultGroupName

+
+

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.

+
+

GroupNamePrefix

+
+

Default: Null

+
+

Add unified prefixes for consumer group. https://github.com/dotnetcore/CAP/pull/780

+

TopicNamePrefix

+
+

Default: Null

+
+

Add unified prefixes for topic/queue name. https://github.com/dotnetcore/CAP/pull/780

+

Versioning

+
+

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

+
+

FailedRetryInterval

+
+

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.

+
+

UseStorageLock

+
+

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.

+

CollectorCleaningInterval

+
+

Default: 300 sec

+
+

The interval of the collector processor deletes expired messages.

+

ConsumerThreadCount

+
+

Default: 1

+
+

Number of consumer threads, when this value is greater than 1, the order of message execution cannot be guaranteed.

+

FailedRetryCount

+
+

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.

+

FallbackWindowLookbackSeconds

+
+

Default: 240 sec

+
+

Configure the retry processor to pick up the fallback window lookback time for Scheduled or Failed status messages.

+

FailedThresholdCallback

+
+

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.

+

SucceedMessageExpiredAfter

+
+

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.

+

FailedMessageExpiredAfter

+
+

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.

+

UseDispatchingPerGroup

+
+

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.

+

[Obsolete] EnableConsumerPrefetch

+
+

Default: false, Before version 7.0 the default behavior is true

+
+

Renamed to EnableSubscriberParallelExecute option, Please use the new option.

+

EnableSubscriberParallelExecute

+
+

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

+
+

SubscriberParallelExecuteThreadCount

+
+

Default: Environment.ProcessorCount

+
+

With the EnableSubscriberParallelExecute option enabled, specify the number of parallel task execution threads.

+

SubscriberParallelExecuteBufferFactor

+
+

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.

+

EnablePublishParallelSend

+
+

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.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/cap/filter/index.html b/user-guide/en/cap/filter/index.html new file mode 100644 index 000000000..eafb27ba2 --- /dev/null +++ b/user-guide/en/cap/filter/index.html @@ -0,0 +1,2110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Filter - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Filter

+

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 subscribe filter

+

Create Filter

+

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;
+}
+
+

Configuration Filter

+

Use AddSubscribeFilter<> to add a filter.

+
services.AddCap(opt =>
+{
+    // ***
+}.AddSubscribeFilter<MyCapFilter>();
+
+

Currently, we do not support adding multiple filters.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/cap/idempotence/index.html b/user-guide/en/cap/idempotence/index.html new file mode 100644 index 000000000..a699c37b5 --- /dev/null +++ b/user-guide/en/cap/idempotence/index.html @@ -0,0 +1,2267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Idempotence - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Idempotence

+

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.

+

Delivery guarantees1

+

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(*)
  • +
  • At Most Once
  • +
  • At Least Once
  • +
+

Exactly once has a (*) next to it, because in the general case, it is simply not possible.

+

At Most Once

+

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.

+

At Least Once

+

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".

+

What is a "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.

+

Idempotence at CAP

+

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.

+

Why are we not providing(achieving) idempotency ?

+
    +
  1. +

    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.

    +
  2. +
  3. +

    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.

    +
  4. +
  5. +

    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.

    +
  6. +
  7. +

    Industry practices.

    +

    Many event-driven frameworks require users to ensure idempotent operations, such as ENode, RocketMQ, etc...

    +
  8. +
+

From an implementation point of view, CAP can do some less stringent idempotence, but strict idempotent can not be guaranteed.

+

Naturally idempotent message processing

+

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.

+

Explicitly handling redeliveries

+

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.

+
+
+
    +
  1. +

    The chapter refers to the Delivery guarantees of rebus, which I think is described very good. 

    +
  2. +
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/cap/messaging/index.html b/user-guide/en/cap/messaging/index.html new file mode 100644 index 000000000..2076d95bf --- /dev/null +++ b/user-guide/en/cap/messaging/index.html @@ -0,0 +1,2304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Messaging - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Message

+

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.

+
+

Compensating transaction

+

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 };
+}
+
+

Heterogeneous system integration

+

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:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyDataTypeDescription
cap-msg-idstringMessage Id, Generated by snowflake algorithm, can also be guid
cap-msg-namestringThe name of the message
cap-msg-typestringThe type of message, typeof(T).FullName(not required)
cap-senttimestringsending time (not required)
cap-kafka-keystringPartitioning by Kafka Key
+

Custom headers

+

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);
+

+

Scheduling

+

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.

+

Storage

+

CAP will store the message after receiving it. For more information on storage, see the Storage section.

+

Retry

+

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.

+

Send retry

+

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.

+

Consumption 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.

+

Data Cleanup

+

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.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/cap/serialization/index.html b/user-guide/en/cap/serialization/index.html new file mode 100644 index 000000000..e5edb0edd --- /dev/null +++ b/user-guide/en/cap/serialization/index.html @@ -0,0 +1,2054 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Serialization - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Serialization

+

We provide the ISerializer interface to support serialization of messages. By default, json is used to serialize messages and store them in the database.

+

Custom Serialization

+
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 
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/cap/transactions/index.html b/user-guide/en/cap/transactions/index.html new file mode 100644 index 000000000..a2bd9545f --- /dev/null +++ b/user-guide/en/cap/transactions/index.html @@ -0,0 +1,2171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Transactions - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Transaction

+

Distributed transactions?

+

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.

+

Eventual Consistency and Compensation 1

+

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.

+

A Case for Eventual Consistency

+

For example, suppose we need to solve the following task:

+
    +
  • register a user profile
  • +
  • do some automated background check that the user can actually access the system
  • +
+

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 user microservice tasked with registering a user profile
  • +
  • the validation microservice tasked with doing a background check
  • +
  • the messaging platform that supports persistent queues
  • +
+

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

+

Best case scenario

+

In this architecture, best case scenario would be:

+
    +
  • the user microservice registers a user, saving information about him in its local database
  • +
  • the user microservice marks this user with a flag. It could signify that this user hasn’t yet been validated and doesn’t have access to full system functionality
  • +
  • a confirmation of registration is sent to the user with a warning that not all functionality of the system is accessible right away
  • +
  • the user microservice sends a message to the validation microservice to do the background check of a user
  • +
  • the validation microservice runs the background check and sends a message to the user microservice with the results of the check
  • +
  • if the results are positive, the user microservice unblocks the user
  • +
  • if the results are negative, the user microservice deletes the user account
  • +
+

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.

+

Failure Scenarios

+

Now let’s consider some failure scenarios:

+
    +
  • if the validation microservice is not accessible, then the messaging platform with its persistent queue functionality ensures that the validation microservice would receive this message at some later time
  • +
  • suppose the messaging platform fails, then the user microservice tries to send the message again at some later time, for example, by scheduled batch-processing of all users that were not yet validated
  • +
  • if the validation microservice receives the message, validates the user but can’t send the answer back due to the messaging platform failure, the validation microservice also retries sending the message at some later time
  • +
  • if one of the messages got lost, or some other failure happened, the user microservice finds all non-validated users by scheduled batch-processing and sends requests for validation again
  • +
+

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.

+
+
+
    +
  1. +

    This chapter is quoted from: https://www.baeldung.com/transactions-across-microservices 

    +
  2. +
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/getting-started/contributing/index.html b/user-guide/en/getting-started/contributing/index.html new file mode 100644 index 000000000..24eec8fe2 --- /dev/null +++ b/user-guide/en/getting-started/contributing/index.html @@ -0,0 +1,2065 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contributing - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Contribution

+

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:

+

+

+

Submitting Changes

+

You can also contribute by submitting pull requests with code changes.

+
+

Pull requests let you tell others about changes you've pushed to a repository on GitHub. Once a pull request is opened, you can discuss and review the potential changes with collaborators and add follow-up commits before the changes are merged into the repository.

+
+

Additional Resources

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/getting-started/introduction/index.html b/user-guide/en/getting-started/introduction/index.html new file mode 100644 index 000000000..790371bd9 --- /dev/null +++ b/user-guide/en/getting-started/introduction/index.html @@ -0,0 +1,2071 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Introduction - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Introduction

+

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: bilibili Tutorial

+

Video: Youtube Tutorial

+

Video: Youtube Tutorial - @CodeOpinion

+

Video: Tencent Tutorial

+ +

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

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/getting-started/quick-start/index.html b/user-guide/en/getting-started/quick-start/index.html new file mode 100644 index 000000000..cbb2171bf --- /dev/null +++ b/user-guide/en/getting-started/quick-start/index.html @@ -0,0 +1,2229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Quick Start - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Quick Start

+

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.

+

Installation

+
PM> Install-Package DotNetCore.CAP
+
+

Integrated in Asp.Net Core

+

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();
+    });
+}
+
+

Publish Message

+
public class PublishController : Controller
+{
+    [Route("~/send")]
+    public IActionResult SendMessage([FromServices]ICapPublisher capBus)
+    {
+        capBus.Publish("test.show.time", DateTime.Now);
+
+        return Ok();
+    }
+}
+
+

Publish delay message

+
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();
+    }
+}
+
+

Publish with extra header

+
var header = new Dictionary<string, string>()
+{
+    ["my.header.first"] = "first",
+    ["my.header.second"] = "second"
+};
+
+capBus.Publish("test.show.time", DateTime.Now, header);
+
+

Process Message

+
public class ConsumerController : Controller
+{
+    [NonAction]
+    [CapSubscribe("test.show.time")]
+    public void ReceiveMessage(DateTime time)
+    {
+        Console.WriteLine("message time is:" + time);
+    }
+}
+
+

Process with extra header

+
[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"]);
+}
+
+

Summary

+

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.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/monitoring/consul/index.html b/user-guide/en/monitoring/consul/index.html new file mode 100644 index 000000000..de572fcf7 --- /dev/null +++ b/user-guide/en/monitoring/consul/index.html @@ -0,0 +1,2062 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Consul - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Consul

+

Consul is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud.

+

Consul Configuration for dashboard

+

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;"
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/monitoring/dashboard/index.html b/user-guide/en/monitoring/dashboard/index.html new file mode 100644 index 000000000..772e16bad --- /dev/null +++ b/user-guide/en/monitoring/dashboard/index.html @@ -0,0 +1,2233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dashboard - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Dashboard

+

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

+
+

Enable Dashboard

+

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.

+

Dashboard Configuration

+
    +
  • PathMatch
  • +
+
+

Default :'/cap'

+
+

You can change the path of the Dashboard by modifying this configuration option.

+
    +
  • StatsPollingInterval
  • +
+
+

Default: 2000ms

+
+

This configuration option is used to configure the Dashboard front end to get the polling time of the status interface (/stats).

+
    +
  • AllowAnonymousExplicit
  • +
+
+

Default: true

+
+

Explicitly allows anonymous access for the CAP dashboard API, passing AllowAnonymous to the ASP.NET Core global authorization filter.

+
    +
  • AuthorizationPolicy
  • +
+
+

Default: null.

+
+

Authorization policy for the Dashboard. Required if AllowAnonymousExplicit is false.

+

Custom Authentication

+

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.

+

Example: Anonymous Access

+
services.AddCap(cap =>
+    {
+        cap.UseDashboard(d =>
+        {
+            d.AllowAnonymousExplicit = true;
+        });
+        cap.UseInMemoryStorage();
+        cap.UseInMemoryMessageQueue();
+    });
+
+

Example: Open Id

+
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();
+    });
+
+

Example: Custom Authentication Scheme

+
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();
+    });
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/monitoring/diagnostics/index.html b/user-guide/en/monitoring/diagnostics/index.html new file mode 100644 index 000000000..b7eb73b5c --- /dev/null +++ b/user-guide/en/monitoring/diagnostics/index.html @@ -0,0 +1,2177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Diagnostics - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Diagnostics

+

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.

+

Tracing

+

The CAP provides support for DiagnosticSource with a listener name of CapDiagnosticListener.

+

Diagnostics provides tracing event information as follows:

+
    +
  • Before the message is persisted
  • +
  • After the message is persisted
  • +
  • Message persistence exception
  • +
  • Before the message is sent to MQ
  • +
  • After the message is sent to MQ
  • +
  • The message sends an exception to MQ.
  • +
  • Messages saved from MQ consumption before saving
  • +
  • After the message is saved from MQ consumption
  • +
  • Before the subscriber method is executed
  • +
  • After the subscriber method is executed
  • +
  • Subscriber method execution exception
  • +
+

Related objects, you can find at the DotNetCore.CAP.Diagnostics namespace.

+

Tracing with Apache Skywalking

+

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 :

+

+

+

Others APM support

+

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

+

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:

+
    +
  • Publish rate pre seconds
  • +
  • Consume rate pre seconds
  • +
  • Invoke Subscriber rate pre seconds
  • +
  • Subscriber elpased time mean pre seconds
  • +
+

Monitor with dotnet-counters

+

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.

+

img

+

Monitor with dashboard

+

You can configure x.UseDashboard() to open the dashboard to view Metrics graph charts.

+

img

+

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).

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/monitoring/kubernetes/index.html b/user-guide/en/monitoring/kubernetes/index.html new file mode 100644 index 000000000..c3c54554d --- /dev/null +++ b/user-guide/en/monitoring/kubernetes/index.html @@ -0,0 +1,2143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Kubernetes - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Kubernetes

+

Kubernetes, also known as K8s, is an open-source system for automating deployment, scaling, and management of containerized applications.

+

Kubernetes in the Dashboard

+

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.

+

Assign Pod Access to Kubernetes Api

+

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
+
+

Using Dashboard Standalone

+

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.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/monitoring/opentelemetry/index.html b/user-guide/en/monitoring/opentelemetry/index.html new file mode 100644 index 000000000..1ec4ceb75 --- /dev/null +++ b/user-guide/en/monitoring/opentelemetry/index.html @@ -0,0 +1,2116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + OpenTelemetry - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

OpenTelemetry

+

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’s performance and behavior.

+

Integration

+

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.

+

Configuration

+

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")
+});
+
+Here is a diagram of CAP's tracking data in Zipkin:

+

+

Context Propagation

+

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.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/samples/eshoponcontainers/index.html b/user-guide/en/samples/eshoponcontainers/index.html new file mode 100644 index 000000000..46c35a1d2 --- /dev/null +++ b/user-guide/en/samples/eshoponcontainers/index.html @@ -0,0 +1,2042 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + eShopOnContainers - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

eShopOnContainers

+

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 with CAP

+

You can see how to use caps in eShopOnContainers at the Github repository.

+

https://github.com/yang-xiaodong/eShopOnContainers

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/samples/faq/index.html b/user-guide/en/samples/faq/index.html new file mode 100644 index 000000000..3c97a2aa8 --- /dev/null +++ b/user-guide/en/samples/faq/index.html @@ -0,0 +1,2024 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + FAQ - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

FAQ

+
+

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.

+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/samples/github/index.html b/user-guide/en/samples/github/index.html new file mode 100644 index 000000000..61ff53202 --- /dev/null +++ b/user-guide/en/samples/github/index.html @@ -0,0 +1,1991 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Github - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/storage/general/index.html b/user-guide/en/storage/general/index.html new file mode 100644 index 000000000..fcdc195a9 --- /dev/null +++ b/user-guide/en/storage/general/index.html @@ -0,0 +1,2364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + General - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

General

+

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.

+

Persistence

+

Before sent

+

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 sent

+

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.

+

Storage

+

Supported storages

+

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.

+

Storage Data Structure

+

Table structure of Published :

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPE
IdMessage Idint
VersionMessage Versionstring
NameTopic Namestring
ContentJson Contentstring
AddedAdded TimeDateTime
ExpiresAtExpire timeDateTime
RetriesRetry timesint
StatusNameStatus Namestring
+

Table structure of Received :

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPE
IdMessage Idint
VersionMessage Versionstring
NameTopic Namestring
GroupGroup Namestring
ContentJson Contentstring
AddedAdded TimeDateTime
ExpiresAtExpire timeDateTime
RetriesRetry timesint
StatusNameStatus Namestring
+

Table structure of Lock (Optional):

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPE
KeyLock Idstring
InstanceAcquired instance of lockstring
LastLockTimeLast acquired lock timeDateTime
+

Wapper Object

+

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.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPE
IdMessage Idstring
TimestampMessage created timestring
ContentMessage contentstring
CallbackNameConsumer callback topic namestring
+

The Id field is generate using the mongo objectid algorithm.

+

Community-supported extensions

+

Thanks to the community for supporting CAP, the following is the implementation of community-supported storage

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/storage/in-memory-storage/index.html b/user-guide/en/storage/in-memory-storage/index.html new file mode 100644 index 000000000..0cb8ac7b6 --- /dev/null +++ b/user-guide/en/storage/in-memory-storage/index.html @@ -0,0 +1,2067 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + In-Memory - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

In-Memory Storage

+

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.

+

Configuration

+

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.

+

Publish with transaction

+

In-Memory Storage does not support Transaction mode to send messages.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/storage/mongodb/index.html b/user-guide/en/storage/mongodb/index.html new file mode 100644 index 000000000..25cdb9667 --- /dev/null +++ b/user-guide/en/storage/mongodb/index.html @@ -0,0 +1,2155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + MongoDB - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

MongoDB

+

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.

+

Configuration

+

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 ...
+    });
+}
+
+

MongoDB Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
DatabaseNameDatabase namestringcap
DatabaseConnectionDatabase connection stringstringmongodb://localhost:27017
ReceivedCollectionDatabase received message collection namestringcap.received
PublishedCollectionDatabase published message collection namestringcap.published
+

Publish with transaction

+

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();
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/storage/mysql/index.html b/user-guide/en/storage/mysql/index.html new file mode 100644 index 000000000..183996b8b --- /dev/null +++ b/user-guide/en/storage/mysql/index.html @@ -0,0 +1,2188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + MySQL - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

MySQL

+

MySQL is an open-source relational database management system. CAP supports MySQL database.

+

Configuration

+

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 ...
+    });
+}
+
+

MySqlOptions

+ + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
TableNamePrefixCAP table name prefixstringcap
ConnectionStringDatabase connection stringstringnull
+

Publish with transaction

+

ADO.NET with transaction

+
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();
+    }
+}
+
+

EntityFramework with transaction

+
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();
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/storage/postgresql/index.html b/user-guide/en/storage/postgresql/index.html new file mode 100644 index 000000000..0b15ed7de --- /dev/null +++ b/user-guide/en/storage/postgresql/index.html @@ -0,0 +1,2194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + PostgreSql - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Postgre SQL

+

PostgreSQL is an open-source relational database management system. CAP supports PostgreSQL database.

+

Configuration

+

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 ...
+    });
+}
+
+

PostgreSqlOptions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
SchemaDatabase schemastringcap
ConnectionStringDatabase connection stringstring
DataSourceData sourceNpgsqlDataSource
+

Publish with transaction

+

ADO.NET with transaction

+
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();
+    }
+}
+
+

EntityFramework with transaction

+
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();
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/storage/sqlserver/index.html b/user-guide/en/storage/sqlserver/index.html new file mode 100644 index 000000000..5f5dd4bd4 --- /dev/null +++ b/user-guide/en/storage/sqlserver/index.html @@ -0,0 +1,2192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + SQL Server - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

SQL Server

+

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.

+
+

Configuration

+

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 ...
+    });
+}
+
+

SqlServerOptions

+ + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
SchemaDatabase schemastringCap
ConnectionStringDatabase connection stringstring
+

Publish with transaction

+

ADO.NET with transaction

+
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();
+    }
+}
+
+

EntityFramework with transaction

+
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();
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/transport/aws-sqs/index.html b/user-guide/en/transport/aws-sqs/index.html new file mode 100644 index 000000000..c487c1440 --- /dev/null +++ b/user-guide/en/transport/aws-sqs/index.html @@ -0,0 +1,2192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Amazon SQS - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Amazon SQS

+

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.

+

How CAP uses AWS SNS and SQS

+

SNS

+

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)
+{
+}
+
+After CAP startups, you will see in SNS management console:

+

img

+

SQS

+

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:

+

img

+
+

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.

+
+

Configuration

+

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 ...
+    });
+}
+
+

AmazonSQS Options

+

The SQS configuration parameters provided directly by the CAP:

+ + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
RegionAWS RegionAmazon.RegionEndpoint
CredentialsAWS AK SK InformationAmazon.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.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/transport/azure-service-bus/index.html b/user-guide/en/transport/azure-service-bus/index.html new file mode 100644 index 000000000..6c2b5bc5f --- /dev/null +++ b/user-guide/en/transport/azure-service-bus/index.html @@ -0,0 +1,2275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Azure Service Bus - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Azure Service Bus

+

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.

+

Configuration

+
+

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 ...
+    });
+}
+
+

AzureServiceBus Options

+

The AzureServiceBus configuration options provided directly by the CAP:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
ConnectionStringEndpoint addressstring
TopicPathTopic entity pathstringcap
EnableSessionsEnable Service bus sessionsboolfalse
MaxConcurrentSessionsThe maximum number of concurrent sessions that the processor can handle. Not applicable when EnableSessions is false.int8
SessionIdleTimeoutThe 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.TimeSpannull
SubscriptionAutoDeleteOnIdleAutomatically delete subscription after a certain idle interval.TimeSpanTimeSpan.MaxValue
SubscriptionMessageLockDurationThe amount of time the message is locked by a given receiver so that no other receiver receives the same message.TimeSpan60 seconds
SubscriptionDefaultMessageTimeToLiveThe default message time to live value for a subscription. This is the duration after which the message expires.TimeSpanTimeSpan.MaxValue
SubscriptionMaxDeliveryCountThe maximum number of times a message is delivered to the subscription before it is dead-lettered.int10
MaxAutoLockRenewalDurationThe maximum duration within which the lock will be renewed automatically. This value should be greater than the longest message lock duration.TimeSpan5 minutes
ManagementTokenProviderToken providerITokenProvidernull
AutoCompleteMessagesGets a value that indicates whether the processor should automatically complete messages after the message handler has completed processingbooltrue
CustomHeadersBuilderAdds custom and/or mandatory Headers for incoming messages from heterogeneous systems.Func<Message, IServiceProvider, List<KeyValuePair<string, string>>>?null
NamespaceNamespace of Servicebus , Needs to be set when using with TokenCredential Propertystringnull
DefaultCorrelationHeadersAdds additional correlation properties to all correlation filters.IDictionaryDictionary.Empty
SQLFiltersCustom SQL Filters by name and expression on Topic SubscribtionList>null
+

Sessions

+

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.

+

Heterogeneous Systems

+

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)
+    ];
+});
+
+

SQL Filters

+

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 =>
+{
+    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")
+        };
+});
+

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/transport/general/index.html b/user-guide/en/transport/general/index.html new file mode 100644 index 000000000..d89de3567 --- /dev/null +++ b/user-guide/en/transport/general/index.html @@ -0,0 +1,2138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + General - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Transports

+

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.

+

Supported transports

+

CAP supports several transport methods:

+ +

How to select a transport

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
🏳‍🌈RabbitMQKafkaAzure Service BusIn-Memory
PositioningReliable message transmissionReal time data processingCloudIn-Memory, testing
Distributed
Persistence
PerformanceMediumHighMediumHigh
+
+

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

+
+

Community-supported transport extensions

+

Thanks to the community for supporting CAP, the following is the implementation of community-supported transport

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/transport/in-memory-queue/index.html b/user-guide/en/transport/in-memory-queue/index.html new file mode 100644 index 000000000..9948558a3 --- /dev/null +++ b/user-guide/en/transport/in-memory-queue/index.html @@ -0,0 +1,2050 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + In-Memory Queue - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

In-Memory Queue

+

In Memory Queue is a memory-based message queue provided by Community.

+

Configuration

+

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 ...
+    });
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/transport/kafka/index.html b/user-guide/en/transport/kafka/index.html new file mode 100644 index 000000000..db743a059 --- /dev/null +++ b/user-guide/en/transport/kafka/index.html @@ -0,0 +1,2176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apache Kafka® - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Apache Kafka®

+

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.

+

Configuration

+

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 ...
+    });
+}
+
+

Kafka Options

+

The Kafka configuration parameters provided directly by the CAP:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
ServersBroker server addressstring
ConnectionPoolSizeconnection pool sizeint10
CustomHeadersBuilderCustom subscribe headersFunc<>N/A
+

CustomHeadersBuilder Options

+

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"];
+}
+
+

Kafka MainConfig Options

+

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

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/transport/nats/index.html b/user-guide/en/transport/nats/index.html new file mode 100644 index 000000000..a09065cec --- /dev/null +++ b/user-guide/en/transport/nats/index.html @@ -0,0 +1,2156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + NATS - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

NATS

+

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

+

Versions of CAP below 5.2.0 are implement based on Request/Response mode, and now we are based on JetStream implementation. +see https://github.com/dotnetcore/CAP/issues/983 for more information.

+
+

Configuration

+

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 Options

+

NATS configuration parameters provided directly by the CAP:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
OptionsNATS client configurationOptionsOptions
ServersServer url/urls used to connect to the NATs server.stringNULL
ConnectionPoolSizenumber of connections pooluint10
DeliverPolicyThe point in the stream to receive messages from (⚠️ Removed from version 8.1.0, use ConsumerOptions instead.)enumDeliverPolicy.New
StreamOptions🆕 Stream configurationActionNULL
ConsumerOptions🆕 Consumer configurationActionNULL
+

NATS ConfigurationOptions

+

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

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/transport/pulsar/index.html b/user-guide/en/transport/pulsar/index.html new file mode 100644 index 000000000..6eb8cab69 --- /dev/null +++ b/user-guide/en/transport/pulsar/index.html @@ -0,0 +1,2105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apache Pulsar - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Apache Pulsar

+

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.

+

Configuration

+

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 ...
+    });
+}
+
+

Pulsar Options

+

The Pulsar configuration parameters provided directly by the CAP:

+ + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
ServiceUrlBroker server addressstring
TlsOptionsTls configurationobject
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/transport/rabbitmq/index.html b/user-guide/en/transport/rabbitmq/index.html new file mode 100644 index 000000000..3489299d0 --- /dev/null +++ b/user-guide/en/transport/rabbitmq/index.html @@ -0,0 +1,2237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + RabbitMQ - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

RabbitMQ

+

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.

+
+

Configuration

+

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 ...
+    });
+}
+
+

RabbitMQ Options

+

The RabbitMQ configuration parameters provided directly by CAP:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
HostNameBroker host addressstringlocalhost
UserNameBroker user namestringguest
PasswordBroker passwordstringguest
VirtualHostBroker virtual hoststring/
PortPortint-1
ExchangeNameDefault exchange namestringcap.default.topic
QueueArgumentsExtra queue x-argumentsQueueArgumentsOptionsN/A
ConnectionFactoryOptionsRabbitMQClient native connection optionsConnectionFactoryN/A
CustomHeadersBuilderCustom subscribe headersSee the blowN/A
PublishConfirmsEnable publish confirmsboolfalse
BasicQosOptionsSpecify Qos of message prefetchBasicQosN/A
+

ConnectionFactory Option

+

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
+        };
+    });
+});
+
+

CustomHeadersBuilder Option

+

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)
+    ];
+});
+
+

How to connect cluster

+

using comma split connection string, like this:

+
x=> x.UseRabbitMQ("localhost:5672,localhost:5673,localhost:5674")
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/en/transport/redis-streams/index.html b/user-guide/en/transport/redis-streams/index.html new file mode 100644 index 000000000..3e57a09ce --- /dev/null +++ b/user-guide/en/transport/redis-streams/index.html @@ -0,0 +1,2151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Redis Streams - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Redis Streams

+

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.

+

Configuration

+

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 Streams Options

+

Redis configuration parameters provided directly by the CAP:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
Configurationredis connection configuration (StackExchange.Redis)ConfigurationOptionsConfigurationOptions
StreamEntriesCountnumber of entries returned from a stream while readinguint10
ConnectionPoolSizenumber of connections pooluint10
+

Redis Configuration Options

+

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

+

Streams Cleanup Notes

+

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.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/cap/configuration/index.html b/user-guide/zh/cap/configuration/index.html new file mode 100644 index 000000000..74cf62242 --- /dev/null +++ b/user-guide/zh/cap/configuration/index.html @@ -0,0 +1,2468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 配置 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

配置

+

默认情况下,你在向DI容器中注册CAP服务的时候指定此配置。

+
services.AddCap(config=> {
+    // config.XXX 
+});
+
+

其中 services 代表的是 IServiceCollection 接口对象,它位于 Microsoft.Extensions.DependencyInjection 下面。

+

什么是最低配置?

+

最简单的回答就是,至少你要配置一个传输器和一个存储,如果你想快速开始你可以使用下面的配置:

+
services.AddCap(config => 
+{
+     config.UseInMemoryMessageQueue(); //需要引用 Savorboard.CAP.InMemoryMessageQueue 包
+     config.UseInMemoryStorage();
+});
+
+

有关具体的传输器配置和存储配置,你可以查看 Transports 章节和 Persistent 章节中具体组件提供的配置项。

+

CAP 中的自定义配置

+

AddCapCapOptions 对象是用来存储配置相关信息,默认情况下它们都具有一些默认值,有些时候你可能需要自定义。

+

DefaultGroupName

+

默认值: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.

+
+

GroupNamePrefix

+

默认值:Null

+

为订阅 Group 统一添加前缀。 https://github.com/dotnetcore/CAP/pull/780

+

TopicNamePrefix

+

默认值: Null

+

为 Topic 统一添加前缀。 https://github.com/dotnetcore/CAP/pull/780

+

Version

+

默认值:v1

+

用于给消息指定版本来隔离不同版本服务的消息,常用于A/B测试或者多服务版本的场景。以下是其应用场景:

+
+

业务快速迭代,需要向前兼容

+

由于业务的快速迭代,在各个服务集成的过程中,消息的数据结构并不是固定不变的,有些时候我们为了适应新引入的需求,会添加或者修改一些数据结构。如果你是一套全新的系统这没有什么问题,但是如果你的系统已经部署到生产环境了并且正在服务客户,这就会导致新的功能在上线的时候和旧的数据结构发生不兼容,那么这些改变可能会导致出现严重的问题,要想解决这个问题,只能把消息队列和持久化的消息全部清空,然后才能启动应用程序,这对于生产环境来说显然是致命的。

+
+
+

多个版本的服务端

+

有些时候,App的服务端需要提供多套接口,来支持不同版本的App,这些不同版本的App相同的接口和服务端交互的数据结构可能是不一样的,所以通常情况下服务端提供不用的路由地址来适配不同版本的App调用。

+
+
+

不同实例,使用相同的持久化表/集合

+

希望多个不同实例的程序可以公用相同的数据库,在 2.4 之前的版本,我们可以通过指定不同的表名来隔离不同实例的数据库表,即在CAP配置的时候通过配置不同的表名前缀来实现。

+
+
+

查看博客来了解更多关于 Version 的信息: https://www.cnblogs.com/savorboard/p/cap-2-4.html

+
+

FailedRetryInterval

+

默认值:60 秒

+

在消息发送的时候,如果发送失败,CAP将会对消息进行重试,此配置项用来配置每次重试的间隔时间。

+

在消息消费的过程中,如果消费失败,CAP将会对消息进行重试消费,此配置项用来配置每次重试的间隔时间。

+
+

重试 & 间隔

+

在默认情况下,重试将在发送和消费消息失败的 FallbackWindowLookbackSeconds(4分钟后) 开始,这是为了避免设置消息状态延迟导致可能出现的问题。
+发送和消费消息的过程中失败会立即重试 3 次,在 3 次以后将进入重试轮询,此时 FailedRetryInterval 配置才会生效。

+
+
+

多实例并发重试

+

我们在7.1.0版本中引入了基于数据库的分布式锁以应对在多个实例下对数据库重试的并发数据获取问题,你需要显式配置 UseStorageLock 为 true。

+
+

UseStorageLock

+
+

默认值: false

+
+

如果设置为true,我们将使用基于数据库的分布式锁以应对重试进程在多个实例下对数据库数据的并发获取问题。这将会在数据库生成 cap.lock 表。

+

ConsumerThreadCount

+
+

默认值:1

+
+

消费者线程并行处理消息的线程数,当这个值大于1时,将不能保证消息执行的顺序。

+

CollectorCleaningInterval

+
+

默认值:300 秒

+
+

收集器删除已经过期消息的时间间隔。

+

FailedRetryCount

+
+

默认值:50

+
+

重试的最大次数。当达到此设置值时,将不会再继续重试,通过改变此参数来设置重试的最大次数。

+

FallbackWindowLookbackSeconds

+
+

默认值:240 秒

+
+

配置重试处理器拾取 ScheduledFailed 状态消息的回退时间窗。

+

FailedThresholdCallback

+
+

默认值:NULL

+
+

类型:Action<FailedInfo>

+

重试阈值的失败回调。当重试达到 FailedRetryCount 设置的值的时候,将调用此 Action 回调,你可以通过指定此回调来接收失败达到最大的通知,以做出人工介入。例如发送邮件或者短信。

+

SucceedMessageExpiredAfter

+
+

默认值:24*3600 秒(1天后)

+
+

成功消息的过期时间(秒)。 当消息发送或者消费成功时候,在时间达到 SucceedMessageExpiredAfter 秒时候将会从 Persistent 中删除,你可以通过指定此值来设置过期的时间。

+

FailedMessageExpiredAfter

+
+

默认值:15*24*3600 秒(15天后)

+
+

失败消息的过期时间(秒)。 当消息发送或者消费失败时候,在时间达到 FailedMessageExpiredAfter 秒时候将会从 Persistent 中删除,你可以通过指定此值来设置过期的时间。

+

UseDispatchingPerGroup

+
+

默认值: false

+
+

默认情况下,CAP会将所有消费者组的消息都先放置到内存同一个Channel中,然后线性处理。 +如果设置为 true,则每个消费者组都会根据 ConsumerThreadCount 设置的值创建单独的线程进行处理。

+

在同时配合使用 EnableConsumerPrefetch 时,请参考 issue #1399 以清晰其预期行为。

+

[已过时] EnableConsumerPrefetch

+
+

默认值: false, 在 7.0 版本之前默认行为 true

+
+

该配置项已被重命名为 EnableSubscriberParallelExecute,请使用新选项。

+

EnableSubscriberParallelExecute

+
+

默认值: false

+
+

如果设置为 true,CAP将提前从Broker拉取一批消息置于内存缓冲区,然后执行订阅方法;当订阅方法执行完成后,拉取下一批消息至于缓冲区然后执行。

+
+

注意事项

+

设置为 true 可能会产生一些问题,当订阅方法执行过慢耗时太久时,会导致重试线程拾取到还未执行的的消息。重试线程默认拾取4分钟前(FallbackWindowLookbackSeconds 配置项)的消息,也就是说如果消费端积压了超过4分钟(FallbackWindowLookbackSeconds 配置项)的消息就会被重新拾取到再次执行

+
+

SubscriberParallelExecuteThreadCount

+
+

Default: Environment.ProcessorCount

+
+

当启用 EnableSubscriberParallelExecute 时, 可通过此参数执行并行处理的线程数,默认值为处理器个数。

+

SubscriberParallelExecuteBufferFactor

+
+

Default: 1

+
+

当启用 EnableSubscriberParallelExecute 时, 通过此参数设置缓冲区和线程数的因子系数,也就是缓冲区大小等于 SubscriberParallelExecuteThreadCountSubscriberParallelExecuteBufferFactor.

+

EnablePublishParallelSend

+
+

默认值: false

+
+

默认情况下,发送的消息都先放置到内存同一个Channel中,然后线性处理。 +如果设置为 true,则发送消息的任务将由.NET线程池并行处理,这会大大提高发送的速度。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/cap/filter/index.html b/user-guide/zh/cap/filter/index.html new file mode 100644 index 000000000..65ead7c6c --- /dev/null +++ b/user-guide/zh/cap/filter/index.html @@ -0,0 +1,2114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 过滤器 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

过滤器

+

从 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 关键字。

+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/cap/idempotence/index.html b/user-guide/zh/cap/idempotence/index.html new file mode 100644 index 000000000..aefdf76c9 --- /dev/null +++ b/user-guide/zh/cap/idempotence/index.html @@ -0,0 +1,2245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 幂等性 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

幂等性

+

幂等性(你可以在Wikipedia读到关于幂等性的定义),当我们谈论幂等时,一般是指可以重复处理传递的消息,而不会产生意外的结果。

+

交付保证

+

在说幂等性之前,我们先来说下关于消费端的消息交付。

+

由于CAP不是使用的 MS DTC 或其他类型的2PC分布式事务机制,所以存在至少消息严格交付一次的问题,具体的说在基于消息的系统中,存在以下三种可能:

+
    +
  • Exactly Once(*) (仅有一次)
  • +
  • At Most Once (最多一次)
  • +
  • At Least Once (最少一次)
  • +
+

带 * 号表示在实际场景中,很难达到。

+

At Most Once

+

最多一次交付保证,涵盖了保证一次或根本不接收所有消息的情况。

+

这种类型的传递保证可能来自你的消息系统,你的代码按以下顺序执行其操作:

+
1. 从队列移除消息
+2. 开始一个工作事务
+3. 处理消息 ( 你的代码 )
+4. 是否成功 ?
+    Yes:
+        1. 提交工作事务
+    No: 
+        1. 回滚工作事务
+        2. 将消息发回到队列。
+
+

正常情况下,他们工作的很好,工作事务将被提交。

+

然而,有些时候并不能总是成功,比如在 1 之后出现异常,或者是你在将消息放回到队列中出现网络问题由或者宕机重启等情况。

+

使用这个协议,你将冒着丢失消息的风险,如果可以接受,那就没有关系。

+

At Least Once

+

这个交付保证包含你收到至少一次的消息,当出现故障时,可能会收到多次消息。

+

它需要稍微改变我们执行步骤的顺序,它要求消息队列系统支持事务或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 中的幂等性

+

在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和对应的处理状态。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/cap/messaging/index.html b/user-guide/zh/cap/messaging/index.html new file mode 100644 index 000000000..6427cb937 --- /dev/null +++ b/user-guide/zh/cap/messaging/index.html @@ -0,0 +1,2234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 消息 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

消息

+

使用 ICapPublisher 接口发送出去的数据称之为 Message (消息)。

+

发送 & 处理消息

+

你可以阅读 quick-start 来学习如何发送和处理消息。

+
+

消费者中使用 HTTPClient 引发的 TimeoutException

+

默认情况下,如果消费者抛出 OperationCanceledException(包括 TaskCanceledException),我们会认为这是用户的正常行为而对异常进行忽略。如果你在消费者方法中使用 HTTPClient 并且进行了配置了Timeout配置,由于HTTP Client的设计问题,你可能需要单独对异常进行处理并重新引发非OperationCanceledException,参考 #1368

+
+

补偿事务

+

Compensating transaction

+

某些情况下,消费者需要返回值以告诉发布者执行结果,以便于发布者实施一些动作,通常情况下这属于补偿范围。

+

你可以在消费者执行的代码中通过重新发布一个新消息来通知上游,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-idstring消息Id, 由雪花算法生成,也可以是 guid
cap-msg-namestring消息名称,即 Topic 名字
cap-msg-typestring消息的类型, 即 typeof(T).FullName (非必须)
cap-senttimestringg发送的时间 (非必须)
+

以 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 配置项来自定义间隔时间。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/cap/serialization/index.html b/user-guide/zh/cap/serialization/index.html new file mode 100644 index 000000000..74739e029 --- /dev/null +++ b/user-guide/zh/cap/serialization/index.html @@ -0,0 +1,2055 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 序列化 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

序列化

+

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 
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/cap/transactions/index.html b/user-guide/zh/cap/transactions/index.html new file mode 100644 index 000000000..a517a6963 --- /dev/null +++ b/user-guide/zh/cap/transactions/index.html @@ -0,0 +1,2072 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 事务 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

事务

+

分布式事务?

+

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

+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/getting-started/contributing/index.html b/user-guide/zh/getting-started/contributing/index.html new file mode 100644 index 000000000..792445cf4 --- /dev/null +++ b/user-guide/zh/getting-started/contributing/index.html @@ -0,0 +1,2065 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 贡献 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

贡献

+

贡献最简单的方式之一就是参与讨论和issue讨论。

+

如果您有任何疑问或问题,请在CAP仓库中报告:

+

+

+

提交更改

+

您还可以通过提交代码更改PR来做出贡献。

+
+

Pull requests 可让您告诉其他人已推送到GitHub上存储库的更改。 打开 Pull requests 后,您可以与协作者讨论和审查做出的更改,并在更改合并到存储库之前添加后续提交。

+
+

其他资源

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/getting-started/introduction/index.html b/user-guide/zh/getting-started/introduction/index.html new file mode 100644 index 000000000..630a98ee8 --- /dev/null +++ b/user-guide/zh/getting-started/introduction/index.html @@ -0,0 +1,2070 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 介绍 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

介绍

+

CAP 是一个EventBus,同时也是一个在微服务或者SOA系统中解决分布式事务问题的一个框架。它有助于创建可扩展,可靠并且易于更改的微服务系统。

+

在微软的 eShop 微服务示例项目中,推荐使用 CAP 作为生产环境可用的 EventBus。

+
+

什么是 EventBus?

+

事件总线是一种机制,它允许不同的组件彼此通信而不彼此了解。 组件可以将事件发送到Eventbus,而无需知道是谁来接听或有多少其他人来接听。 组件也可以侦听Eventbus上的事件,而无需知道谁发送了事件。 这样,组件可以相互通信而无需相互依赖。 同样,很容易替换一个组件。 只要新组件了解正在发送和接收的事件,其他组件就永远不会知道.

+
+

相对于其他的 Service Bus 或者 Event Bus, CAP 拥有自己的特色,它不要求使用者发送消息或者处理消息的时候实现或者继承任何接口,拥有非常高的灵活性。我们一直坚信约定大于配置,所以CAP使用起来非常简单,对于新手非常友好,并且拥有轻量级。

+

CAP 采用模块化设计,具有高度的可扩展性。你有许多选项可以选择,包括消息队列,存储,序列化方式等,系统的许多元素内容可以替换为自定义实现。

+

相关视频

+

Video: bilibili 教程

+

Video: Youtube 教程

+

Video: 腾讯视频教程

+

相关文章

+

Article: CAP 介绍及使用

+

Article: CAP 7.0 版本中的新特性

+

Article: CAP 6.0 版本中的新特性

+

Article: CAP 5.0 版本中的新特性

+

Article: CAP 3.0 版本中的新特性

+

Article: CAP 2.6 版本中的新特性

+

Article: CAP 2.5 版本中的新特性

+

Article: CAP 2.4 版本中的新特性

+

Article: CAP 2.3 版本中的新特性用

+

Article: .NET Core Community 首个千星项目诞生:CAP

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/getting-started/quick-start/index.html b/user-guide/zh/getting-started/quick-start/index.html new file mode 100644 index 000000000..5c0bb3635 --- /dev/null +++ b/user-guide/zh/getting-started/quick-start/index.html @@ -0,0 +1,2229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 快速开始 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

快速开始

+

了解如何使用 CAP 构建微服务事件总线架构,它比直接集成消息队列提供了哪些优势,它提供了哪些开箱即用的功能。

+

安装

+
PM> Install-Package DotNetCore.CAP
+
+

在 Asp.Net Core 中集成

+

以便于快速启动,我们使用基于内存的事件存储和消息队列。

+
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 内部会将消息进行存储,以保证消息的可靠性,并配合重试等策略以达到各个服务之间的数据最终一致性。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/monitoring/consul/index.html b/user-guide/zh/monitoring/consul/index.html new file mode 100644 index 000000000..b8164a81c --- /dev/null +++ b/user-guide/zh/monitoring/consul/index.html @@ -0,0 +1,2062 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Consul - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Consul

+

Consul 是一个分布式服务网格,用于跨任何运行时平台和公共或私有云连接,保护和配置服务。

+

Dashboard 中的 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;"
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/monitoring/dashboard/index.html b/user-guide/zh/monitoring/dashboard/index.html new file mode 100644 index 000000000..ef8005fc9 --- /dev/null +++ b/user-guide/zh/monitoring/dashboard/index.html @@ -0,0 +1,2330 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dashboard - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Dashboard

+

CAP 原生提供了 Dashboard 供查看消息,利用 Dashboard 提供的功能可以很方便的查看和管理消息。

+
+

使用限制

+

Dashboard 只支持在 ASP.NET Core 中使用,不支持控制台应用(Console App)

+
+

启用 Dashboard

+

首先,你需要安装Dashboard的 NuGet 包。

+
PM> Install-Package DotNetCore.CAP.Dashboard
+
+

然后,在配置中添加如下代码:

+
services.AddCap(x =>
+{
+    //...
+
+    // Register Dashboard
+    x.UseDashboard();
+});
+
+

默认情况下,你可以访问 http://localhost:xxx/cap 这个地址打开Dashboard。

+

Dashboard 配置项

+
    +
  • PathBase
  • +
+

默认值:N/A

+

当位于代理后时,通过配置此参数可以指定代理请求前缀。

+
    +
  • PathMatch
  • +
+

默认值:'/cap'

+

你可以通过修改此配置项来更改Dashboard的访问路径。

+
    +
  • StatsPollingInterval
  • +
+

默认值:2000 毫秒

+

此配置项用来配置Dashboard 前端 获取状态接口(/stats)的轮询时间

+
    +
  • AllowAnonymousExplicit
  • +
+
+

Default: true

+
+

显式允许对 CAP 仪表板 API 进行匿名访问,当启用ASP.NET Core 全局授权筛选器请启用 AllowAnonymous。

+
    +
  • AuthorizationPolicy
  • +
+
+

Default: null.

+
+

Dashboard 的授权策略。 需设置 AllowAnonymousExplicit为 false。

+

自定义认证

+

从版本 8.0.0 开始,CAP 仪表板利用 ASP.NET Core 身份验证机制,允许通过自定义授权策略和 ASP.NET Core 身份验证和授权中间件进行扩展,以授权仪表板访问。 有关 ASP.NET Core 身份验证内部结构的更多详细信息,请查看官方文档.

+

您可以在示例项目 Sample.Dashboard.Auth中查看示例代码。

+

Example: 匿名访问

+
services.AddCap(cap =>
+    {
+        cap.UseDashboard(d =>
+        {
+            d.AllowAnonymousExplicit = true;
+        });
+        cap.UseInMemoryStorage();
+        cap.UseInMemoryMessageQueue();
+    });
+
+

Example: Open Id

+
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 中查看以下示例。

+

例子:Anonymous Access 匿名访问

+
services.AddCap(cap =>
+    {
+        cap.UseDashboard(d =>
+        {
+            d.AllowAnonymousExplicit = true;
+        });
+        cap.UseInMemoryStorage();
+        cap.UseInMemoryMessageQueue();
+    });
+
+

例子:使用 Open Id

+
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();
+    });
+
+

例子:自定义 Authentication Scheme

+
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();
+    });
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/monitoring/diagnostics/index.html b/user-guide/zh/monitoring/diagnostics/index.html new file mode 100644 index 000000000..407616815 --- /dev/null +++ b/user-guide/zh/monitoring/diagnostics/index.html @@ -0,0 +1,2175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Diagnostics - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

诊断(Diagnostics)

+

Diagnostics 提供一组功能使我们能够很方便的可以记录在应用程序运行期间发生的关键性操作以及他们的执行时间等,使管理员可以查找特别是生产环境中出现问题所在的根本原因。

+

跟踪(Tracing)

+

CAP 对 .NET DiagnosticSource 提供了支持,监听器名称为 CapDiagnosticListener

+

你可以在 DotNetCore.CAP.Diagnostics.CapDiagnosticListenerNames 类下面找到CAP已经定义的事件名称。

+

Diagnostics 提供对外提供的事件信息有:

+
    +
  • 消息持久化之前
  • +
  • 消息持久化之后
  • +
  • 消息持久化异常
  • +
  • 消息向MQ发送之前
  • +
  • 消息向MQ发送之后
  • +
  • 消息向MQ发送异常
  • +
  • 消息从MQ消费保存之前
  • +
  • 消息从MQ消费保存之后
  • +
  • 订阅者方法执行之前
  • +
  • 订阅者方法执行之后
  • +
  • 订阅者方法执行异常
  • +
+

在 Skywalking APM 中追踪 CAP 事件

+

Skywalking 的 C# 客户端提供了对 CAP Diagnostics 的支持,你可以利用 SkyAPM-dotnet 来实现在 Skywalking 中追踪事件。

+

尝试阅读Readme文档来在你的项目中集成它。

+

+

+

其他 APM 的支持

+

目前还没有实现对除了 Skywalking 的其他APM的支持,如果你想在其他 APM 中实现对 CAP 诊断事件的支持,你可以参考这里的代码来实现它:

+

https://github.com/SkyAPM/SkyAPM-dotnet/tree/master/src/SkyApm.Diagnostics.CAP

+

度量(Metrics)

+

度量是指对于一个物体或是事件的某个性质给予一个数字,使其可以和其他物体或是事件的相同性质比较。度量可以是对一物理量(如长度、尺寸或容量等)的估计或测定,也可以是其他较抽象的特质。

+

CAP 7.0 对 EventSource 提供了支持,计数器名称为 DotNetCore.CAP.EventCounter

+

CAP 提供了以下几个度量指标:

+
    +
  • 每秒发布速度
  • +
  • 每秒消费速度
  • +
  • 每秒调用订阅者速度
  • +
  • 每秒执行订阅者平均耗时
  • +
+

使用 dotnet-counters 查看度量

+

dotnet-counters 是一个性能监视工具,用于临时运行状况监视和初级性能调查。 它可以观察通过 EventCounter API 或 Meter API 发布的性能计数器值。

+

使用以下命令来监视CAP中的度量指标:

+
dotnet-counters ps
+dotnet-counters monitor --process-id=25496 --counters=DotNetCore.CAP.EventCounter
+
+

其中 process-id 为 CAP 所属的进程Id。

+

img

+

在 Dashboard 中查看度量

+

你可以配置 x.UseDashboard() 来开启仪表盘以图表的形式查看 Metrics 指标。 如下图:

+

img

+

在 Realtime Metric Graph 中,时间轴会随着时间实时滚动从而可以看到发布和消费消息每秒的速率,同时我们可以看到消费者执行耗时以“打点”的方式在 Y1 轴上(Y0轴为速率,Y1轴为执行耗时)。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/monitoring/kubernetes/index.html b/user-guide/zh/monitoring/kubernetes/index.html new file mode 100644 index 000000000..247cd4e10 --- /dev/null +++ b/user-guide/zh/monitoring/kubernetes/index.html @@ -0,0 +1,2143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Kubernetes - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Kubernetes

+

Kubernetes,也称为 K8s,是一个开源系统,用于自动部署、扩展和管理容器化应用程序。

+

Dashboard 中的 Kubernetes

+

我们的 Dashboard 从 7.2.0 版本开始支持 Kubernetes 作为服务发现。你可以切换到Node节点页面,然后选择命名空间,CAP会列出该命名空间下的所有Services,点击 切换 按钮后Dashboard将检测该节点的CAP服务是否可用,如果可用则会代理到切换的节点进行数据查看。

+

以下是一个配置示例

+
services.AddCap(x =>
+{
+    // ...
+    x.UseDashboard();
+    x.UseK8sDiscovery();
+});
+
+

组件将会自动检测是否处于集群内部,如果处于集群内部在需要赋予Pod Kubernetes Api 的权限。参考下一章节。

+

分配 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

+

你可以独立使用 Dashboard 而不需要配置CAP,此时相当于 Dashboard 可作为单独的 Pod 部署到 Kubernetes 集群中仅用作查看数据,待查看的服务不再需要配置 cap.UseK8sDiscovery() 配置项。

+
services.AddCapDashboardStandalone();
+
+

同样,你需要为此Pod配置 ServiceAccount 的访问权限。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/monitoring/opentelemetry/index.html b/user-guide/zh/monitoring/opentelemetry/index.html new file mode 100644 index 000000000..3d0e2e7ea --- /dev/null +++ b/user-guide/zh/monitoring/opentelemetry/index.html @@ -0,0 +1,2077 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + OpenTelemetry - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

OpenTelemetry

+

https://opentelemetry.io/

+

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 中的一个示意图:

+

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/samples/castle.dynamicproxy/index.html b/user-guide/zh/samples/castle.dynamicproxy/index.html new file mode 100644 index 000000000..ad059f581 --- /dev/null +++ b/user-guide/zh/samples/castle.dynamicproxy/index.html @@ -0,0 +1,2177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Castle DynamicProxy - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

和 Castle DynamicProxy 集成

+

Castle DynamicProxy 是一个用于在运行时动态生成轻量级.NET代理的库。代理对象允许在不修改类代码的情况下截取对对象成员的调用。可以代理类和接口,但是只能拦截虚拟成员。

+

Castle.DynamicProxy 可以帮助你方便的创建代理对象,代理对象可以帮助构建灵活的应用程序体系结构,因为它允许将功能透明地添加到代码中,而无需对其进行修改。例如,可以代理一个类来添加日志记录或安全检查,而无需使代码知道已添加此功能。

+

下面可以看到如何在 CAP 中集成使用 Castle.DynamicProxy。

+

1、安装 NuGet 包

+

在 集成了 CAP 的项目中安装包,有关如何集成 CAP 的文档请看这里

+

注意,Castle.DynamicProxy 这个包已经被废弃,请使用最新的 Castle.Core 包。

+
<PackageReference Include="Castle.Core" Version="4.4.1" />
+
+

2、创建一个 Castle 切面拦截器

+

可以在这里 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,你可以在其中处理你的业务逻辑,比如添加日志或其他的一些行为。

+

3、创建 IServiceCollection 的扩展类

+

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 来执行,所以无需担心资源释放问题。

+

4、创建 CAP 订阅服务

+

创建一个 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}");
+    }
+}
+
+

5、在 Startup 中集成

+
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 反馈。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/samples/eshoponcontainers/index.html b/user-guide/zh/samples/eshoponcontainers/index.html new file mode 100644 index 000000000..72381d1a2 --- /dev/null +++ b/user-guide/zh/samples/eshoponcontainers/index.html @@ -0,0 +1,2042 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + eShopOnContainers - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

eShopOnContainers

+

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 with CAP

+

你可以在下面的地址看到如何在 eShopOnContainers 中使用 CAP。

+

https://github.com/yang-xiaodong/eShopOnContainers

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/samples/faq/index.html b/user-guide/zh/samples/faq/index.html new file mode 100644 index 000000000..e3a6e5129 --- /dev/null +++ b/user-guide/zh/samples/faq/index.html @@ -0,0 +1,2024 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + FAQ - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

FAQ

+
+

有没有学习和讨论 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 的情况与此类似。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/samples/github/index.html b/user-guide/zh/samples/github/index.html new file mode 100644 index 000000000..d287649c6 --- /dev/null +++ b/user-guide/zh/samples/github/index.html @@ -0,0 +1,1991 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Github - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/storage/general/index.html b/user-guide/zh/storage/general/index.html new file mode 100644 index 000000000..0e7db80fc --- /dev/null +++ b/user-guide/zh/storage/general/index.html @@ -0,0 +1,2362 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 简介 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

基本

+

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 表结构:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPE
IdMessage Idint
VersionMessage Versionstring
NameTopic Namestring
ContentJson Contentstring
AddedAdded TimeDateTime
ExpiresAtExpire timeDateTime
RetriesRetry timesint
StatusNameStatus Namestring
+

Received 表结构:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPE
IdMessage Idint
VersionMessage Versionstring
NameTopic Namestring
GroupGroup Namestring
ContentJson Contentstring
AddedAdded TimeDateTime
ExpiresAtExpire timeDateTime
RetriesRetry timesint
StatusNameStatus Namestring
+

Lock 表结构(可选):

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPE
KeyLock Idstring
InstanceAcquired instance of lockstring
LastLockTimeLast acquired lock timeDateTime
+

包装器对象

+

CAP 在进行消息发送到时候,会对原始消息对象进行一个二次包装存储到 Content 字段中,以下为包装 Content 的 Message 对象数据结构:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPE
IdCAP生成的消息编号string
Timestamp消息创建时间string
Content内容string
CallbackName回调的订阅者名称string
+

其中 Id 字段,CAP 采用的 MongoDB 中的 ObjectId 分布式Id生成算法生成。

+

社区支持的持久化

+

感谢社区对CAP的支持,以下是社区支持的持久化的实现

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/storage/in-memory-storage/index.html b/user-guide/zh/storage/in-memory-storage/index.html new file mode 100644 index 000000000..3eccfb2b6 --- /dev/null +++ b/user-guide/zh/storage/in-memory-storage/index.html @@ -0,0 +1,2067 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + In-Memory - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

In-Memory Storage

+

内存消息的持久化存储常用于开发和测试环境,如果使用基于内存的存储则你会失去本地事务消息可靠性保证。

+

配置

+

如果要使用内存存储,你需要从 NuGet 安装以下扩展包:

+
Install-Package DotNetCore.CAP.InMemoryStorage
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于内存的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UseInMemoryStorage();
+        // x.UseXXX ...
+    });
+}
+
+

内存中的发送成功消息,CAP 将会每 5分钟 进行一次清理。

+

Publish with transaction

+

In-Memory 存储 不支持 事务方式发送消息。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/storage/mongodb/index.html b/user-guide/zh/storage/mongodb/index.html new file mode 100644 index 000000000..a216fdf1b --- /dev/null +++ b/user-guide/zh/storage/mongodb/index.html @@ -0,0 +1,2148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + MongoDB - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

MongoDB

+

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.csConfigureServices 方法中添加基于内存的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UseMongoDB(opt=>{
+            //MongoDBOptions
+        });
+        // x.UseXXX ...
+    });
+}
+
+

配置项

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
DatabaseName数据库名称stringcap
DatabaseConnection数据库连接字符串stringmongodb://localhost:27017
ReceivedCollection接收消息集合名称stringcap.received
PublishedCollection发送消息集合名称stringcap.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();
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/storage/mysql/index.html b/user-guide/zh/storage/mysql/index.html new file mode 100644 index 000000000..981c7e81a --- /dev/null +++ b/user-guide/zh/storage/mysql/index.html @@ -0,0 +1,2221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + MySQL - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

MySQL

+

MySQL 是一个开源的关系型数据库,你可以使用 MySQL 来作为 CAP 消息的持久化。

+

配置

+

要使用 MySQL 存储,你需要从 NuGet 安装以下扩展包:

+
Install-Package DotNetCore.CAP.MySql
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于内存的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UseMySql(opt=>{
+            //MySqlOptions
+        });
+        // x.UseXXX ...
+    });
+}
+
+

配置项

+ + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
TableNamePrefixCap表名前缀stringcap
ConnectionString数据库连接字符串stringnull
+

自定义表名称

+

你可以通过重写 IStorageInitializer 接口获取表名称的方法来做到这一点

+

示例代码:

+

public class MyTableInitializer : MySqlStorageInitializer
+{
+    public override string GetPublishedTableName()
+    {
+        //你的 发送消息表 名称
+    }
+
+    public override string GetReceivedTableName()
+    {
+        //你的 接收消息表 名称
+    }
+}
+
+然后将你的实现注册到容器中

+
services.AddSingleton<IStorageInitializer, MyTableInitializer>();
+
+

使用事务发布消息

+

ADO.NET

+
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();
+    }
+}
+
+

EntityFramework

+
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();
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/storage/postgresql/index.html b/user-guide/zh/storage/postgresql/index.html new file mode 100644 index 000000000..66e3c5a31 --- /dev/null +++ b/user-guide/zh/storage/postgresql/index.html @@ -0,0 +1,2227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + PostgreSql - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Postgre SQL

+

PostgreSQL 是一个开源的关系型数据库,它已经变得越来越流行,你可以使用 Postgre SQL 来作为 CAP 消息的持久化。

+

配置

+

要使用 PostgreSQL 存储,你需要从 NuGet 安装以下扩展包:

+
Install-Package DotNetCore.CAP.PostgreSql
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于内存的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UsePostgreSql(opt=>{
+            //PostgreSqlOptions
+        }); 
+        // x.UseXXX ...
+    });
+}
+
+

配置项

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
Schema数据库架构stringcap
ConnectionString数据库连接字符串string
DataSourceData sourceNpgsqlDataSource
+

自定义表名称

+

你可以通过重写 IStorageInitializer 接口获取表名称的方法来做到这一点

+

示例代码:

+

public class MyTableInitializer : PostgreSqlStorageInitializer
+{
+    public override string GetPublishedTableName()
+    {
+        //你的 发送消息表 名称
+    }
+
+    public override string GetReceivedTableName()
+    {
+        //你的 接收消息表 名称
+    }
+}
+
+然后将你的实现注册到容器中

+
services.AddSingleton<IStorageInitializer, MyTableInitializer>();
+
+

使用事务发布消息

+

ADO.NET

+
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();
+    }
+}
+
+

EntityFramework

+
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();
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/storage/sqlserver/index.html b/user-guide/zh/storage/sqlserver/index.html new file mode 100644 index 000000000..3d561b5b9 --- /dev/null +++ b/user-guide/zh/storage/sqlserver/index.html @@ -0,0 +1,2225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + SQL Server - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

SQL Server

+

SQL Server 是由微软开发的一个关系型数据库,你可以使用 SQL Server 来作为 CAP 消息的持久化。

+
+

注意

+

我们目前使用 Microsoft.Data.SqlClient 作为数据库驱动程序,它是SQL Server 驱动的未来,并且已经放弃了 System.Data.SqlClient,我们建议你切换过去。

+
+

配置

+

要使用 SQL Server 存储,你需要从 NuGet 安装以下扩展包:

+
Install-Package DotNetCore.CAP.SqlServer
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于内存的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UseSqlServer(opt=>{
+            //SqlServerOptions
+        }); 
+        // x.UseXXX ...
+    });
+}
+
+

配置项

+ + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
Schema数据库架构stringCap
ConnectionString数据库连接字符串string
+

自定义表名称

+

你可以通过重写 IStorageInitializer 接口获取表名称的方法来做到这一点

+

示例代码:

+

public class MyTableInitializer : SqlServerStorageInitializer
+{
+    public override string GetPublishedTableName()
+    {
+        //你的 发送消息表 名称
+    }
+
+    public override string GetReceivedTableName()
+    {
+        //你的 接收消息表 名称
+    }
+}
+
+然后将你的实现注册到容器中

+
services.AddSingleton<IStorageInitializer, MyTableInitializer>();
+
+

使用事务发布消息

+

ADO.NET

+
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();
+    }
+}
+
+

EntityFramework

+
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();
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/transport/aws-sqs/index.html b/user-guide/zh/transport/aws-sqs/index.html new file mode 100644 index 000000000..108e82887 --- /dev/null +++ b/user-guide/zh/transport/aws-sqs/index.html @@ -0,0 +1,2192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Amazon SQS - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Amazon SQS

+

AWS SQS 是一种完全托管的消息队列服务,可让您分离和扩展微服务、分布式系统和无服务器应用程序。

+

AWS SNS 是一种高度可用、持久、安全、完全托管的发布/订阅消息收发服务,可以轻松分离微服务、分布式系统和无服务器应用程序。

+

CAP 如何使用 AWS SNS & SQS

+

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 中你将看到

+

img

+

SQS

+

针对每个消费者组,CAP 将创建一个与之对应的 SQS 队列,队列的名称为配置项中 DefaultGroup 的名称,类型为 Standard Queue 。

+

该 SQS 队列将订阅 SNS 中的 Topic ,如下图:

+

img

+
+

注意事项

+

由于 AWS SNS 的限制,当你减少订阅方法时,我们不会主动删除 AWS SNS 或者 SQS 上的相关 Topic 或 Queue,你需要手动删除他们。

+
+

配置

+

要使用 AWS SQS 作为消息传输器,你需要从 NuGet 安装以下扩展包:

+
Install-Package DotNetCore.CAP.AmazonSQS
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于 RabbitMQ 的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UseAmazonSQS(opt=>
+        {
+            //AmazonSQSOptions
+        });
+        // x.UseXXX ...
+    });
+}
+
+

AmazonSQS Options

+

CAP 直接对外提供的 AmazonSQSOptions 配置参数如下:

+ + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
RegionAWS 所处的区域Amazon.RegionEndpoint
CredentialsAWS AK SK信息Amazon.Runtime.AWSCredentials
+

如果你的项目运行在 AWS EC2 中,则不需要设置 Credentials,直接对 EC2 应用 IAM 策略即可。

+

Credentials 需要具有新增和订阅 SNS Topic,SQS Queue 等权限。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/transport/azure-service-bus/index.html b/user-guide/zh/transport/azure-service-bus/index.html new file mode 100644 index 000000000..6110da217 --- /dev/null +++ b/user-guide/zh/transport/azure-service-bus/index.html @@ -0,0 +1,2201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Azure Service Bus - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Azure Service Bus

+

Azure 服务总线是一种多租户云消息服务,可用于在应用程序和服务之间发送信息。 异步操作可实现灵活的中转消息传送、结构化的先进先出 (FIFO) 消息传送以及发布/订阅功能。

+

CAP 支持使用 Azure Service Bus 作为消息传输器。

+

Configuration

+
+

必须条件

+

针对 Service Bus 的定价, CAP 要求使用 “标准” 或者 “高级” 以支持 Topic 功能。

+
+

要使用 Azure Service Bus 作为消息传输器,你需要从 NuGet 安装以下扩展包:

+
Install-Package DotNetCore.CAP.AzureServiceBus
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于内存的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UseAzureServiceBus(opt=>
+        {
+            //AzureServiceBusOptions
+        });
+        // x.UseXXX ...
+    });
+}
+
+

AzureServiceBus Options

+

CAP 直接对外提供的 Azure Service Bus 配置参数如下:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
ConnectionStringEndpoint 地址string
EnableSessionsEnable Service bus sessionsboolfalse
TopicPathTopic entity pathstringcap
ManagementTokenProviderToken providerITokenProvidernull
AutoCompleteMessages获取一个值,该值指示处理器是否应在消息处理程序完成处理后自动完成消息boolfalse
CustomHeadersBuilder为来自异构系统的传入消息添加自定义头Func<Message, List<KeyValuePair<string, string>>>?null
NamespaceServicebus 的命名空间,与 TokenCredential 属性一起使用时需要设置stringnull
SQLFilters根据名称和表达式自定义 SQL 过滤器List>null
+

Sessions

+

当使用 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.

+

Heterogeneous Systems

+

有时您可能想接收由外部系统发布的消息。 在这种情况下,您需要添加一组两个强制标头以实现 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)的标头,则不会添加自定义标头。

+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/transport/general/index.html b/user-guide/zh/transport/general/index.html new file mode 100644 index 000000000..5eab0da84 --- /dev/null +++ b/user-guide/zh/transport/general/index.html @@ -0,0 +1,2138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 简介 - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

运输器

+

通过运输将数据从一个地方移动到另一个地方-在采集程序和管道之间,管道与实体数据库之间,甚至在管道与外部系统之间。

+

支持的运输器

+

CAP 支持以下几种运输方式:

+ +

怎么选择运输器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
🏳‍🌈RabbitMQKafkaAzure Service BusIn-Memory
定位可靠消息传输实时数据处理内存型,测试
分布式
持久化
性能MediumHighMediumHigh
+
+

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

+
+

社区支持的运输器

+

感谢社区对CAP的支持,以下是社区支持的运输器实现

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/transport/in-memory-queue/index.html b/user-guide/zh/transport/in-memory-queue/index.html new file mode 100644 index 000000000..ddb40a798 --- /dev/null +++ b/user-guide/zh/transport/in-memory-queue/index.html @@ -0,0 +1,2050 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + In-Memory Queue - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

In-Memory Queue

+

In Memory Queue 为基于内存的消息队列,该扩展由 社区 进行提供。

+

配置

+

要使用 In Memory Queue 作为消息传输器,你需要从 NuGet 安装以下扩展包:

+
Install-Package Savorboard.CAP.InMemoryMessageQueue
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于内存的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UseInMemoryMessageQueue();
+        // x.UseXXX ...
+    });
+}
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/transport/kafka/index.html b/user-guide/zh/transport/kafka/index.html new file mode 100644 index 000000000..bf1bbe028 --- /dev/null +++ b/user-guide/zh/transport/kafka/index.html @@ -0,0 +1,2160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apache Kafka® - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Apache Kafka®

+

Apache Kafka® 是一个开源流处理软件平台,由 LinkedIn 开发并捐赠给 Apache Software Foundation,用 Scala 和 Java 编写。

+

CAP 支持使用 Apache Kafka® 作为消息传输器。

+

Configuration

+

要使用 Kafka 作为消息传输器,你需要从 NuGet 安装以下扩展包:

+
Install-Package DotNetCore.CAP.Kafka
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于 Kafka 的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UseKafka(opt=>{
+            //KafkaOptions
+        });
+        // x.UseXXX ...
+    });
+}
+
+

Kafka Options

+

CAP 直接对外提供的 Kafka 配置参数如下:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
ServersBroker 地址string
ConnectionPoolSize用户名int10
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"];
+}
+
+

Kafka MainConfig Options

+

如果你需要 更多 原生 Kakfa 相关的配置项,可以通过 MainConfig 配置项进行设定:

+
services.AddCap(capOptions => 
+{
+    capOptions.UseKafka(kafkaOption=>
+    {
+        // kafka options.
+        // kafkaOptions.MainConfig.Add("", "");
+    });
+});
+
+

MainConfig 为配置字典,你可以通过以下链接找到其支持的配置项列表。

+

https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/transport/nats/index.html b/user-guide/zh/transport/nats/index.html new file mode 100644 index 000000000..0cbe5b2e2 --- /dev/null +++ b/user-guide/zh/transport/nats/index.html @@ -0,0 +1,2156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + NATS - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

NATS

+

NATS是一个简单、安全、高性能的数字系统、服务和设备通信系统。NATS 是 CNCF 的一部分。

+
+

Warning

+

CAP 5.2.0 以下的版本基于 Request/Response 实现, 现在我们已经基于 JetStream 实现。 +查看 https://github.com/dotnetcore/CAP/issues/983 了解更多。

+
+

配置

+

要使用NATS 传输器,你需要安装下面的NuGet包:

+
PM> Install-Package DotNetCore.CAP.NATS
+
+

你可以通过在 Startup.cs 文件中配置 ConfigureServices 来添加配置:

+
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCap(capOptions =>
+    {
+        capOptions.UseNATS(natsOptions=>{
+            //NATS Options
+        });
+    });
+}
+
+

NATS 配置

+

CAP 直接提供的关于 NATS 的配置参数:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
OptionsNATS 客户端配置OptionsOptions
Servers服务器Urls地址stringNULL
ConnectionPoolSize连接池数量uint10
DeliverPolicy消费消息的策略点(⚠️在8.1.0版本移除,使用ConsumerOptions替代。)enumDeliverPolicy.New
StreamOptions🆕 Stream 配置项ActionNULL
ConsumerOptions🆕 Consumer 配置项ActionNULL
+

NATS ConfigurationOptions

+

如果你需要 更多 原生相关的配置项,可以通过 Options 配置项进行设定:

+
services.AddCap(capOptions => 
+{
+    capOptions.UseNATS(natsOptions=>
+    {
+        // NATS options.
+        natsOptions.Options.Url="";
+    });
+});
+
+

Options 是 NATS.Client 客户端提供的配置, 你可以在这个链接找到更多详细信息。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/transport/pulsar/index.html b/user-guide/zh/transport/pulsar/index.html new file mode 100644 index 000000000..8b6b11692 --- /dev/null +++ b/user-guide/zh/transport/pulsar/index.html @@ -0,0 +1,2105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apache Pulsar - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Apache Pulsar

+

Apache Pulsar 是一个用于服务器到服务器的消息系统,具有多租户、高性能等优势。 Pulsar 最初由 Yahoo 开发,目前由 Apache 软件基金会管理。

+

CAP 支持使用 Apache Pulsar 作为消息传输器。

+

Configuration

+

要使用 Pulsar 作为消息传输器,你需要从 NuGet 安装以下扩展包:

+
Install-Package DotNetCore.CAP.Pulsar
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于 Pulsar 的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UsePulsar(opt => {
+            //Pulsar Options
+        });
+        // x.UseXXX ...
+    });
+}
+
+

Pulsar Options

+

CAP 直接对外提供的 Pulsar 配置参数如下:

+ + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
ServiceUrlBroker 地址string
TlsOptionsTLS 配置项object
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/transport/rabbitmq/index.html b/user-guide/zh/transport/rabbitmq/index.html new file mode 100644 index 000000000..20fda2dc5 --- /dev/null +++ b/user-guide/zh/transport/rabbitmq/index.html @@ -0,0 +1,2237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + RabbitMQ - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

RabbitMQ

+

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ 服务器是用 Erlang 语言编写的,而聚类和故障转移是构建在开源的通讯平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。

+

CAP 支持使用 RabbitMQ 作为消息传输器。

+
+

注意事项

+

在使用RabbitMQ时,集成了CAP的消费者应用在启动过一次后会自动创建持久化的队列,后续消息会正常传递到队列中并消费。 +如果你从来没有启动过消费者,则队列不会被自动创建,此时如果先行发布消息,在此时间段的消息 RabbitMQ Exchange 收到后会直接丢弃。

+
+

配置

+

要使用 RabbitMQ 作为消息传输器,你需要从 NuGet 安装以下扩展包:

+
Install-Package DotNetCore.CAP.RabbitMQ
+
+

然后,你可以在 Startup.csConfigureServices 方法中添加基于 RabbitMQ 的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    // ...
+
+    services.AddCap(x =>
+    {
+        x.UseRabbitMQ(opt=>
+        {
+            //RabbitMQOptions
+        });
+        // x.UseXXX ...
+    });
+}
+
+

RabbitMQ Options

+

CAP 直接对外提供的 RabbitMQ 配置参数如下:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
配置项描述类型默认值
HostName宿主地址,如果要配置集群可以使用逗号分隔,例如 192.168.1.111,192.168.1.112stringlocalhost
UserName用户名stringguest
Password密码stringguest
VirtualHost虚拟主机string/
Port端口号int-1
ExchangeNameCAP默认Exchange名称stringcap.default.topic
QueueArguments队列额外参数 x-argumentsQueueArgumentsOptionsN/A
ConnectionFactoryOptionsRabbitMQClient原生参数ConnectionFactoryN/A
CustomHeadersBuilder订阅者自定义头信息见下文N/A
PublishConfirms是否启用发布确认boolfalse
BasicQosOptions指定消费的QosBasicQosN/A
+

ConnectionFactory Option

+

如果你需要 更多 原生 ConnectionFactory 相关的配置项,可以通过 ConnectionFactoryOptions 配置项进行设定:

+
services.AddCap(x =>
+{
+    x.UseRabbitMQ(o =>
+    {
+        o.HostName = "localhost";
+        o.ConnectionFactoryOptions = opt => { 
+            //rabbitmq client ConnectionFactory config
+        };
+    });
+});
+
+

CustomHeadersBuilder Option

+

当需要从异构系统或者直接接收从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)
+    ];
+});
+
+

如何连接 RabbitMQ 集群?

+

使用逗号分隔连接字符串即可,如下:

+
x=> x.UseRabbitMQ("localhost:5672,localhost:5673,localhost:5674")
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/transport/redis-streams/index.html b/user-guide/zh/transport/redis-streams/index.html new file mode 100644 index 000000000..e649cf4e6 --- /dev/null +++ b/user-guide/zh/transport/redis-streams/index.html @@ -0,0 +1,2151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Redis Streams - CAP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Redis Streams

+

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

+

Redis Stream 是 Redis 5.0 引入的一种新数据类型,它用一种仅附加的数据结构以更抽象的方式模拟日志数据结构。

+

Redis Streams 可以在 CAP 中用作消息传输器。

+

配置

+

要使用 Redis Streams 传输器,您需要从 NuGet 安装以下包:

+
PM> Install-Package DotNetCore.CAP.RedisStreams
+
+

然后,您可以在 Startup.csConfigureServices 方法中添加基于 Redis Stream 的配置项。

+
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCap(capOptions =>
+    {
+        capOptions.UseRedis(redisOptions=>{
+            //redisOptions
+        });
+    });
+}
+
+

Redis Streams Options

+

CAP 直接对外提供的 Redis Stream 配置参数如下:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NAMEDESCRIPTIONTYPEDEFAULT
Configurationredis连接配置 (StackExchange.Redis)ConfigurationOptionsConfigurationOptions
StreamEntriesCount读取时从 stream 返回的条目数uint10
ConnectionPoolSize连接池数uint10
+

Redis Configuration Options

+

如果需要**更多**原生Redis相关配置选项,您可以在 Configuration 选项中进行设置 :

+
services.AddCap(capOptions => 
+{
+    capOptions.UseRedis(redisOptions=>
+    {
+        // redis options.
+        redisOptions.Configuration.EndPoints.Add(IPAddress.Loopback, 0);
+    });
+});
+
+

Configuration 是 StackExchange.Redis ConfigurationOptions ,您可以通过此链接找到更多详细信息。

+

流清理注意事项

+

由于redis streams 没有自动删除所有已经被所有组确认的消息的特性issue,所以你需要考虑是否使用脚本来执行定期删除。

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file