{"id":485715,"date":"2026-06-30T20:32:20","date_gmt":"2026-06-30T20:32:20","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=485715"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=485715","title":{"rendered":"redb.Route \u2014 \u0443\u0445\u043e\u0434\u0438\u043c \u043e\u0442 MassTransit, \u0438\u0434\u0451\u043c \u043a Apache Camel: Kafka, Scatter\u2011Gather \u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/99f\/480\/ed4\/99f480ed42ac0a426f3fad62b964676f.webp\" alt=\"redb kafka connector\" title=\"redb kafka connector\" width=\"832\" height=\"1248\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/99f\/480\/ed4\/99f480ed42ac0a426f3fad62b964676f.webp 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/99f\/480\/ed4\/99f480ed42ac0a426f3fad62b964676f.webp 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>redb kafka connector<\/figcaption><\/div>\n<\/figure>\n<p><strong>\u0421\u0435\u0440\u0438\u044f:<\/strong> redb ecosystem \/ redb.Route deep-dive<\/p>\n<p>\u041e\u0447\u0435\u0440\u0435\u0434\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0438\u0437 \u0446\u0438\u043a\u043b\u0430 \u043f\u0440\u043e\u00a0<strong>redb.Route<\/strong>\u00a0\u2014 \u043d\u0430\u0448 Apache Camel \u043f\u043e\u0434 .NET. \u0415\u0441\u043b\u0438 \u0432\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043b\u0438\u0441\u044c, \u0432\u043e\u0442 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435 \u043d\u0430 \u0425\u0430\u0431\u0440\u0435:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1042392\/\" rel=\"noopener noreferrer nofollow\">redb.Route \u2014 Apache Camel \u0434\u043b\u044f .NET, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0432\u044b\u0445\u043e\u0434\u0430 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043d\u0435 \u0431\u044b\u043b\u043e<\/a>\u00a0\u2014 \u0441 \u0447\u0435\u0433\u043e \u0432\u0441\u0451 \u043d\u0430\u0447\u0430\u043b\u043e\u0441\u044c;<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1042872\/\" rel=\"noopener noreferrer nofollow\">redb.Route \u0438\u0437\u043d\u0443\u0442\u0440\u0438: \u0447\u0435\u0442\u044b\u0440\u0435 in\u2011memory \u043a\u0430\u043d\u0430\u043b\u0430 \u0438 Exchange, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0445 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442<\/a>;<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1043332\/\" rel=\"noopener noreferrer nofollow\">redb.Route 3.0.1 \u2014 \u043f\u043b\u043e\u0441\u043a\u0430\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u043f\u043e DSL, \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433 CRTP \u0438 \u0442\u0438\u0445\u0438\u0439 null<\/a>;<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1049222\/\" rel=\"noopener noreferrer nofollow\">Apache Camel \u043f\u043e\u0434 .NET, \u0440\u0430\u0437\u0431\u043e\u0440 \u043f\u043e \u043a\u043e\u0441\u0442\u043e\u0447\u043a\u0430\u043c: HTTP\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0431\u0435\u0437 <\/a><a href=\"http:\/\/ASP.NET\" rel=\"noopener noreferrer nofollow\">ASP.NET<\/a><a href=\"https:\/\/habr.com\/ru\/articles\/1049222\/\" rel=\"noopener noreferrer nofollow\"> MVC + \u043f\u0430\u0442\u0442\u0435\u0440\u043d Content\u2011Based Router<\/a>\u00a0\u2014 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f \u00abEIP + \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u00bb.<\/p>\n<\/li>\n<\/ul>\n<p>\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0437\u0430\u0445\u043e\u0434\u0438\u043c \u0441\u00a0<strong>Kafka\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430<\/strong>\u00a0\u2014 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u043c \u0435\u0433\u043e \u043f\u043e \u043a\u043e\u0441\u0442\u043e\u0447\u043a\u0430\u043c, \u043a\u0430\u043a \u0434\u0435\u043b\u0430\u043b\u0438 \u0441 HTTP, \u2014 \u0430 \u043f\u043e\u0442\u043e\u043c \u0441\u0430\u0436\u0430\u0435\u043c \u043d\u0430 \u043d\u0435\u0433\u043e \u0434\u0432\u0430 EIP\u2011\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430:\u00a0<strong>Scatter\u2011Gather<\/strong>\u00a0\u0438\u00a0<strong>Aggregator<\/strong>. \u0418 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u2014 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u043c \u0442\u043e, \u043e \u0447\u0451\u043c \u0432 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430\u0445 \u043c\u043e\u043b\u0447\u0430\u0442:\u00a0<strong>\u043a\u0430\u043a \u044d\u0442\u043e \u0436\u0438\u0432\u0451\u0442 \u043f\u043e\u0434 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u043c\u0438<\/strong>. \u0417\u0430\u043e\u0434\u043d\u043e \u0432\u044b\u0448\u0435\u043b\u00a0<strong>3.2.0<\/strong>.<\/p>\n<h3>\u0421\u0440\u0430\u0437\u0443 \u0441\u043f\u043e\u0439\u043b\u0435\u0440<\/h3>\n<p>\u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u00abexactly\u2011once \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438\u00bb \u0432 Kafka \u0437\u0434\u0435\u0441\u044c \u043d\u0435\u0442 \u2014 \u0438 \u043d\u0438\u0436\u0435 \u043f\u043e \u043a\u043e\u0434\u0443 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u043d\u043e, \u043f\u043e\u0447\u0435\u043c\u0443 \u0438\u043c\u0435\u043d\u043d\u043e. \u0427\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0430 \u0447\u0442\u043e \u043d\u0435\u0442 \u2014 \u043f\u043e \u0444\u0430\u043a\u0442\u0443, \u0431\u0435\u0437 \u043e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0439.<\/p>\n<hr\/>\n<h3>\u041e\u0433\u043b\u0430\u0432\u043b\u0435\u043d\u0438\u0435<\/h3>\n<ol>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#%D0%B7%D0%B0%D1%87%D0%B5%D0%BC\" rel=\"noopener noreferrer nofollow\">\u0417\u0430\u0447\u0435\u043c \u0443\u0445\u043e\u0434\u0438\u0442\u044c \u043e\u0442 MassTransit<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#v320\" rel=\"noopener noreferrer nofollow\">\u0427\u0442\u043e \u043f\u0440\u0438\u0435\u0445\u0430\u043b\u043e \u0432 3.2.0<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#kafka-uri\" rel=\"noopener noreferrer nofollow\">Kafka\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440: \u0430\u043d\u0430\u0442\u043e\u043c\u0438\u044f URI \u0438 \u0444\u0430\u0431\u0440\u0438\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#kafka-producer\" rel=\"noopener noreferrer nofollow\">Kafka\u2011\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440: \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430\u00a0<code>.To(\"kafka:...\")<\/code><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#kafka-consumer\" rel=\"noopener noreferrer nofollow\">Kafka\u2011\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440: \u043f\u043e\u043b\u043b\u0438\u043d\u0433, \u0431\u0430\u0442\u0447\u0438, \u0440\u0435\u0431\u0430\u043b\u0430\u043d\u0441<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#kafka-headers\" rel=\"noopener noreferrer nofollow\">\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0438 \u0442\u0440\u0435\u0439\u0441\u0438\u043d\u0433 \u0441\u043a\u0432\u043e\u0437\u044c \u0431\u0440\u043e\u043a\u0435\u0440<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#kafka-tx\" rel=\"noopener noreferrer nofollow\">\u0427\u0435\u0441\u0442\u043d\u043e \u043f\u0440\u043e\u00a0<code>transacted=true<\/code><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#scatter-gather\" rel=\"noopener noreferrer nofollow\">EIP #1 \u2014 Scatter\u2011Gather: \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440, \u0438 \u0440\u0430\u0437\u043e\u0441\u043b\u0430\u043b, \u0438 \u0441\u043e\u0431\u0440\u0430\u043b<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#aggregator\" rel=\"noopener noreferrer nofollow\">EIP #2 \u2014 Aggregator: \u0441\u0431\u043e\u0440\u043a\u0430 \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#transactions\" rel=\"noopener noreferrer nofollow\">\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438: \u0434\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438, \u0438 \u0432 \u044d\u0442\u043e\u043c \u0432\u0441\u044f \u0441\u043e\u043b\u044c<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#splitter-multicast\" rel=\"noopener noreferrer nofollow\">\u0420\u043e\u0434\u043d\u044f \u043f\u043e \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u0438: Splitter \u0438 Multicast<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#saga\" rel=\"noopener noreferrer nofollow\">Saga \u2014 \u00ab\u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u0440\u0443\u0433\u0430\u044f\u00bb<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#outbox\" rel=\"noopener noreferrer nofollow\">Outbox \u2014 \u0435\u0433\u043e \u043d\u0435\u0442, \u0438 \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/docs\/launch-2026-05\/HABR_340_SCATTERGATHER_KAFKA.md#%D0%B8%D1%82%D0%BE%D0%B3\" rel=\"noopener noreferrer nofollow\">\u0418\u0442\u043e\u0433: \u0440\u0430\u0437\u043c\u0435\u043d, \u0430 \u043d\u0435 \u00ab\u043b\u0443\u0447\u0448\u0435\/\u0445\u0443\u0436\u0435\u00bb<\/a><\/p>\n<\/li>\n<\/ol>\n<hr\/>\n<h3>1. \u0417\u0430\u0447\u0435\u043c \u0443\u0445\u043e\u0434\u0438\u0442\u044c \u043e\u0442 MassTransit<\/h3>\n<p>MassTransit \u2014 \u043e\u0442\u043b\u0438\u0447\u043d\u0430\u044f \u0448\u0442\u0443\u043a\u0430, \u0438 \u044f \u043d\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0441\u044c \u0435\u0451 \u0445\u043e\u0440\u043e\u043d\u0438\u0442\u044c. \u041d\u043e \u044d\u0442\u043e\u00a0<strong>opinionated<\/strong>\u2011\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a: durable saga \u043a\u0430\u043a state\u2011machine, transactional outbox, retry\u2011\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u2014 \u0432\u0441\u0451 \u0437\u0430\u0448\u0438\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u044c, \u0438 \u0442\u044b \u0436\u0438\u0432\u0451\u0448\u044c \u043f\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u0430\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430. \u041a\u043e\u0433\u0434\u0430 \u0442\u0432\u043e\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u0435\u0433\u043e \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u0439 \u043c\u0438\u0440\u0430 \u2014 \u044d\u0442\u043e \u043a\u0430\u0439\u0444, \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u00ab\u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438\u00bb. \u041a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u2014 \u0442\u044b \u0432\u043e\u044e\u0435\u0448\u044c \u0441 \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0435\u0439, \u043b\u0435\u0437\u0435\u0448\u044c \u0432 \u043a\u0438\u0448\u043a\u0438 \u0438 \u043f\u0438\u0448\u0435\u0448\u044c \u043a\u043e\u0441\u0442\u044b\u043b\u0438 \u043f\u043e\u0432\u0435\u0440\u0445 \u0447\u0443\u0436\u0438\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u0439.<\/p>\n<p>Apache Camel \u2014 \u0434\u0440\u0443\u0433\u0430\u044f \u0448\u043a\u043e\u043b\u0430. \u0412 \u0444\u0443\u043d\u0434\u0430\u043c\u0435\u043d\u0442\u0435:\u00a0<strong>EIP\u2011\u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u044b + \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b + DSL<\/strong>. \u0422\u0435\u0431\u0435 \u043d\u0438\u043a\u0442\u043e \u043d\u0435 \u00ab\u0434\u0430\u0440\u0438\u0442\u00bb outbox \u043a\u0430\u043a \u0444\u0438\u0447\u0443 \u2014 \u0442\u0435\u0431\u0435 \u0434\u0430\u044e\u0442 Splitter, Aggregator, Scatter\u2011Gather, \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b, \u0438 \u0442\u044b\u00a0<strong>\u0441\u0430\u043c \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0448\u044c<\/strong>\u00a0\u0440\u043e\u0432\u043d\u043e \u0442\u043e, \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434 \u0442\u0432\u043e\u0438 \u0438\u043d\u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b. \u041c\u0435\u043d\u044c\u0448\u0435 \u043c\u0430\u0433\u0438\u0438 \u2014 \u0431\u043e\u043b\u044c\u0448\u0435 \u044f\u0432\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438 \u0438\u0437 \u043a\u0443\u0431\u0438\u043a\u043e\u0432.<\/p>\n<p>redb.Route \u0441\u0442\u043e\u0438\u0442 \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u0448\u043a\u043e\u043b\u0435. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 \u2014 \u00ab\u0438\u0434\u0451\u043c \u043a Apache Camel\u00bb: \u043d\u0435 \u00ab\u0443 \u043d\u0430\u0441 \u0432\u0441\u0451 \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438\u00bb, \u0430 \u00ab\u0443 \u043d\u0430\u0441 \u043a\u0443\u0431\u0438\u043a\u0438, \u0438 \u043e\u043d\u0438 \u0441\u0442\u044b\u043a\u0443\u044e\u0442\u0441\u044f \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u043e\u00bb. \u0414\u0430\u043b\u044c\u0448\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u043d\u043e, \u0447\u0435\u043c \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e\u0442 \u0440\u0430\u0437\u043c\u0435\u043d \u043e\u043f\u043b\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438 \u0447\u0442\u043e \u0432\u0437\u0430\u043c\u0435\u043d \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0448\u044c.<\/p>\n<hr\/>\n<h3>2. \u0427\u0442\u043e \u043f\u0440\u0438\u0435\u0445\u0430\u043b\u043e \u0432 3.2.0<\/h3>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u2014 \u0432\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/CHANGELOG.md\" rel=\"noopener noreferrer nofollow\">CHANGELOG<\/a>. \u0414\u043b\u044f \u043d\u0430\u0448\u0435\u0439 \u0442\u0435\u043c\u044b \u0432\u0430\u0436\u043d\u044b \u0434\u0432\u0430 \u043f\u0443\u043d\u043a\u0442\u0430:<\/p>\n<ol>\n<li>\n<p><strong>\u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0435\u00a0<\/strong><code><strong>Splitter<\/strong><\/code><strong>\u00a0\/\u00a0<\/strong><code><strong>Multicast<\/strong><\/code><strong>\u00a0\u0442\u0435\u043f\u0435\u0440\u044c \u0438\u0437\u043e\u043b\u0438\u0440\u0443\u044e\u0442 ambient\u2011\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u043f\u043e \u0432\u0435\u0442\u043a\u0435.<\/strong>\u00a0\u041a\u0430\u0436\u0434\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u0432\u043e\u0439\u00a0<code>DependentTransaction.DependentClone(BlockCommitUntilComplete)<\/code>; \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043e\u043c\u043c\u0438\u0442 \u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0432\u0435\u0442\u043e\u043a. \u0420\u0430\u043d\u044c\u0448\u0435 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u0435\u0442\u043a\u0438 \u0448\u0430\u0440\u0438\u043b\u0438 \u043e\u0434\u0438\u043d\u00a0<code>Transaction.Current<\/code>, \u0430\u00a0<code>System.Transactions<\/code>\u00a0\u0437\u0430\u043f\u0440\u0435\u0449\u0430\u0435\u0442 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043f\u043e\u0442\u043e\u043a\u043e\u0432. \u041d\u0438\u0436\u0435 \u043c\u044b \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u044d\u0442\u043e\u0442 \u0444\u0438\u043a\u0441 \u043f\u043e \u043a\u043e\u0434\u0443 \u0438 \u043f\u043e\u0439\u043c\u0451\u043c,\u00a0<strong>\u043f\u043e\u0447\u0435\u043c\u0443 Scatter\u2011Gather \u0432 \u043d\u0451\u043c \u043d\u0435 \u043d\u0443\u0436\u0434\u0430\u043b\u0441\u044f<\/strong>.<\/p>\n<\/li>\n<li>\n<p><code><strong>Throttle<\/strong><\/code><strong>\u00a0\u043f\u043e\u043b\u0443\u0447\u0438\u043b RFC 6585\u2011\u0440\u0435\u0436\u0438\u043c<\/strong>\u00a0<code>.RejectOnOverflow()<\/code>\u00a0(429 +\u00a0<code>Retry\u2011After<\/code>). \u041a \u043d\u0430\u0448\u0435\u0439 \u0442\u0435\u043c\u0435 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u043d\u0435 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0441\u044f, \u043d\u043e \u0435\u0441\u043b\u0438 \u0441\u0442\u0440\u043e\u0438\u0442\u0435 HTTP\u2011\u0444\u0430\u0441\u0430\u0434 \u043f\u0435\u0440\u0435\u0434 \u0432\u0441\u0435\u043c \u044d\u0442\u0438\u043c \u2014 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u0441\u0435 \u043f\u0430\u043a\u0435\u0442\u044b\u00a0<code>redb.Route.*<\/code>\u00a0\u0432\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435:\u00a0<strong>3.2.0<\/strong>.<\/p>\n<hr\/>\n<h3>3. Kafka\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440: \u0430\u043d\u0430\u0442\u043e\u043c\u0438\u044f URI \u0438 \u0444\u0430\u0431\u0440\u0438\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439<\/h3>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c, \u043a\u0430\u043a \u043f\u0440\u043e\u0441\u0438\u043b\u0438, \u0441 Kafka. \u0418 \u043d\u0430\u0447\u043d\u0451\u043c \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0435\u0441\u0442\u044c \u2014\u00a0<strong>\u0441\u0442\u0440\u043e\u043a\u043e\u0432\u043e\u0433\u043e URI \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430<\/strong>. \u0412\u043e\u0442 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u0438 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440:<\/p>\n<pre><code>\/\/ \u041a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440: \u0447\u0438\u0442\u0430\u0435\u043c \u0442\u043e\u043f\u0438\u043a orders, \u0433\u0440\u0443\u043f\u043f\u0430 order-workers, \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043d\u0430\u0447\u0430\u043b\u0430From(\"kafka:orders?brokers=kafka:9092&amp;groupId=order-workers&amp;autoOffsetReset=earliest\")    .Log(\"\u041f\u043e\u043b\u0443\u0447\u0438\u043b\u0438: ${body}\")    .To(\"direct:process\");\/\/ \u041f\u0440\u043e\u0434\u044e\u0441\u0435\u0440: \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u043c \u0432 \u0442\u043e\u043f\u0438\u043a notifications \u0441 acks=allFrom(\"direct:notify\")    .To(\"kafka:notifications?brokers=kafka:9092&amp;acks=All\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u0445\u0435\u043c\u0430 URI \u2014\u00a0<code>kafka:&lt;topic&gt;?&lt;query&gt;<\/code>. \u0418\u043c\u044f \u0442\u043e\u043f\u0438\u043a\u0430 \u0431\u0435\u0440\u0451\u0442\u0441\u044f \u0438\u0437 \u043f\u0443\u0442\u0438 (<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Kafka\/KafkaEndpoint.cs\" rel=\"noopener noreferrer nofollow\"><code>KafkaEndpoint<\/code><\/a>):<\/p>\n<pre><code>public KafkaEndpoint(EndpointUri uri, KafkaComponent component, KafkaEndpointOptions options)    : base(uri, component, options){    TopicName = uri.Path;                       \/\/ \u2190 \u0442\u043e\u043f\u0438\u043a = \u043f\u0443\u0442\u044c URI    \/\/ \u0415\u0441\u043b\u0438 \u0432 URI \u0443\u043a\u0430\u0437\u0430\u043d connectionFactory=&lt;\u0438\u043c\u044f&gt; \u2014 \u0440\u0435\u0437\u043e\u043b\u0432\u0438\u043c \u0444\u0430\u0431\u0440\u0438\u043a\u0443 \u0438\u0437 \u0440\u0435\u0435\u0441\u0442\u0440\u0430    if (!string.IsNullOrEmpty(options.ConnectionFactory) &amp;&amp; component.Context is not null)        ResolvedFactory = component.Context.GetFromRegistry&lt;KafkaConnectionFactory&gt;(options.ConnectionFactory);    \/\/ brokers \u0432 URI \u043d\u0435\u0442, \u043d\u043e \u0444\u0430\u0431\u0440\u0438\u043a\u0430 \u0438\u0445 \u0437\u043d\u0430\u0435\u0442 \u2014 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c    if (string.IsNullOrWhiteSpace(options.Brokers) &amp;&amp; ResolvedFactory is not null)        options.Brokers = ResolvedFactory.Brokers;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0448\u0442\u0440\u0438\u0445: \u043d\u0430 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u0435\u00a0<code>groupId<\/code>\u00a0<strong>\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d<\/strong>, \u0438 \u0435\u0441\u043b\u0438 \u0435\u0433\u043e \u0437\u0430\u0431\u044b\u0442\u044c \u2014 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 \u043d\u0435 \u0434\u0430\u0441\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440:<\/p>\n<pre><code>public override IConsumer CreateConsumer(IProcessor processor){    if (string.IsNullOrWhiteSpace(Options.GroupId))        throw new InvalidOperationException(            $\"The 'groupId' parameter is required for Kafka consumer on topic '{TopicName}'.\");    return new KafkaConsumer(this, processor, Options);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0424\u0430\u0431\u0440\u0438\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u2014 \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438<\/h4>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0443 \u0442\u0435\u0431\u044f \u0434\u0432\u0430\u0434\u0446\u0430\u0442\u044c Kafka\u2011\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432 \u043d\u0430 \u043e\u0434\u0438\u043d \u043a\u043b\u0430\u0441\u0442\u0435\u0440, \u0440\u0430\u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u00a0<code>brokers=...&amp;securityProtocol=...&amp;saslMechanism=...<\/code>\u00a0\u0432 \u043a\u0430\u0436\u0434\u043e\u043c URI \u2014 \u0441\u0430\u043c\u043e\u0443\u0431\u0438\u0439\u0441\u0442\u0432\u043e. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0435\u0441\u0442\u044c\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Kafka\/KafkaConnectionFactory.cs\" rel=\"noopener noreferrer nofollow\"><code>KafkaConnectionFactory<\/code><\/a>: \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0448\u044c \u0435\u0451 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435 \u043f\u043e\u0434 \u0438\u043c\u0435\u043d\u0435\u043c \u0438 \u0441\u0441\u044b\u043b\u0430\u0435\u0448\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437\u00a0<code>connectionFactory=\u0438\u043c\u044f<\/code>.<\/p>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0435\u0451 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u2011\u0441\u0435\u0442 (\u0432\u0441\u0451 \u0438\u0437 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0430, \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0432\u044b\u0434\u0443\u043c\u0430\u043d\u043e):<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0413\u0440\u0443\u043f\u043f\u0430<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0414\u0435\u0444\u043e\u043b\u0442<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0411\u0440\u043e\u043a\u0435\u0440\u044b<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>Brokers<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>localhost:9092<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>SecurityProtocol<\/code>,\u00a0<code>SaslMechanism<\/code>,\u00a0<code>SaslUsername<\/code>,\u00a0<code>SaslPassword<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>Plaintext<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">SSL\/TLS<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>SslCaLocation<\/code>,\u00a0<code>SslCertificateLocation<\/code>,\u00a0<code>SslKeyLocation<\/code>,\u00a0<code>SslKeyPassword<\/code>,\u00a0<code>SslEndpointIdentificationAlgorithm<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041f\u0440\u043e\u0434\u044e\u0441\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>Acks<\/code>,\u00a0<code>Retries<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>Leader<\/code>,\u00a0<code>3<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>GroupId<\/code>,\u00a0<code>AutoOffsetReset<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014,\u00a0<code>Latest<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0422\u044e\u043d\u0438\u043d\u0433 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>GroupInstanceId<\/code>,\u00a0<code>SessionTimeoutMs<\/code>,\u00a0<code>HeartbeatIntervalMs<\/code>,\u00a0<code>MaxPollIntervalMs<\/code>,\u00a0<code>PartitionAssignmentStrategy<\/code>,\u00a0<code>IsolationLevel<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0422\u044e\u043d\u0438\u043d\u0433 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>LingerMs<\/code>,\u00a0<code>BatchSize<\/code>,\u00a0<code>CompressionType<\/code>,\u00a0<code>MessageTimeoutMs<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Reconnect<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>ReconnectBackoffMs<\/code>,\u00a0<code>ReconnectBackoffMaxMs<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Advanced<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>ClientId<\/code>,\u00a0<code>RequestTimeoutMs<\/code>,\u00a0<code>MetadataMaxAgeMs<\/code>,\u00a0<code>SocketTimeoutMs<\/code>,\u00a0<code>MaxInFlight<\/code>,\u00a0<code>AdditionalProperties<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>redb.Route<\/code>, 30000, 300000, 60000, 5<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435:<\/p>\n<pre><code>context.AddToRegistry(\"prod-cluster\", new KafkaConnectionFactory{    Brokers          = \"kafka1:9092,kafka2:9092,kafka3:9092\",    SecurityProtocol = \"SaslSsl\",    SaslMechanism    = \"ScramSha512\",    SaslUsername     = \"svc-orders\",    SaslPassword     = secret,    CompressionType  = \"Zstd\",    MaxInFlight      = 5,});\/\/ \u0422\u0435\u043f\u0435\u0440\u044c \u0432 URI \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e, \u0447\u0442\u043e \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u043e \u0434\u043b\u044f \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430:From(\"kafka:orders?connectionFactory=prod-cluster&amp;groupId=order-workers\");To(\"kafka:notifications?connectionFactory=prod-cluster&amp;acks=All\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>KafkaConnectionFactory<\/code>\u00a0\u0443\u043c\u0435\u0435\u0442 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0442\u0440\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u2014\u00a0<code>BuildConsumerConfig()<\/code>,\u00a0<code>BuildProducerConfig()<\/code>,\u00a0<code>BuildAdminConfig()<\/code>\u00a0(\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u043e\u043f\u0438\u043a\u0430). \u042d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u2011\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0435 \u043e\u043f\u0446\u0438\u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f\u00a0<strong>\u043f\u043e\u0432\u0435\u0440\u0445<\/strong>\u00a0\u0444\u0430\u0431\u0440\u0438\u043a\u0438 (<code>if (!string.IsNullOrWhiteSpace(Brokers)) config.BootstrapServers = Brokers;<\/code>\u00a0\u0438 \u0442.\u0434.) \u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u0444\u0430\u0431\u0440\u0438\u043a\u0430 \u0437\u0430\u0434\u0430\u0451\u0442 \u0431\u0430\u0437\u0443, URI \u0435\u0451 \u0442\u043e\u0447\u0435\u0447\u043d\u043e \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u0435\u0442.<\/p>\n<h4>\u0412\u0441\u0435 \u043e\u043f\u0446\u0438\u0438 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430<\/h4>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u2014\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Kafka\/KafkaEndpointOptions.cs\" rel=\"noopener noreferrer nofollow\"><code>KafkaEndpointOptions<\/code><\/a>. \u0413\u0440\u0443\u043f\u043f\u0430\u043c\u0438, \u0441 \u0434\u0435\u0444\u043e\u043b\u0442\u0430\u043c\u0438:<\/p>\n<p><strong>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \/ \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c:<\/strong>\u00a0<code>brokers<\/code>\u00a0(<strong>\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d<\/strong>, \u0438\u043d\u0430\u0447\u0435\u00a0<code>Validate()<\/code>\u00a0\u0431\u0440\u043e\u0441\u0438\u0442),\u00a0<code>securityProtocol<\/code>\u00a0(<code>Plaintext<\/code>\/<code>Ssl<\/code>\/<code>SaslPlaintext<\/code>\/<code>SaslSsl<\/code>),\u00a0<code>saslMechanism<\/code>\/<code>saslUsername<\/code>\/<code>saslPassword<\/code>,\u00a0<code>sslCaLocation<\/code>\/<code>sslCertificateLocation<\/code>\/<code>sslKeyLocation<\/code>\/<code>sslKeyPassword<\/code>,\u00a0<code>connectionFactory<\/code>.<\/p>\n<p><strong>\u041a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440:<\/strong>\u00a0<code>groupId<\/code>,\u00a0<code>autoOffsetReset<\/code>\u00a0(<code>Latest<\/code>\/<code>Earliest<\/code>\/<code>Error<\/code>, \u0434\u0435\u0444\u043e\u043b\u0442\u00a0<code>Latest<\/code>),\u00a0<code>enableAutoCommit<\/code>\u00a0(\u0434\u0435\u0444\u043e\u043b\u0442\u00a0<code>true<\/code>, framework-level \u2014 \u0441 3.2.1),\u00a0<code>maxPollRecords<\/code>\u00a0(0 = \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c, &gt;0 = \u0431\u0430\u0442\u0447),\u00a0<code>pollTimeoutMs<\/code>\u00a0(1000),\u00a0<code>breakOnFirstError<\/code>,\u00a0<code>seekTo<\/code>\u00a0(<code>beginning<\/code>\/<code>end<\/code>),\u00a0<code>topicIsPattern<\/code>,\u00a0<code>groupInstanceId<\/code>,\u00a0<code>sessionTimeoutMs<\/code>,\u00a0<code>heartbeatIntervalMs<\/code>,\u00a0<code>maxPollIntervalMs<\/code>,\u00a0<code>partitionAssignmentStrategy<\/code>\u00a0(<code>Range<\/code>\/<code>RoundRobin<\/code>\/<code>CooperativeSticky<\/code>),\u00a0<code>isolationLevel<\/code>\u00a0(<code>ReadUncommitted<\/code>\/<code>ReadCommitted<\/code>).<\/p>\n<p><strong>\u041f\u0440\u043e\u0434\u044e\u0441\u0435\u0440:<\/strong>\u00a0<code>acks<\/code>\u00a0(<code>None<\/code>\/<code>Leader<\/code>\/<code>All<\/code>, \u0434\u0435\u0444\u043e\u043b\u0442\u00a0<code>Leader<\/code>),\u00a0<code>retries<\/code>\u00a0(3),\u00a0<code>recordMetadata<\/code>,\u00a0<code>key<\/code>,\u00a0<code>partitionNumber<\/code>,\u00a0<code>transacted<\/code>,\u00a0<code>transactionIdPrefix<\/code>\u00a0(<code>redb-kafka<\/code>).<\/p>\n<p><strong>\u0422\u044e\u043d\u0438\u043d\u0433 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u0430:<\/strong>\u00a0<code>lingerMs<\/code>,\u00a0<code>batchSize<\/code>,\u00a0<code>compressionType<\/code>\u00a0(<code>None<\/code>\/<code>Gzip<\/code>\/<code>Snappy<\/code>\/<code>Lz4<\/code>\/<code>Zstd<\/code>),\u00a0<code>messageTimeoutMs<\/code>.<\/p>\n<p><strong>Advanced:<\/strong>\u00a0<code>additionalProperties<\/code>\u00a0\u2014 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0435 librdkafka\u2011\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f\u00a0<strong>\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u043c\u0438<\/strong>\u00a0\u0438 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u044e\u0442 \u0432\u0441\u0451 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435.<\/p>\n<p><code>Validate()<\/code>\u00a0\u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0440\u043e\u0432\u043d\u043e \u0442\u0440\u0438 \u0432\u0435\u0449\u0438 (\u043f\u043e \u043a\u043e\u0434\u0443):\u00a0<code>brokers<\/code>\u00a0\u043d\u0435\u043f\u0443\u0441\u0442\u043e\u0439,\u00a0<code>maxPollRecords &gt;= 0<\/code>,\u00a0<code>pollTimeoutMs &gt;= 0<\/code>,\u00a0<code>retries &gt;= 0<\/code>. \u0412\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043b\u0438\u0431\u043e \u0438\u043c\u0435\u0435\u0442 \u0434\u0435\u0444\u043e\u043b\u0442, \u043b\u0438\u0431\u043e \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e.<\/p>\n<p>\u041e\u0434\u0438\u043d \u043d\u044e\u0430\u043d\u0441 \u2014 \u043f\u0440\u043e \u043a\u043e\u043c\u043c\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442\u0430, \u0438 \u043e\u043d \u0432\u0430\u0436\u043d\u044b\u0439. \u041d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435\u00a0<strong>librdkafka<\/strong>\u00a0<code>EnableAutoCommit<\/code>\u00a0\u043f\u0440\u043e\u0448\u0438\u0442 \u0432\u00a0<code>false<\/code>\u00a0<strong>\u0432\u0441\u0435\u0433\u0434\u0430<\/strong>\u00a0\u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0441\u0430\u043c\u0430, \u043f\u043e \u0442\u0430\u0439\u043c\u0435\u0440\u0443, \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 (\u0438\u043d\u0430\u0447\u0435 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b\u0430 \u0431\u044b \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043d\u043e\u0435). \u0418\u0437\u00a0<code>BuildConsumerConfig<\/code>:<\/p>\n<pre><code>var cfg = new ConsumerConfig{    BootstrapServers = Brokers,    GroupId          = GroupId,    AutoOffsetReset  = ParseAutoOffsetReset(),    EnableAutoCommit = false,   \/\/ librdkafka-\u0443\u0440\u043e\u0432\u0435\u043d\u044c: \u0440\u0443\u0447\u043d\u043e\u0439 \u043a\u043e\u043c\u043c\u0438\u0442 \u0432\u0441\u0435\u0433\u0434\u0430};<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u043e \u043a\u043e\u043c\u043c\u0438\u0442, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u0435\u0441\u0442\u044c \u2014 \u043f\u043e\u0432\u0435\u0440\u0445, \u043d\u0430\u00a0<strong>\u0443\u0440\u043e\u0432\u043d\u0435 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430<\/strong>. \u0421\u00a0<strong>3.2.1<\/strong>\u00a0\u0443 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u0430 \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u043e\u043f\u0446\u0438\u044f\u00a0<code><strong>EnableAutoCommit<\/strong><\/code>\u00a0(\u0434\u0435\u0444\u043e\u043b\u0442\u00a0<code>true<\/code>): \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e\u00a0<code>Process<\/code>\u00a0\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442\u00a0<strong>\u0438\u043d\u043b\u0430\u0439\u043d<\/strong>\u00a0\u2014 \u0440\u043e\u0432\u043d\u043e \u043a\u0430\u043a RabbitMQ\u2011\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u0430\u0446\u043a\u0430\u0435\u0442 \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0421\u0442\u0430\u0432\u0438\u0442\u0441\u044f \u0438\u0437 URI (<code>?enableAutoCommit=false<\/code>) \u0438\u043b\u0438 \u0444\u043b\u0435\u043d\u0442\u043e\u043c (<code>.EnableAutoCommit(false)<\/code>). \u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0438\u043c\u0435\u0435\u0442 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442: \u0435\u0441\u043b\u0438 \u043e\u0444\u0444\u0441\u0435\u0442 \u0443\u0436\u0435 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b\u0430\u00a0<code>.Transacted()<\/code>, \u0438\u043d\u043b\u0430\u0439\u043d\u2011\u0432\u0435\u0442\u043a\u0430 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f. \u041c\u0435\u0445\u0430\u043d\u0438\u043a\u0443 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u0432 \u00a75.<\/p>\n<h4>\u0422\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0444\u043b\u0435\u043d\u0442\u043e\u043c<\/h4>\n<p>\u0421\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0435 URI \u0445\u043e\u0440\u043e\u0448\u0438 \u0434\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0438 \u0434\u043b\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u0432. \u0412 \u043a\u043e\u0434\u0435 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b \u043e\u0431\u044b\u0447\u043d\u043e \u0441\u0442\u0440\u043e\u044f\u0442 \u0444\u043b\u0435\u043d\u0442\u043e\u043c \u2014\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Kafka\/Fluent\/KafkaDsl.cs\" rel=\"noopener noreferrer nofollow\"><code>Kafka.Topic(...)<\/code><\/a>:<\/p>\n<pre><code>From(Kafka.Topic(\"orders\")        .Brokers(\"kafka1:9092,kafka2:9092\")        .GroupId(\"order-workers\")        .AutoOffsetReset(\"Earliest\")        .MaxPollRecords(500)        .PartitionAssignmentStrategy(\"CooperativeSticky\")        .IsolationLevel(\"ReadCommitted\")        .SessionTimeout(30_000)        .MaxPollInterval(300_000));To(Kafka.Topic(\"notifications\")        .Brokers(\"kafka1:9092\")        .Acks(\"All\")        .Compression(\"zstd\")        .Linger(20)        .BatchSize(64 * 1024)        .Key(\"${header.orderId}\"));   \/\/ \u043a\u043b\u044e\u0447 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u2014 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0424\u043b\u0435\u043d\u0442\u2011\u0431\u0438\u043b\u0434\u0435\u0440 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u0442\u0443 \u0436\u0435 query\u2011\u0441\u0442\u0440\u043e\u043a\u0443 (<code>Kafka.Topic(\"orders\").Brokers(...)...Build()<\/code>\u00a0\u2192\u00a0<code>\"kafka:orders?brokers=...&amp;...\"<\/code>), \u0441 URL\u2011\u044d\u043d\u043a\u043e\u0434\u0438\u043d\u0433\u043e\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439. \u0421\u0435\u0442\u0442\u0435\u0440\u044b \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0442\u00a0<code>IExpression<\/code>, \u043d\u043e \u0440\u0435\u0437\u043e\u043b\u0432\u044f\u0442\u0441\u044f \u043e\u043f\u0446\u0438\u0438 \u0432 \u0440\u0430\u0437\u043d\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b:\u00a0<strong>\u043a\u043b\u044e\u0447 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u0430<\/strong>\u00a0\u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f\u00a0<code>${...}<\/code>\u2011\u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c\u00a0<strong>\u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435<\/strong>\u00a0(\u0432\u00a0<code>DetermineKey<\/code>\u00a0\u043d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u2014 \u0441\u043c. \u043d\u0438\u0436\u0435), \u0430 connection\u2011level \u043e\u043f\u0446\u0438\u0438 (<code>brokers<\/code>, \u043a\u0440\u0435\u0434\u044b) \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043d\u0430 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0435, \u0433\u0434\u0435 exchange \u0435\u0449\u0451 \u043d\u0435\u0442, \u0442\u0430\u043a \u0447\u0442\u043e \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0432 \u043d\u0438\u0445 \u0441\u043c\u044b\u0441\u043b\u0430 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442. Per\u2011message\u2011\u0438\u0441\u0442\u043e\u0440\u0438\u044f \u2014 \u044d\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u043e \u043a\u043b\u044e\u0447 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<hr\/>\n<h3>4. Kafka\u2011\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440: \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430\u00a0.To(&#171;kafka:&#8230;&#187;)<\/h3>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c, \u0447\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Kafka\/KafkaProducer.cs\" rel=\"noopener noreferrer nofollow\"><code>KafkaProducer<\/code><\/a>, \u043a\u043e\u0433\u0434\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0434\u043e\u0445\u043e\u0434\u0438\u0442 \u0434\u043e\u00a0<code>.To(\"kafka:...\")<\/code>. \u042d\u0442\u043e\u00a0<code>ConnectableProducer<\/code>\u00a0\u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u00a0<code>Start()<\/code>\u00a0\u043f\u0435\u0440\u0435\u0434 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c (<code>EnsureStarted()<\/code>\u00a0\u0432 \u043d\u0430\u0447\u0430\u043b\u0435\u00a0<code>Process<\/code>).<\/p>\n<h4>\u041a\u043e\u043d\u043d\u0435\u043a\u0442<\/h4>\n<pre><code>protected override Task ConnectAsync(CancellationToken ct){    var config = _options.BuildProducerConfig(_endpoint.ResolvedFactory);    _producer = new ProducerBuilder&lt;string, byte[]&gt;(config)        .SetValueSerializer(Serializers.ByteArray)        .SetErrorHandler((_, e) =&gt;            Logger?.LogError(\"Kafka producer error: {Reason} (Code: {Code})\", e.Reason, e.Code))        .Build();    if (_options.Transacted)    {        _producer.InitTransactions(TimeSpan.FromSeconds(30));        Logger?.LogInformation(\"Kafka transactional mode enabled: topic={Topic}\", _endpoint.TopicName);    }    return Task.CompletedTask;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u043b\u044e\u0447 \u2014\u00a0<code>string<\/code>, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u2014\u00a0<code>byte[]<\/code>. \u041d\u0430 \u0434\u0438\u0441\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0435 \u2014\u00a0<code>Flush(30s)<\/code>\u00a0\u043f\u0435\u0440\u0435\u0434\u00a0<code>Dispose<\/code>, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u0442\u044c \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f.<\/p>\n<h4>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f<\/h4>\n<p><code>PrepareMessage<\/code>\u00a0\u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u0435\u043b\u043e exchange \u0432\u00a0<code>Message&lt;string, byte[]&gt;<\/code>:<\/p>\n<pre><code>byte[] valueBytes = body switch{    byte[] bytes =&gt; bytes,                                  \/\/ \u0443\u0436\u0435 \u0431\u0430\u0439\u0442\u044b \u2014 \u043a\u0430\u043a \u0435\u0441\u0442\u044c    string str   =&gt; Encoding.UTF8.GetBytes(str),            \/\/ \u0441\u0442\u0440\u043e\u043a\u0430 \u2192 UTF-8    null         =&gt; Array.Empty&lt;byte&gt;(),                    \/\/ null \u2192 \u043f\u0443\u0441\u0442\u043e\u0439 \u043c\u0430\u0441\u0441\u0438\u0432    _            =&gt; Encoding.UTF8.GetBytes(body.ToString() ?? string.Empty)  \/\/ \u043f\u0440\u043e\u0447\u0435\u0435 \u2192 ToString \u2192 UTF-8};<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u2014 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438.\u00a0<code>ContentType<\/code>\u00a0\u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u043c\u00a0<code>content-type<\/code>, \u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 exchange \u043f\u0435\u0440\u0435\u0435\u0437\u0436\u0430\u044e\u0442 \u0432 Kafka\u2011\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u2014\u00a0<strong>\u043a\u0440\u043e\u043c\u0435 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445\u00a0<\/strong><code><strong>redbKafka.*<\/strong><\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0442\u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u044b\u0432\u0430\u044e\u0442\u0441\u044f:<\/p>\n<pre><code>foreach (var (key, value) in exchange.In.Headers){    if (KafkaHeaders.IsRedbHeader(key))   \/\/ redbKafka.* \u2014 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435, \u043d\u0435 \u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c        continue;    msg.Headers.Add(key, Encoding.UTF8.GetBytes(value?.ToString() ?? string.Empty));}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u041a\u043b\u044e\u0447 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/h4>\n<p><code>DetermineKey<\/code>\u00a0\u2014 \u0442\u0440\u0438 \u0432\u0435\u0442\u043a\u0438, \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u0430:<\/p>\n<pre><code>private string? DetermineKey(IExchange exchange){    \/\/ 1. \u042f\u0432\u043d\u0430\u044f \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u044f \u2192 \u043a\u043b\u044e\u0447 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d (\u043f\u0430\u0440\u0442\u0438\u0446\u0438\u043e\u043d\u0435\u0440 \u043e\u0431\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0441\u0442\u043e\u0440\u043e\u043d\u043e\u0439)    if (_options.PartitionNumber.HasValue)        return null;    \/\/ 2. \u041a\u043b\u044e\u0447 \u0438\u0437 \u043e\u043f\u0446\u0438\u0438 \u2014 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 ${...}-\u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u0440\u0435\u0437\u043e\u043b\u0432\u0438\u0442\u0441\u044f \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435    if (!string.IsNullOrWhiteSpace(_options.Key))    {        var resolved = _options.ResolveOption(_options.Key, exchange);        if (!string.IsNullOrEmpty(resolved)) return resolved;    }    \/\/ 3. \u0418\u043d\u0430\u0447\u0435 \u2014 \u0431\u0435\u0437 \u043a\u043b\u044e\u0447\u0430 (round-robin \u043f\u043e \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u044f\u043c)    return null;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c\u00a0<code>key=${header.orderId}<\/code>\u00a0\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u00ab\u043f\u0430\u0440\u0442\u0438\u0446\u0438\u043e\u043d\u0438\u0440\u0443\u0439 \u043f\u043e orderId\u00bb \u2014 \u0432\u0441\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u0437\u0430\u043a\u0430\u0437\u0430 \u043b\u044f\u0433\u0443\u0442 \u0432 \u043e\u0434\u043d\u0443 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u044e \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442 \u043f\u043e\u0440\u044f\u0434\u043e\u043a.<\/p>\n<h4>\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430: \u043d\u0435\u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0430\u044f vs \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u0430\u044f<\/h4>\n<p>\u0412\u043e\u0442 \u0440\u0430\u0437\u0432\u0438\u043b\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u0430\u0436\u043d\u0430 \u0434\u043b\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439:<\/p>\n<pre><code>if (_options.Transacted){    \/\/ \u041e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u0430\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u2014 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 publish \u0441\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u043d\u0430 \u043a\u043e\u043c\u043c\u0438\u0442\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438    var action = new KafkaSendAction(_producer, _endpoint.TopicName, message,        _options.PartitionNumber, _options.RecordMetadata, exchange, Logger);    RegisterTransactedAction(exchange, $\"kafka-send-{Guid.NewGuid():N}\", action);}else{    \/\/ \u041d\u0435\u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0430\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430    var result = _options.PartitionNumber.HasValue        ? await _producer.ProduceAsync(new TopicPartition(_endpoint.TopicName,              new Partition(_options.PartitionNumber.Value)), message, ct)        : await _producer.ProduceAsync(_endpoint.TopicName, message, ct);    if (_options.RecordMetadata)        AddDeliveryMetadata(exchange, result);   \/\/ redbKafka.Sent.Topic\/Partition\/Offset\/Timestamp}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440 \u043d\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u0440\u0430\u0437\u0443 \u2014 \u043e\u043d \u043a\u043b\u0430\u0434\u0451\u0442\u00a0<code>KafkaSendAction<\/code>\u00a0\u0432 \u0441\u043b\u043e\u0432\u0430\u0440\u044c \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0441 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c\u00a0<code>kafka-send-{guid}<\/code>. \u0420\u0435\u0430\u043b\u044c\u043d\u044b\u0439\u00a0<code>ProduceAsync<\/code>\u00a0\u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0451\u0442 \u043f\u043e\u0437\u0436\u0435, \u043d\u0430 \u0433\u0440\u0430\u043d\u0438\u0446\u0435\u00a0<code>.Transacted()<\/code>. \u041a \u044d\u0442\u043e\u043c\u0443 \u0432\u0435\u0440\u043d\u0451\u043c\u0441\u044f.<\/p>\n<p><code>KafkaSendAction<\/code>\u00a0\u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438\u00a0<strong>\u043a\u043b\u043e\u043d\u0438\u0440\u0443\u0435\u0442<\/strong>\u00a0\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 (\u0433\u043b\u0443\u0431\u043e\u043a\u0430\u044f \u043a\u043e\u043f\u0438\u044f \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u0447\u0435\u0440\u0435\u0437\u00a0<code>Buffer.BlockCopy<\/code>), \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u0430\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0435\u043b\u0430 \u043e\u0442 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0441\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u0441 exchange \u0434\u0430\u043b\u044c\u0448\u0435:<\/p>\n<pre><code>public async Task Commit(CancellationToken ct = default){    var result = _partition.HasValue        ? await _producer.ProduceAsync(new TopicPartition(_topicName, new Partition(_partition.Value)), _message, ct)        : await _producer.ProduceAsync(_topicName, _message, ct);    if (_recordMetadata) { \/* \u043f\u0438\u0448\u0435\u043c Sent.* \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 *\/ }}public Task Rollback(CancellationToken ct = default){    \/\/ \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u044b\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u2014 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043b\u0438    return Task.CompletedTask;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0422\u0440\u0435\u0439\u0441\u0438\u043d\u0433 \u0441\u043a\u0432\u043e\u0437\u044c \u0431\u0440\u043e\u043a\u0435\u0440<\/h4>\n<p>\u041f\u0435\u0440\u0435\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440 \u0438\u043d\u0436\u0435\u043a\u0442\u0438\u0442 W3C trace context (<code>traceparent<\/code>\/<code>tracestate<\/code>) \u0432 Kafka\u2011\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439\u00a0<code>DistributedContextPropagator<\/code>:<\/p>\n<pre><code>var propagator = DistributedContextPropagator.Current;propagator.Inject(activity, headers, static (carrier, key, value) =&gt;{    if (carrier is Headers h &amp;&amp; !string.IsNullOrEmpty(value))    {        h.Remove(key);        h.Add(key, Encoding.UTF8.GetBytes(value));    }});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u043c \u043a\u043e\u043d\u0446\u0435 \u044d\u0442\u043e \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u0442 \u2014 \u0438 \u0432 \u0442\u0440\u0435\u0439\u0441\u0435 \u0432\u044b \u0443\u0432\u0438\u0434\u0438\u0442\u0435 \u043d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u0443\u044e \u0446\u0435\u043f\u043e\u0447\u043a\u0443 \u0447\u0435\u0440\u0435\u0437 \u0431\u0440\u043e\u043a\u0435\u0440. \u0411\u0435\u0437 \u0435\u0434\u0438\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441 \u0432\u0430\u0448\u0435\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b.<\/p>\n<hr\/>\n<h3>5. Kafka\u2011\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440: \u043f\u043e\u043b\u043b\u0438\u043d\u0433, \u0431\u0430\u0442\u0447\u0438, \u0440\u0435\u0431\u0430\u043b\u0430\u043d\u0441<\/h3>\n<p><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Kafka\/KafkaConsumer.cs\" rel=\"noopener noreferrer nofollow\"><code>KafkaConsumer<\/code><\/a>\u00a0\u2014 \u044d\u0442\u043e\u00a0<code>DrainableConsumer<\/code>\u00a0(\u0443\u043c\u0435\u0435\u0442 gracefully \u0434\u043e\u0441\u043b\u0438\u0442\u044c in\u2011flight \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435). \u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u043e \u0448\u0430\u0433\u0430\u043c.<\/p>\n<h4>\u0421\u0442\u0430\u0440\u0442: \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430\/\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, seek, \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435<\/h4>\n<pre><code>protected override Task OnStarting(CancellationToken ct){    var config = _options.BuildConsumerConfig(_endpoint.ResolvedFactory);    _consumer = new ConsumerBuilder&lt;string, byte[]&gt;(config)        .SetValueDeserializer(Deserializers.ByteArray)        .SetErrorHandler((_, e) =&gt; { \/* fatal \u2192 LogError, \u0438\u043d\u0430\u0447\u0435 LogWarning *\/ })        .SetPartitionsAssignedHandler((c, partitions) =&gt; { \/* \u043b\u043e\u0433 assigned *\/ })        .SetPartitionsRevokedHandler((c, partitions) =&gt;        {            \/\/ \u041f\u0435\u0440\u0435\u0434 \u043e\u0442\u0437\u044b\u0432\u043e\u043c \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0439 \u2014 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043c \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u043e\u0444\u0444\u0441\u0435\u0442\u044b            try { c.Commit(partitions); }            catch (KafkaException) { \/* \u043d\u0435\u0447\u0435\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u044c \u2014 \u043e\u043a *\/ }        })        .SetPartitionsLostHandler((_, partitions) =&gt; { \/* \u043b\u043e\u0433 involuntary loss *\/ })        .Build();    if (_options.PartitionNumber.HasValue)        _consumer.Assign(new[] { new TopicPartition(_endpoint.TopicName, new Partition(_options.PartitionNumber.Value)) });    else        _consumer.Subscribe(_endpoint.TopicName);    HandleSeekTo();        \/\/ seekTo=beginning\/end \u043d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u0441\u0442\u0430\u0440\u0442\u0435    LogTopicMetadata();    \/\/ \u0434\u0430\u043c\u043f: \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0438, \u043b\u0438\u0434\u0435\u0440\u044b, \u0440\u0435\u043f\u043b\u0438\u043a\u0438, ISR    return Task.CompletedTask;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u0432\u0430 \u0440\u0435\u0436\u0438\u043c\u0430 \u0447\u043b\u0435\u043d\u0441\u0442\u0432\u0430: \u043b\u0438\u0431\u043e\u00a0<code>Subscribe(topic)<\/code>\u00a0(\u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0439 \u0433\u0440\u0443\u043f\u043f\u043e\u0439 + \u0440\u0435\u0431\u0430\u043b\u0430\u043d\u0441), \u043b\u0438\u0431\u043e \u044f\u0432\u043d\u044b\u0439\u00a0<code>Assign(partition)<\/code>\u00a0\u0435\u0441\u043b\u0438 \u0437\u0430\u0434\u0430\u043d\u00a0<code>partitionNumber<\/code>\u00a0(\u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u0431\u0435\u0437 \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u043e\u0433\u043e \u0440\u0435\u0431\u0430\u043b\u0430\u043d\u0441\u0430).<\/p>\n<p><code>LogTopicMetadata<\/code>\u00a0\u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u00a0<code>AdminClient<\/code>\u00a0\u0438 \u043b\u043e\u0433\u0438\u0440\u0443\u0435\u0442 \u0442\u043e\u043f\u043e\u043b\u043e\u0433\u0438\u044e \u0442\u043e\u043f\u0438\u043a\u0430 \u2014 \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0439, \u043a\u0442\u043e \u043b\u0438\u0434\u0435\u0440 \u043a\u0430\u0436\u0434\u043e\u0439, \u0440\u0435\u043f\u043b\u0438\u043a\u0438, ISR. \u0423\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f \u0434\u0435\u0431\u0430\u0433\u0430 \u00ab\u043f\u043e\u0447\u0435\u043c\u0443 \u043c\u0435\u043d\u044f \u043d\u0430\u0437\u043d\u0430\u0447\u0438\u043b\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 2 \u0438\u0437 6 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0439\u00bb.<\/p>\n<h4>\u041f\u043e\u043b\u043b\u0438\u043d\u0433\u2011\u0446\u0438\u043a\u043b<\/h4>\n<p><code>RunAsync<\/code>\u00a0\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 long\u2011running \u0442\u0430\u0441\u043a\u0443 \u0441 \u043f\u043e\u043b\u043b\u0438\u043d\u0433\u2011\u0446\u0438\u043a\u043b\u043e\u043c:<\/p>\n<pre><code>private async Task PollLoop(CancellationToken pollCt, CancellationToken processingCt){    while (!pollCt.IsCancellationRequested)    {        try        {            if (_options.MaxPollRecords &gt; 0)                await ProcessBatch(pollCt, processingCt);     \/\/ \u0431\u0430\u0442\u0447-\u0440\u0435\u0436\u0438\u043c            else                await ProcessSingleMessage(pollCt, processingCt);  \/\/ \u043f\u043e \u043e\u0434\u043d\u043e\u043c\u0443        }        catch (OperationCanceledException) { break; }        catch (Exception ex)        {            Logger?.LogError(ex, \"Error in Kafka poll loop for topic {Topic}\", _endpoint.TopicName);            try { await Task.Delay(1000, pollCt); } catch (OperationCanceledException) { break; }        }    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0439\u043c\u0430\u043b\u0438 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0432 \u0446\u0438\u043a\u043b\u0435 \u2014 \u0437\u0430\u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043b\u0438, \u043f\u043e\u0434\u043e\u0436\u0434\u0430\u043b\u0438 \u0441\u0435\u043a\u0443\u043d\u0434\u0443, \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043b\u0438 (\u043d\u0435 \u0443\u0431\u0438\u0432\u0430\u0435\u043c \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u0438\u0437\u2011\u0437\u0430 \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0431\u043e\u0439\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f).<\/p>\n<h4>\u041e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c<\/h4>\n<pre><code>private async Task ProcessSingleMessage(CancellationToken pollCt, CancellationToken processingCt){    var result = _consumer!.Consume(pollCt);    if (result?.Message is null) return;    var exchange = CreateExchange(result);    IncrementInflight();    try    {        \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043a\u043e\u043c\u043c\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442\u0430 \u043a\u0430\u043a \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u2014 \u043d\u0430 \u0441\u043b\u0443\u0447\u0430\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430.        var commitAction = new KafkaCommitAction(_consumer, result, Logger);        RegisterTransactedAction(exchange, $\"kafka-commit-{result.Offset.Value}\", commitAction);        await Processor.Process(exchange, processingCt);        ProcessedCount++;        \/\/ 3.2.1: \u0430\u0432\u0442\u043e-\u043a\u043e\u043c\u043c\u0438\u0442 \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0445\u0430 \u2014 \u0435\u0441\u043b\u0438 \u043e\u0444\u0444\u0441\u0435\u0442 \u043d\u0435 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b\u0430 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f (\u0444\u043b\u0430\u0433 Committed).        if (_options.EnableAutoCommit &amp;&amp; !commitAction.Committed)            await commitAction.Commit(processingCt);    }    finally    {        await exchange.DisposeAsync();        DecrementInflight();    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u0430\u0436\u043d\u0430\u044f \u0434\u0435\u0442\u0430\u043b\u044c: \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442\u00a0<code>KafkaCommitAction<\/code>\u00a0<strong>\u0434\u043e<\/strong>\u00a0\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0422\u043e \u0435\u0441\u0442\u044c \u043e\u0444\u0444\u0441\u0435\u0442 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u0441\u044f \u043d\u0435 \u0441\u0440\u0430\u0437\u0443, \u0430 \u043d\u0430 \u043a\u043e\u043c\u043c\u0438\u0442\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u2014 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043b\u044e\u0431\u044b\u043c\u0438 \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u043c\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430\u043c\u0438. \u0415\u0441\u043b\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0443\u043f\u0430\u0434\u0451\u0442 \u2014 \u043e\u0444\u0444\u0441\u0435\u0442 \u043d\u0435 \u043a\u043e\u043c\u043c\u0438\u0442\u043d\u0435\u0442\u0441\u044f, \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u0435\u0434\u0435\u0442 \u0441\u043d\u043e\u0432\u0430.<\/p>\n<p><code>KafkaCommitAction.Commit<\/code>\u00a0\u2014 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0439\u00a0<code>_consumer.Commit(result)<\/code>:<\/p>\n<pre><code>public Task Commit(CancellationToken ct = default){    _consumer.Commit(_result);   \/\/ \u2190 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043a\u043e\u043c\u043c\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442\u0430, \u041d\u0415 SendOffsetsToTransaction    return Task.CompletedTask;}public Task Rollback(CancellationToken ct = default) =&gt; Task.CompletedTask;  \/\/ \u043d\u0435 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043c \u2014 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0411\u0430\u0442\u0447\u2011\u0440\u0435\u0436\u0438\u043c<\/h4>\n<p>\u041f\u0440\u0438\u00a0<code>maxPollRecords &gt; 0<\/code>\u00a0\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u0430\u0447\u043a\u0443 \u0434\u043e\u00a0<code>maxPollRecords<\/code>\u00a0\u0448\u0442\u0443\u043a \u0438\u043b\u0438 \u0434\u043e \u0434\u0435\u0434\u043b\u0430\u0439\u043d\u0430\u00a0<code>pollTimeoutMs<\/code>, \u0438 \u043e\u0442\u0434\u0430\u0451\u0442 \u0438\u0445\u00a0<strong>\u043e\u0434\u043d\u0438\u043c<\/strong>\u00a0exchange, \u0432 \u0442\u0435\u043b\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u2014\u00a0<code>List&lt;IMessage&gt;<\/code>:<\/p>\n<pre><code>while (batch.Count &lt; _options.MaxPollRecords &amp;&amp; DateTime.UtcNow &lt; deadline &amp;&amp; !pollCt.IsCancellationRequested){    var result = _consumer!.Consume(TimeSpan.FromMilliseconds(100));    if (result?.Message is not null) batch.Add(result);}if (batch.Count == 0) return;var exchange = CreateBatchExchange(batch);   \/\/ Body = List&lt;IMessage&gt;, redbKafka.BatchSize = N\/\/ \u041a\u043e\u043c\u043c\u0438\u0442\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043e\u0444\u0444\u0441\u0435\u0442 \u0431\u0430\u0442\u0447\u0430var last = batch[^1];RegisterTransactedAction(exchange, $\"kafka-batch-commit-{last.Offset.Value}\", new KafkaCommitAction(_consumer!, last, Logger));await Processor.Process(exchange, processingCt);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u043e\u043d\u043a\u043e\u0441\u0442\u044c: \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u0441\u044f\u00a0<strong>\u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439<\/strong>\u00a0\u043e\u0444\u0444\u0441\u0435\u0442 \u0431\u0430\u0442\u0447\u0430 (Kafka\u2011\u043e\u0444\u0444\u0441\u0435\u0442\u044b \u043c\u043e\u043d\u043e\u0442\u043e\u043d\u043d\u044b \u0432 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0438, \u043a\u043e\u043c\u043c\u0438\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u043f\u043e\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u044e \u043f\u0430\u0447\u043a\u0443). \u0414\u0430\u043b\u044c\u0448\u0435 \u0432 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0435 \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c\u00a0<code>.Split(body =&gt; body)<\/code>\u00a0\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u2014 \u043d\u043e \u043a\u043e\u043c\u043c\u0438\u0442 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043e\u0434\u0438\u043d \u043d\u0430 \u0431\u0430\u0442\u0447.<\/p>\n<h4>\u041a\u0430\u043a exchange \u043d\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u043c\u0438<\/h4>\n<p><code>CreateExchange<\/code>\u00a0\u043a\u043b\u0430\u0434\u0451\u0442 \u0442\u0435\u043b\u043e (<code>byte[]<\/code>), \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442 Kafka\u2011\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 (\u0434\u0435\u043a\u043e\u0434\u0438\u0440\u0443\u044f \u043a\u0430\u043a UTF\u20118), \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u00a0<code>ContentType<\/code>, \u0438 \u043f\u0440\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435:<\/p>\n<pre><code>message.Headers[KafkaHeaders.Topic]     = result.Topic;message.Headers[KafkaHeaders.Partition] = result.Partition.Value;message.Headers[KafkaHeaders.Offset]    = result.Offset.Value;if (result.Message.Timestamp.Type != TimestampType.NotAvailable)    message.Headers[KafkaHeaders.Timestamp] = result.Message.Timestamp.UtcDateTime;if (!string.IsNullOrEmpty(result.Message.Key))    message.Headers[KafkaHeaders.Key] = result.Message.Key;var exchange = Exchange.Create(message, _endpoint.ScopeFactory);exchange.Pattern = ExchangePattern.InOnly;   \/\/ \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u2014 InOnly<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440, \u043f\u043e\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438\u0434\u0451\u0442 \u043f\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0443<\/h4>\n<p>\u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u043e \u0448\u0430\u0433\u0430\u043c\u00a0<code>ProcessSingleMessage<\/code>, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0435\u0442 \u0438 \u043f\u0440\u043e \u043a\u043e\u043c\u043c\u0438\u0442:<\/p>\n<ol>\n<li>\n<p><code>Consume(pollCt)<\/code>\u00a0\u2014 \u0437\u0430\u0431\u0440\u0430\u043b \u043e\u0434\u043d\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 (\u0432 \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u043f\u043e\u043b\u043b\u0438\u043d\u0433\u00a0<strong>\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0438\u0439<\/strong>: \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435 \u043d\u0435 \u0437\u0430\u0431\u0435\u0440\u0451\u0442\u0441\u044f, \u043f\u043e\u043a\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u043d\u0435 \u043e\u0442\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442);<\/p>\n<\/li>\n<li>\n<p><code>CreateExchange<\/code>\u00a0\u2014 \u0441\u043e\u0431\u0440\u0430\u043b exchange;<\/p>\n<\/li>\n<li>\n<p><code>IncrementInflight()<\/code>\u00a0\u2014 \u043f\u043e\u043c\u0435\u0442\u0438\u043b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043a\u0430\u043a\u00a0<strong>in\u2011flight<\/strong>\u00a0(\u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e\u00a0<code>DrainableConsumer<\/code>\u00a0\u0434\u043b\u044f graceful\u2011\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438: \u043f\u0440\u0438 \u0441\u0442\u043e\u043f\u0435 \u043e\u043d \u0434\u043e\u0436\u0434\u0451\u0442\u0441\u044f \u0432\u0441\u0435\u0445 in\u2011flight);<\/p>\n<\/li>\n<li>\n<p>\u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u00a0<code>KafkaCommitAction<\/code>\u00a0\u0432\u00a0<code>TRANSACT_ACTION<\/code>\u00a0\u2014\u00a0<strong>\u043d\u043e \u043f\u043e\u043a\u0430 \u043d\u0435 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b<\/strong>;<\/p>\n<\/li>\n<li>\n<p><code>await Processor.Process(exchange)<\/code>\u00a0\u2014 \u043f\u0440\u043e\u0433\u043d\u0430\u043b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 \u0432\u0435\u0441\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442 (\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f);<\/p>\n<\/li>\n<li>\n<p><code>ProcessedCount++<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u0435\u0441\u043b\u0438\u00a0<code>EnableAutoCommit<\/code>\u00a0(\u0434\u0435\u0444\u043e\u043b\u0442\u00a0<code>true<\/code>) \u0438 \u043e\u0444\u0444\u0441\u0435\u0442 \u0435\u0449\u0451 \u043d\u0435 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b\u0430 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u2014 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u00a0<strong>\u0438\u043d\u043b\u0430\u0439\u043d<\/strong>\u00a0\u043f\u0440\u044f\u043c\u043e \u0437\u0434\u0435\u0441\u044c;<\/p>\n<\/li>\n<li>\n<p><code>finally<\/code>:\u00a0<code>exchange.DisposeAsync()<\/code>\u00a0+\u00a0<code>DecrementInflight()<\/code>.<\/p>\n<\/li>\n<\/ol>\n<p>\u041a\u043b\u044e\u0447\u0435\u0432\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442: \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e (<code>EnableAutoCommit=true<\/code>, \u0441 3.2.1) \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u00a0<strong>\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442 \u0438\u043d\u043b\u0430\u0439\u043d \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e\u00a0<\/strong><code><strong>Process<\/strong><\/code>\u00a0\u2014 at\u2011least\u2011once, \u043a\u0430\u043a \u0438 RabbitMQ. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f\u00a0<code>KafkaCommitAction<\/code>\u00a0\u0432\u00a0<code>TRANSACT_ACTION<\/code>\u00a0\u043d\u0443\u0436\u043d\u0430 \u0434\u043b\u044f\u00a0<strong>\u0434\u0440\u0443\u0433\u043e\u0433\u043e<\/strong>\u00a0\u0441\u043b\u0443\u0447\u0430\u044f \u2014 \u043a\u043e\u0433\u0434\u0430 \u043e\u0444\u0444\u0441\u0435\u0442 \u0434\u043e\u043b\u0436\u043d\u0430 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u044c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f: \u0442\u043e\u0433\u0434\u0430\u00a0<code>.Transacted()<\/code>\u00a0\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 \u0435\u0433\u043e \u043d\u0430 \u0441\u0432\u043e\u0435\u0439 \u0433\u0440\u0430\u043d\u0438\u0446\u0435, \u0430 \u0444\u043b\u0430\u0433\u00a0<code>Committed<\/code>\u00a0\u043d\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438 \u043d\u0435 \u0434\u0430\u0451\u0442 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u0443 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u044c \u0432\u0442\u043e\u0440\u043e\u0439 \u0440\u0430\u0437 \u0438\u043d\u043b\u0430\u0439\u043d.<\/p>\n<p>\u0412 \u0431\u0430\u0442\u0447\u2011\u0440\u0435\u0436\u0438\u043c\u0435 \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435, \u0442\u043e\u043b\u044c\u043a\u043e\u00a0<code>Process<\/code>\u00a0\u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043e\u0434\u0438\u043d exchange \u0441\u00a0<code>List&lt;IMessage&gt;<\/code>\u00a0\u0432 \u0442\u0435\u043b\u0435, \u0430 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u0441\u044f (\u0438\u043d\u043b\u0430\u0439\u043d \u0438\u043b\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0435\u0439)\u00a0<strong>\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439<\/strong>\u00a0\u043e\u0444\u0444\u0441\u0435\u0442 \u0431\u0430\u0442\u0447\u0430.<\/p>\n<h4>\u041a\u0430\u043a \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u0441\u044f \u043e\u0444\u0444\u0441\u0435\u0442: \u0434\u0435\u0444\u043e\u043b\u0442 \u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c<\/h4>\n<p>\u0421 3.2.1 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0438 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u043a\u0440\u043e\u043b\u0438\u043a\u043e\u043c:\u00a0<strong>\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043b \u2014 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b<\/strong>.<\/p>\n<pre><code>\/\/ \u2705 \u0414\u0435\u0444\u043e\u043b\u0442 (EnableAutoCommit=true): \u043e\u0444\u0444\u0441\u0435\u0442 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u0441\u044f \u0438\u043d\u043b\u0430\u0439\u043d \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e Process.\/\/    \u0413\u043e\u043b\u044b\u0439 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u2014 at-least-once \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438.From(\"kafka:orders?brokers=kafka:9092&amp;groupId=w\")    .To(\"direct:process\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u043d\u0443\u0436\u0435\u043d, \u043a\u043e\u0433\u0434\u0430 \u043e\u0444\u0444\u0441\u0435\u0442 \u043d\u0430\u0434\u043e \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u044c\u00a0<strong>\u0432\u043c\u0435\u0441\u0442\u0435<\/strong>\u00a0\u0441 \u0434\u0440\u0443\u0433\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u043e\u0439 (\u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0432 Kafka\/Redis, redb\u2011\u0437\u0430\u043f\u0438\u0441\u044c) \u2014 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e \u043d\u0430 \u0433\u0440\u0430\u043d\u0438\u0446\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430. \u0422\u043e\u0433\u0434\u0430 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0448\u044c \u0430\u0432\u0442\u043e\u2011\u043a\u043e\u043c\u043c\u0438\u0442 \u0438 \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0448\u044c \u0432 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e: \u043a\u043e\u043c\u043c\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442\u0430 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0447\u0430\u0441\u0442\u044c\u044e \u043f\u0430\u0447\u043a\u0438\u00a0<code>TRANSACT_ACTION<\/code>.<\/p>\n<pre><code>\/\/ \u2705 \u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u043e: \u043e\u0444\u0444\u0441\u0435\u0442 + \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u044f\u0442\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435 \u043d\u0430 \u0433\u0440\u0430\u043d\u0438\u0446\u0435 .Transacted().\/\/    enableAutoCommit=false \u2192 \u0438\u043d\u043b\u0430\u0439\u043d-\u043a\u043e\u043c\u043c\u0438\u0442\u0430 \u043d\u0435\u0442, \u043e\u0444\u0444\u0441\u0435\u0442 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f.From(\"kafka:orders?brokers=kafka:9092&amp;groupId=w&amp;enableAutoCommit=false\")    .Transacted()        .To(\"kafka:orders.done?brokers=kafka:9092&amp;transacted=true\")    .EndTransaction();\/\/ \u2705 \u0418\u043c\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e \u2014 \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0447\u0435\u0440\u0435\u0437 \u044f\u0432\u043d\u044b\u0439 commit.From(\"kafka:orders?brokers=kafka:9092&amp;groupId=w&amp;enableAutoCommit=false\")    .BeginTransaction()    .To(\"direct:process\")    .CommitTransaction();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c\u00a0<code>enableAutoCommit=true<\/code>\u00a0(\u0434\u0435\u0444\u043e\u043b\u0442) \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u00a0<code>.Transacted()<\/code>\u00a0\u2014\u00a0<strong>\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u0438\u043c\u0435\u0435\u0442 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442<\/strong>: \u043e\u043d\u0430 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442 \u043d\u0430 \u0433\u0440\u0430\u043d\u0438\u0446\u0435, \u0430 \u0444\u043b\u0430\u0433\u00a0<code>Committed<\/code>\u00a0\u043d\u0430\u00a0<code>KafkaCommitAction<\/code>\u00a0\u043d\u0435 \u0434\u0430\u0451\u0442 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u0443 \u043f\u0440\u043e\u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u043c\u0438\u0442 \u0438\u043d\u043b\u0430\u0439\u043d. \u0422\u043e \u0435\u0441\u0442\u044c \u0432 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u043e\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0435 \u043e\u043f\u0446\u0438\u044f \u0434\u0435\u2011\u0444\u0430\u043a\u0442\u043e \u0438\u0433\u043d\u043e\u0440\u0438\u0442\u0441\u044f.<\/p>\n<blockquote>\n<p>\u041a\u0441\u0442\u0430\u0442\u0438, \u0432 \u0434\u0435\u043c\u043a\u0430\u0445\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/demos\/redb.Route.Demo\/Routes\" rel=\"noopener noreferrer nofollow\">redb.Route.Demo\/Routes<\/a>\u00a0Kafka \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\u00a0<strong>\u0442\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043a \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440<\/strong>\u00a0(<code>.WireTap(KafkaWireTap)<\/code>\u00a0\u043d\u0430 \u0442\u043e\u043f\u0438\u043a\u00a0<code>demo-audit<\/code>\u00a0\u0432\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/demos\/redb.Route.Demo\/Routes\/MainPipelineRoutes.cs\" rel=\"noopener noreferrer nofollow\"><code>MainPipelineRoutes.cs<\/code><\/a>), \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u0430 \u0442\u0430\u043c \u043d\u0435\u0442 \u2014 \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u043e\u0435\u0432\u043e\u0439 \u043f\u0430\u0442\u0442\u0435\u0440\u043d consume\u2011process\u2011produce \u0441\u043c\u043e\u0442\u0440\u0438 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u0440\u043e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438.<\/p>\n<\/blockquote>\n<hr\/>\n<h3>6. \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0438 \u0442\u0440\u0435\u0439\u0441\u0438\u043d\u0433 \u0441\u043a\u0432\u043e\u0437\u044c \u0431\u0440\u043e\u043a\u0435\u0440<\/h3>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u2014\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Kafka\/KafkaHeaders.cs\" rel=\"noopener noreferrer nofollow\"><code>KafkaHeaders<\/code><\/a>. \u041f\u0440\u0435\u0444\u0438\u043a\u0441 \u0443 \u0432\u0441\u0435\u0445 \u2014\u00a0<code>redbKafka.<\/code>:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041a\u0442\u043e \u0441\u0442\u0430\u0432\u0438\u0442<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0427\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.Topic<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0442\u043e\u043f\u0438\u043a \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.Partition<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043f\u0430\u0440\u0442\u0438\u0446\u0438\u044f<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.Offset<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043e\u0444\u0444\u0441\u0435\u0442 \u0432 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u0438<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.Timestamp<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0442\u0430\u0439\u043c\u0441\u0442\u0435\u043c\u043f \u0437\u0430\u043f\u0438\u0441\u0438<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.Key<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043a\u043b\u044e\u0447 (\u0435\u0441\u043b\u0438 \u0431\u044b\u043b)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.BatchSize<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0440\u0430\u0437\u043c\u0435\u0440 \u0431\u0430\u0442\u0447\u0430 (\u0431\u0430\u0442\u0447\u2011\u0440\u0435\u0436\u0438\u043c)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.Sent.Topic<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0442\u043e\u043f\u0438\u043a, \u043a\u0443\u0434\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u0438 (\u043f\u0440\u0438\u00a0<code>recordMetadata<\/code>)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.Sent.Partition<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043f\u0430\u0440\u0442\u0438\u0446\u0438\u044f \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.Sent.Offset<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043f\u0440\u0438\u0441\u0432\u043e\u0435\u043d\u043d\u044b\u0439 \u043e\u0444\u0444\u0441\u0435\u0442<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>redbKafka.Sent.Timestamp<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u0412 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0435 \u043a \u043d\u0438\u043c \u043e\u0431\u0440\u0430\u0449\u0430\u0435\u0448\u044c\u0441\u044f \u043a\u0430\u043a\u00a0<code>${header.redbKafka.Offset}<\/code>:<\/p>\n<pre><code>From(\"kafka:orders?brokers=kafka:9092&amp;groupId=w\")    .Log(\"offset=${header.redbKafka.Offset} partition=${header.redbKafka.Partition} key=${header.redbKafka.Key}\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u0438\u00a0<code>redbKafka.*<\/code>\u00a0\u043f\u0440\u0438 \u0440\u0435\u2011\u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438\u00a0<strong>\u043d\u0435<\/strong>\u00a0\u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0435\u0435 Kafka\u2011\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 (\u0444\u0438\u043b\u044c\u0442\u0440\u00a0<code>IsRedbHeader<\/code>\u00a0\u0432 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u0435) \u2014 \u0447\u0442\u043e\u0431\u044b \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0445\u043e\u043f\u0430 \u043d\u0435 \u0443\u0442\u0435\u043a\u0430\u043b\u0438 \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439.<\/p>\n<hr\/>\n<h3>7. \u0427\u0435\u0441\u0442\u043d\u043e \u043f\u0440\u043e\u00a0transacted=true<\/h3>\n<p>\u0412\u043e\u0442 \u043c\u0435\u0441\u0442\u043e, \u0433\u0434\u0435 \u043c\u0430\u0440\u043a\u0435\u0442\u0438\u043d\u0433 \u043b\u044e\u0431\u0438\u0442 \u0441\u043a\u0430\u0437\u0430\u0442\u044c \u00abexactly\u2011once\u00bb. \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u0434.<\/p>\n<p>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u00a0<code>transacted=true<\/code>\u00a0(\u0438\u0437\u00a0<code>BuildProducerConfig<\/code>):<\/p>\n<pre><code>if (Transacted){    config.EnableIdempotence = true;    config.Acks              = Acks.All;   \/\/ \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u0430}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0432\u0441\u0451:\u00a0<a href=\"http:\/\/transactional.id\" rel=\"noopener noreferrer nofollow\"><code>transactional.id<\/code><\/a>\u00a0\u043d\u0435 \u0441\u0442\u0430\u0432\u0438\u0442\u0441\u044f,\u00a0<code>InitTransactions<\/code>\u00a0\/\u00a0<code>BeginTransaction<\/code>\u00a0\/\u00a0<code>CommitTransaction<\/code>\u00a0\/\u00a0<code>SendOffsetsToTransaction<\/code>\u00a0\u2014 \u043d\u0438\u0433\u0434\u0435. \u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0439 API librdkafka \u043d\u0435 \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u043d.<\/p>\n<p>\u0427\u0442\u043e \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u043f\u043e \u0444\u0430\u043a\u0442\u0443:<\/p>\n<ul>\n<li>\n<p>\u00abtransacted\u00bb \u0437\u0434\u0435\u0441\u044c =\u00a0<strong>\u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440<\/strong>\u00a0(<code>EnableIdempotence=true<\/code>, \u0434\u0435\u0434\u0443\u043f \u0440\u0435\u0442\u0440\u0430\u0435\u0432 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0435\u0441\u0441\u0438\u0438) +\u00a0<strong>\u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u0430\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430<\/strong>\u00a0(\u0447\u0435\u0440\u0435\u0437\u00a0<code>KafkaSendAction<\/code>, \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439\u00a0<code>ProduceAsync<\/code>\u00a0\u043d\u0430 \u0433\u0440\u0430\u043d\u0438\u0446\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438);<\/p>\n<\/li>\n<li>\n<p>\u044d\u0442\u043e\u00a0<strong>\u041d\u0415<\/strong>\u00a0Kafka\u2011EOS \u043f\u043e\u0432\u0435\u0440\u0445 consume\u2011process\u2011produce: \u043d\u0435\u0442 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0439 Kafka\u2011\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0447\u0435\u0440\u0435\u0437\u00a0<code>SendOffsetsToTransaction<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u043a\u0440\u0430\u0448 \u0440\u043e\u0432\u043d\u043e \u043c\u0435\u0436\u0434\u0443 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u043e\u043c \u043e\u0444\u0444\u0441\u0435\u0442\u0430 \u043d\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0435 \u0434\u0430\u0441\u0442\u00a0<strong>\u043f\u043e\u0432\u0442\u043e\u0440<\/strong>\u00a0(at\u2011least\u2011once), \u0430 \u043d\u0435 exactly\u2011once.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0447\u0435\u0441\u0442\u043d\u0430\u044f \u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0430 \u2014 \u00ab\u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c + deferred\u2011commit \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0440\u043e\u0443\u0442\u0430\u00bb, \u0430 \u043d\u0435 exactly\u2011once. \u0414\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u0437\u0430\u0434\u0430\u0447 \u00ab\u043d\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u043d\u0435 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u044c \u043e\u0444\u0444\u0441\u0435\u0442 \u0440\u0430\u043d\u044c\u0448\u0435, \u0447\u0435\u043c \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0440\u0430\u0431\u043e\u0442\u0443\u00bb \u044d\u0442\u043e\u0433\u043e \u0445\u0432\u0430\u0442\u0430\u0435\u0442; \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 read\u2011process\u2011write EOS (<code>BeginTransaction<\/code>\u00a0\/\u00a0<code>SendOffsetsToTransaction<\/code>\u00a0\/\u00a0<code>CommitTransaction<\/code>) \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f, \u0438 \u0435\u0451 \u0442\u0443\u0442 \u043d\u0435\u0442.<\/p>\n<p>\u0410\u043f\u0430\u0447 \u041a\u0435\u043c\u0435\u043b, \u043a \u0441\u043b\u043e\u0432\u0443, EOS \u0447\u0435\u0440\u0435\u0437\u00a0<code>transacted()<\/code>\u00a0\u0442\u043e\u0436\u0435 \u043d\u0435 \u0434\u0430\u0451\u0442: \u0442\u0430\u043c \u0430\u0432\u0442\u043e\u2011\u043a\u043e\u043c\u043c\u0438\u0442 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e + \u0440\u0443\u0447\u043d\u043e\u0439 commit (<code>KafkaManualCommit<\/code>) + idempotent\u2011repo \u0434\u043b\u044f \u0434\u0435\u0434\u0443\u043f\u0430 \u2014 \u0442\u0430 \u0436\u0435 \u043c\u043e\u0434\u0435\u043b\u044c. \u0422\u0430\u043a \u0447\u0442\u043e \u044d\u0442\u043e \u043d\u0435 \u00ab\u043d\u0435\u0434\u043e\u2011Kafka\u00bb, \u0430 \u0440\u043e\u0432\u043d\u043e \u0442\u043e\u0442 \u0436\u0435 \u0447\u0435\u0441\u0442\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u043d, \u0447\u0442\u043e \u0438 \u0443 \u0437\u0440\u0435\u043b\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 Kafka \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d, \u043c\u043e\u0436\u043d\u043e \u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043d\u0430 \u043d\u0435\u0433\u043e EIP.<\/p>\n<hr\/>\n<h3>8. EIP #1 \u2014 Scatter\u2011Gather: \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440, \u0438 \u0440\u0430\u0437\u043e\u0441\u043b\u0430\u043b, \u0438 \u0441\u043e\u0431\u0440\u0430\u043b<\/h3>\n<p>\u041a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0438\u0439 Scatter\u2011Gather \u0438\u0437 \u043a\u043d\u0438\u0433\u0438 \u0425\u043e\u043f\u0435\/\u0412\u0443\u043b\u044c\u0444\u0430 \u2014 \u044d\u0442\u043e \u00ab\u0440\u0430\u0437\u043e\u0441\u043b\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441 N \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f\u043c \u0438 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0438\u0445 \u043e\u0442\u0432\u0435\u0442\u044b \u0432 \u043e\u0434\u0438\u043d\u00bb. \u0412 redb.Route \u044d\u0442\u043e\u00a0<strong>\u043e\u0434\u0438\u043d<\/strong>\u00a0\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Processors\/ScatterGatherProcessor.cs\" rel=\"noopener noreferrer nofollow\"><code>ScatterGatherProcessor<\/code><\/a>, \u0443 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0440\u00a0<strong>\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439<\/strong>, \u0430 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u00a0<strong>\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e<\/strong>.<\/p>\n<p>\u0421\u0430\u043c\u044b\u0439 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0432\u0438\u0434 \u2014 Kafka \u043d\u0430 \u0432\u0445\u043e\u0434\u0435, fan\u2011out \u043f\u043e \u0442\u0440\u0451\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c, \u0441\u043a\u043b\u0435\u0439\u043a\u0430, \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430:<\/p>\n<pre><code>From(\"kafka:orders.incoming?brokers=kafka:9092&amp;groupId=enricher&amp;autoOffsetReset=earliest\")    .RouteId(\"order-enrich\")    .ScatterGather(        \/\/ (\u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u043d\u043e\u0435, \u0442\u0435\u043a\u0443\u0449\u0435\u0435) \u2192 \u0441\u043a\u043b\u0435\u0435\u043d\u043d\u043e\u0435. \u0412\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u043f\u0430\u0440\u043d\u043e.        aggregationStrategy: (acc, cur) =&gt;        {            var merged = (acc.In.Headers.TryGetValue(\"merged\", out var m) ? m as string : \"\") ?? \"\";            acc.In.Headers[\"merged\"] = merged + cur.In.Body;            return acc;        },        \"http:\/\/pricing:8080\/quote\",        \"http:\/\/inventory:8080\/check\",        \"http:\/\/fraud:8080\/score\")    .To(\"kafka:orders.enriched?brokers=kafka:9092&amp;acks=All\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<blockquote>\n<p>\u2139\ufe0f \u0417\u0434\u0435\u0441\u044c \u0431\u0435\u0437 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438, \u0438 \u044d\u0442\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e: \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e (3.2.1) \u0432\u0445\u043e\u0434\u043d\u043e\u0439 \u043e\u0444\u0444\u0441\u0435\u0442 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u0441\u044f \u0438\u043d\u043b\u0430\u0439\u043d \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043d\u0443\u0436\u043d\u0430, \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u0445\u043e\u0447\u0435\u0448\u044c \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u0443\u044e \u0441\u0432\u044f\u0437\u043a\u0443 \u00ab\u043a\u043e\u043c\u043c\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442\u0430 + \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u00bb \u2014 \u0442\u043e\u0433\u0434\u0430\u00a0<code>enableAutoCommit=false<\/code>\u00a0+\u00a0<code>.Transacted()<\/code>\u00a0(\u0441\u043c. \u00a75).<\/p>\n<\/blockquote>\n<blockquote>\n<p><strong>\u0420\u0435\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u0430\u044f \u043e\u0433\u043e\u0432\u043e\u0440\u043a\u0430 \u043f\u0440\u043e Kafka.<\/strong>\u00a0Scatter\u2011Gather\u00a0<em>\u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u044b<\/em>, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0433\u043e \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0446\u0435\u043b\u0438 \u0440\u0430\u0437\u0441\u044b\u043b\u043a\u0438 \u2014\u00a0<strong>request\/reply<\/strong>\u00a0\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b: HTTP, gRPC, SQL\u2011SELECT. Kafka\u2011\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440 fire\u2011and\u2011forget, \u00ab\u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c\u00bb \u0441 \u043d\u0435\u0433\u043e \u043f\u043e \u0441\u0443\u0442\u0438 \u043d\u0435\u0447\u0435\u0433\u043e, \u043a\u0440\u043e\u043c\u0435 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 Kafka \u0437\u0434\u0435\u0441\u044c \u0436\u0438\u0432\u0451\u0442\u00a0<strong>\u043f\u043e \u043a\u0440\u0430\u044f\u043c<\/strong>: \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a (<code>From(\"kafka:...\")<\/code>) \u0438 \u0441\u0442\u043e\u043a (<code>To(\"kafka:...\")<\/code>), \u0430 fan\u2011out \u0432\u043d\u0443\u0442\u0440\u0438 \u0438\u0434\u0451\u0442 \u043f\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c. \u0422\u0430\u043a \u043e\u043d\u043e \u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u0431\u043e\u044e.<\/p>\n<\/blockquote>\n<h4>\u0427\u0442\u043e \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c: \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c<\/h4>\n<pre><code>private async Task ProcessParallel(IExchange exchange, IReadOnlyList&lt;string&gt; recipients, CancellationToken ct, CancellationToken callerCt){    var clones = new IExchange?[recipients.Count];    var maxDop = _maxDegreeOfParallelism &gt; 0 ? _maxDegreeOfParallelism : Environment.ProcessorCount;    using var semaphore = new SemaphoreSlim(maxDop);    async Task&lt;IExchange&gt; SendToRecipient(int index)    {        await semaphore.WaitAsync(ct);        try        {            var clone = exchange.Clone();                 \/\/ \u2190 Clone(): Properties \u043a\u043e\u043f\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u043d\u043e            clones[index] = clone;            var producer = GetOrCreateProducer(recipients[index]);  \/\/ \u043a\u0435\u0448 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u043e\u0432 \u043f\u043e URI            await producer.Process(clone, ct);            return clone;        }        finally        {            if (clones[index] != null)                await clones[index]!.ReleaseScopes();      \/\/ \u043e\u0442\u043f\u0443\u0441\u043a\u0430\u0435\u043c DI-\u0441\u043a\u043e\u0443\u043f\u044b \u0440\u0430\u043d\u043e, \u0442\u0435\u043b\u043e\/\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0436\u0438\u0432\u0443\u0442 \u0434\u043b\u044f \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u0438            semaphore.Release();        }    }    var tasks = Enumerable.Range(0, recipients.Count).Select(SendToRecipient).ToList();    var results = await Task.WhenAll(tasks);   \/\/ (\u0432 stopOnException; \u0432 best-effort \u2014 \u043e\u0431\u0451\u0440\u043d\u0443\u0442\u043e \u0432 try\/catch \u043d\u0430 \u0432\u0435\u0442\u043a\u0443)    \/\/ \u0410\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044f \u2014 \u0432 \u0434\u0435\u0442\u0435\u0440\u043c\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u0432, \u043d\u0435 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u0430    IExchange? aggregated = null;    foreach (var result in results)    {        if (result == null) continue;        aggregated = aggregated is null ? result : _aggregationStrategy(aggregated, result);    }    ApplyAggregation(exchange, aggregated);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0435\u0449\u0435\u0439 \u0437\u0434\u0435\u0441\u044c \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u044b:<\/p>\n<ul>\n<li>\n<p><code><strong>exchange.Clone()<\/strong><\/code>\u00a0\u2014 \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u0441\u0432\u043e\u0451\u043c \u043a\u043b\u043e\u043d\u0435.\u00a0<code>Clone()<\/code>\u00a0\u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442\u00a0<code>Properties<\/code>\u00a0<strong>\u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u043d\u043e<\/strong>\u00a0(\u043e\u0431 \u044d\u0442\u043e\u043c \u2014 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439), \u0447\u0442\u043e \u0432\u0430\u0436\u043d\u043e \u0434\u043b\u044f \u0441\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430.<\/p>\n<\/li>\n<li>\n<p><code><strong>SemaphoreSlim(maxDop)<\/strong><\/code>\u00a0\u2014 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0430.\u00a0<code>MaxDegreeOfParallelism=0<\/code>\u00a0\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442\u00a0<code>Environment.ProcessorCount<\/code>.<\/p>\n<\/li>\n<li>\n<p><strong>\u0410\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044f \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u0432.<\/strong>\u00a0\u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438\u00a0<code>fraud<\/code>\u00a0\u043e\u0442\u0432\u0435\u0442\u0438\u043b \u043f\u0435\u0440\u0432\u044b\u043c, \u0441\u043a\u043b\u0435\u0439\u043a\u0430 \u043f\u043e\u0439\u0434\u0451\u0442\u00a0<code>pricing \u2192 inventory \u2192 fraud<\/code>. \u042d\u0442\u043e \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u043e\u0441\u0442\u044c, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c\u0441\u044f.<\/p>\n<\/li>\n<li>\n<p><code><strong>ReleaseScopes()<\/strong><\/code><strong>\u00a0\u0440\u0430\u043d\u043e<\/strong>\u00a0\u2014 DI\u2011\u0441\u043a\u043e\u0443\u043f\u044b \u043a\u043b\u043e\u043d\u0430 \u043e\u0442\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438, \u0430 \u0442\u0435\u043b\u043e \u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0436\u0438\u0432\u044b\u043c\u0438 \u0434\u043b\u044f \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>\u041a\u0435\u0448 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u043e\u0432<\/strong>\u00a0\u2014\u00a0<code>GetOrCreateProducer<\/code>\u00a0\u0445\u0440\u0430\u043d\u0438\u0442\u00a0<code>ConcurrentDictionary&lt;string, Lazy&lt;ToProcessor&gt;&gt;<\/code>, \u043d\u0430\u00a0<code>DisposeAsync<\/code>\u00a0\u0432\u0441\u0435 \u043f\u043e\u0434\u043d\u044f\u0442\u044b\u0435 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u044b \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f.<\/p>\n<\/li>\n<\/ul>\n<h4>\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c<\/h4>\n<p>\u041f\u0440\u0438\u00a0<code>ParallelProcessing(false)<\/code>\u00a0\u2014 \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043e\u0434, \u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043b\u043e\u043d:<\/p>\n<pre><code>foreach (var uri in recipients){    var clone = exchange.CloneLinked();        \/\/ \u2190 CloneLinked(), \u043d\u0435 Clone()    var producer = GetOrCreateProducer(uri);    await producer.Process(clone, ct);    aggregated = aggregated is null ? clone : _aggregationStrategy(aggregated, clone);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0420\u0430\u0437\u043d\u0438\u0446\u0430\u00a0<code>Clone()<\/code>\u00a0vs\u00a0<code>CloneLinked()<\/code>\u00a0\u2014 \u043d\u0435 \u043a\u043e\u0441\u043c\u0435\u0442\u0438\u043a\u0430, \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u0432 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u0445.<\/p>\n<h4>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a: best\u2011effort vs stop\u2011on\u2011exception<\/h4>\n<p><code>StopOnException(false)<\/code>\u00a0(\u0434\u0435\u0444\u043e\u043b\u0442, best\u2011effort): \u0443\u043f\u0430\u0432\u0448\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u00a0<code>clone.Exception<\/code>\u00a0\u0438\u00a0<strong>\u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e<\/strong>\u00a0\u0438\u0434\u0451\u0442 \u0432 \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044e \u2014 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044f \u0441\u0430\u043c\u0430 \u0440\u0435\u0448\u0430\u0435\u0442, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0441 \u0432\u0435\u0442\u043a\u043e\u0439, \u0443 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u00a0<code>Exception<\/code>:<\/p>\n<pre><code>catch (Exception ex){    if (_stopOnException) throw;    clone.Exception = ex;    aggregated = aggregated is null ? clone : _aggregationStrategy(aggregated, clone);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0430\u0439\u043c\u0430\u0443\u0442 \u0432 best\u2011effort: \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e\u0435 \u0438 \u0432\u044b\u0445\u043e\u0434\u0438\u0442 (<code>break<\/code>), \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0439 \u2014 \u0432\u044b\u043a\u0438\u0434\u044b\u0432\u0430\u0435\u0442 \u043f\u0440\u043e\u0442\u0443\u0445\u0448\u0438\u0435 \u0432\u0435\u0442\u043a\u0438 (<code>null<\/code>).\u00a0<code>StopOnException(true)<\/code>\u00a0\u2014 \u043f\u0435\u0440\u0432\u0430\u044f \u0436\u0435 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f, \u0430 \u0442\u0430\u0439\u043c\u0430\u0443\u0442 \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u00a0<code>TimeoutException<\/code>:<\/p>\n<pre><code>catch (OperationCanceledException) when (_timeout &gt; TimeSpan.Zero &amp;&amp; !ct.IsCancellationRequested){    throw new TimeoutException($\"Scatter-gather timed out after {_timeout}.\");}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0412\u0441\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b<\/h4>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 \u2014\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Abstractions\/IScatterGatherDefinition.cs\" rel=\"noopener noreferrer nofollow\"><code>IScatterGatherDefinition<\/code><\/a>:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Recipients(params string[])<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0421\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a URI \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0435\u0439.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Recipients(Func&lt;IExchange, IEnumerable&lt;string&gt;&gt;)<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u2014<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0414\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u2014 \u0441\u043f\u0438\u0441\u043e\u043a \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0437 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>AggregationStrategy(...)<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u0430<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u043e\u043f\u0430\u0440\u043d\u0430\u044f\u00a0<code>(acc, cur) \u2192 merged<\/code>. \u0411\u0435\u0437 \u043d\u0435\u0451 \u2014 \u043e\u0448\u0438\u0431\u043a\u0430 \u043d\u0430 build.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>ParallelProcessing(bool)<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>true<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0438\u043b\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>MaxDegreeOfParallelism(int)<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>0<\/code>\u00a0\u2192 ProcessorCount<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u043e\u0442\u043e\u043b\u043e\u043a \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u0442\u043f\u0440\u0430\u0432\u043e\u043a.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>StopOnException(bool)<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>false<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">best\u2011effort \u0438\u043b\u0438 fail\u2011fast.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Timeout(TimeSpan)<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>Zero<\/code>\u00a0(\u043d\u0435\u0442)<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041e\u0431\u0449\u0438\u0439 \u0434\u0435\u0434\u043b\u0430\u0439\u043d.<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0444\u043b\u0435\u043d\u0442\u2011\u0432\u0438\u0434 \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u0440\u0443\u0447\u043a\u0430\u043c\u0438:<\/p>\n<pre><code>.ScatterGather(sg =&gt; sg    .Recipients(\"http:\/\/pricing:8080\/quote\", \"http:\/\/inventory:8080\/check\", \"http:\/\/fraud:8080\/score\")    .AggregationStrategy((acc, cur) =&gt; { \/* ... *\/ return acc; })    .ParallelProcessing(true)    .MaxDegreeOfParallelism(3)    .Timeout(TimeSpan.FromSeconds(2))    .StopOnException(false))<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0414\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0438<\/h4>\n<p>\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0435\u0439 \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0442\u044c \u0438\u0437 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432\u0435\u0435\u0440 \u043f\u043e \u0448\u0430\u0440\u0434\u0430\u043c, \u043d\u043e\u043c\u0435\u0440\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u0440\u0438\u0448\u043b\u0438 \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435:<\/p>\n<pre><code>.ScatterGather(sg =&gt; sg    .Recipients(e =&gt;    {        var shards = (e.In.Headers[\"shards\"] as string ?? \"\").Split(',');        return shards.Select(s =&gt; $\"http:\/\/shard-{s.Trim()}:8080\/query\");    })    .AggregationStrategy((acc, cur) =&gt; MergeJson(acc, cur)))<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u0443\u0436\u0435 \u043f\u043e\u0447\u0442\u0438 Dynamic Recipient List \u0432\u043d\u0443\u0442\u0440\u0438 Scatter\u2011Gather \u2014 \u0442\u043e, \u0440\u0430\u0434\u0438 \u0447\u0435\u0433\u043e EIP\u2011\u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u044b \u0438 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442.<\/p>\n<h4>\u0411\u043e\u0435\u0432\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440: \u043e\u0434\u0438\u043d HTTP\u2011\u0437\u0430\u043f\u0440\u043e\u0441 \u2192 \u0448\u0435\u0441\u0442\u044c \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u0439<\/h4>\n<p>\u0425\u0432\u0430\u0442\u0438\u0442 \u0441\u0438\u043d\u0442\u0435\u0442\u0438\u043a\u0438 \u2014 \u0432\u043e\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u2011\u043c\u0430\u0440\u0448\u0440\u0443\u0442 (\u0434\u0430\u0448\u0431\u043e\u0440\u0434 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u043b\u043e\u0433\u0438\u0441\u0442\u0438\u043a\u0438). \u042d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u00a0<code>POST \/api\/tsum\/routes<\/code>\u00a0\u043e\u0442\u0434\u0430\u0451\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432\u00a0<strong>\u043f\u043b\u044e\u0441<\/strong>\u00a0\u043f\u044f\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0445 \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 \u0434\u043b\u044f \u0432\u0438\u0434\u0436\u0435\u0442\u043e\u0432. \u0420\u0430\u043d\u044c\u0448\u0435 \u044d\u0442\u043e \u0431\u044b\u043b\u0438 \u0431\u044b \u0448\u0435\u0441\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a \u0411\u0414; \u0437\u0434\u0435\u0441\u044c \u2014 \u043e\u0434\u0438\u043d Scatter\u2011Gather:<\/p>\n<pre><code>From(\"http:0.0.0.0:5090\/api\/tsum\/routes?inOut=true&amp;cors=true\")    .RouteId(\"tsum-api-routes\")    .Process(Auth.ProcessAsync)                 \/\/ \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f    .ConvertBody&lt;string&gt;()    .Process(ParseAndStashFilter)               \/\/ \u0440\u0430\u0441\u043f\u0430\u0440\u0441\u0438\u043b\u0438 \u0444\u0438\u043b\u044c\u0442\u0440 \u2192 \u0432 Properties    .ScatterGather(sg =&gt; sg        .Recipients(            \"direct:\/\/routes-page\",             \/\/ \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 (\u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044f)            \"direct:\/\/routes-ownership\",        \/\/ \u0430\u0433\u0440\u0435\u0433\u0430\u0442: \u0441\u0432\u043e\u0438\/\u043d\u0430\u0451\u043c\u043d\u044b\u0435 \u00d7 \u0441\u0442\u0430\u0442\u0443\u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438            \"direct:\/\/routes-route-status\",     \/\/ \u0430\u0433\u0440\u0435\u0433\u0430\u0442: \u043f\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u0430\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432            \"direct:\/\/routes-point-status\",     \/\/ \u0430\u0433\u0440\u0435\u0433\u0430\u0442: \u043f\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u0430\u043c \u0442\u043e\u0447\u0435\u043a            \"direct:\/\/routes-territory\",        \/\/ \u0430\u0433\u0440\u0435\u0433\u0430\u0442: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0430\u0448\u0438\u043d \u043d\u0430 \u0442\u0435\u0440\u0440\u0438\u0442\u043e\u0440\u0438\u0438            \"direct:\/\/routes-departure\")        \/\/ \u0430\u0433\u0440\u0435\u0433\u0430\u0442: \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439        .AggregationStrategy(MergeRouteFragments)        .ParallelProcessing(true)        .MaxDegreeOfParallelism(4)        .StopOnException(true)        .Timeout(TimeSpan.FromSeconds(30)))    .Process(ComposeRoutesResponse);            \/\/ \u0441\u043e\u0431\u0440\u0430\u043b\u0438 \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 JSON \u0438\u0437 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u043e\u0432<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u0430\u0436\u0434\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439\u00a0<code>direct:\/\/<\/code>\u2011\u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0441\u043e\u00a0<strong>\u0441\u0432\u043e\u0438\u043c<\/strong>\u00a0redb\u2011\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a\u043b\u0430\u0434\u0451\u0442 \u0441\u0432\u043e\u0439 \u043a\u0443\u0441\u043e\u043a \u043e\u0442\u0432\u0435\u0442\u0430 \u0432\u00a0<code>frag:*<\/code>\u2011\u043f\u0440\u043e\u043f\u0435\u0440\u0442\u0438 (\u0438 \u0437\u0430\u043e\u0434\u043d\u043e \u0441\u0432\u043e\u044e \u043c\u0435\u0442\u0440\u0438\u043a\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438):<\/p>\n<pre><code>From(\"direct:\/\/routes-ownership\")    .ProcessWithRedb(async (redb, ex, ct) =&gt;    {        var sw = Stopwatch.StartNew();        var query = await BuildFilteredQuery(redb, Filter(ex));   \/\/ \u0442\u043e\u0442 \u0436\u0435 \u0444\u0438\u043b\u044c\u0442\u0440, \u0447\u0442\u043e \u0438 \u0443 \u0432\u0441\u0435\u0445 \u0432\u0435\u0442\u043e\u043a        var groups = await query            .GroupBy(r =&gt; new { r.Own, r.LoadStatus })            \/\/ \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044f \u0432 redb            .SelectAsync(g =&gt; new { g.Key.Own, g.Key.LoadStatus, Count = Agg.Count(g) });        ex.Properties[\"frag:ownership\"]      = BuildOwnershipFragment(groups);        ex.Properties[\"metric:ownershipMs\"]  = sw.ElapsedMilliseconds;    });<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0410\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0440 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u00a0<code>frag:*<\/code>\u00a0\u0438\u00a0<code>metric:*<\/code>\u00a0\u0438\u0437 \u043a\u043b\u043e\u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u0432\u0435\u0442\u043a\u0438 \u0432 \u0430\u043a\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440:<\/p>\n<pre><code>private static IExchange MergeRouteFragments(IExchange aggregated, IExchange current){    foreach (var kv in current.Properties)        if (kv.Key.StartsWith(\"frag:\") || kv.Key.StartsWith(\"metric:\"))            aggregated.Properties[kv.Key] = kv.Value;   \/\/ \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442 \u0432\u0435\u0442\u043a\u0438 \u2192 \u0432 \u043e\u0431\u0449\u0438\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442    return aggregated;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0410\u00a0<code>ComposeRoutesResponse<\/code>\u00a0\u0443\u0436\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u0438\u0437 \u0448\u0435\u0441\u0442\u0438\u00a0<code>frag:*<\/code>\u00a0\u0444\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 JSON.<\/p>\n<p><strong>\u041f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u00ab\u043a\u0440\u0430\u0439\u043d\u0435 \u0431\u044b\u0441\u0442\u0440\u043e\u00bb.<\/strong>\u00a0\u0428\u0435\u0441\u0442\u044c \u0432\u0435\u0442\u043e\u043a \u0431\u0435\u0433\u0443\u0442\u00a0<strong>\u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e<\/strong>\u00a0(\u043f\u043e\u0442\u043e\u043b\u043e\u043a 4 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e). \u041b\u0430\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430 =\u00a0<strong>\u0441\u0430\u043c\u0430\u044f \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0430\u044f \u0432\u0435\u0442\u043a\u0430<\/strong>, \u0430 \u043d\u0435 \u0441\u0443\u043c\u043c\u0430 \u0448\u0435\u0441\u0442\u0438. \u0415\u0441\u043b\u0438 \u043a\u0430\u0436\u0434\u0430\u044f \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044f ~80\u2013150 \u043c\u0441, \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u044b\u0448\u043b\u043e \u0431\u044b ~0.6\u20130.9 \u0441, \u0430 \u0447\u0435\u0440\u0435\u0437 Scatter\u2011Gather \u2014 ~150 \u043c\u0441. \u041e\u0434\u0438\u043d \u0437\u0430\u043f\u0440\u043e\u0441 \u0444\u0440\u043e\u043d\u0442\u0430 \u2192 \u043e\u0434\u0438\u043d HTTP\u2011\u0445\u043e\u043f \u2192 \u0448\u0435\u0441\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0445 redb\u2011\u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u0439 \u0440\u0430\u0437\u043e\u043c \u2192 \u043e\u0434\u0438\u043d JSON.<\/p>\n<p>\u0418 \u0442\u0443\u0442 \u0432\u0441\u0451, \u0447\u0442\u043e \u043c\u044b \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0438 \u043f\u0440\u043e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438, \u0441\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435: \u043a\u0430\u0436\u0434\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u0447\u0435\u0440\u0435\u0437\u00a0<code>ProcessWithRedb<\/code>\u00a0\u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u00a0<strong>\u0441\u0432\u043e\u0439<\/strong>\u00a0per\u2011exchange redb\u2011\u0441\u043a\u043e\u0443\u043f \u2192\u00a0<strong>\u0441\u0432\u043e\u0439<\/strong>\u00a0\u043a\u043e\u043d\u043d\u0435\u043a\u0442 (\u0441\u043c. \u00a710). \u0428\u0435\u0441\u0442\u044c \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438\u0434\u0443\u0442 \u043f\u043e \u0448\u0435\u0441\u0442\u0438 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0430\u043c, \u043d\u0435 \u0434\u0435\u043b\u044f\u0442 \u043e\u0434\u043d\u0443 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u2014 \u0438 \u043f\u043e\u044d\u0442\u043e\u043c\u0443\u00a0<strong>\u043d\u0435 \u043f\u0430\u0434\u0430\u044e\u0442<\/strong>. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e\u00a0<code>.Transacted()<\/code>\u00a0\u0442\u0443\u0442 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e: \u0432\u0435\u0442\u043a\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0438\u0442\u0430\u044e\u0442, \u0441\u043a\u043b\u0435\u0439\u043a\u0430 \u0438\u0434\u0451\u0442 \u043f\u043e\u00a0<code>frag:*<\/code>\u00a0\u0432 \u043f\u0430\u043c\u044f\u0442\u0438.\u00a0<code>StopOnException(true)<\/code>\u00a0\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u00ab\u0432\u0438\u0434\u0436\u0435\u0442 \u043d\u0435 \u043e\u0442\u0434\u0430\u0434\u0438\u043c \u043a\u0440\u0438\u0432\u044b\u043c\u00bb \u2014 \u0435\u0441\u043b\u0438 \u043b\u044e\u0431\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u0443\u043f\u0430\u043b\u0430, \u0432\u0435\u0441\u044c \u043e\u0442\u0432\u0435\u0442 \u2014 \u043e\u0448\u0438\u0431\u043a\u0430, \u0430 \u043d\u0435 \u043f\u043e\u043b\u0443\u2011\u0434\u0430\u043d\u043d\u044b\u0435.<\/p>\n<hr\/>\n<h3>9. EIP #2 \u2014 Aggregator: \u0441\u0431\u043e\u0440\u043a\u0430 \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438<\/h3>\n<p>Scatter\u2011Gather \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u044b\u00a0<strong>\u0437\u0434\u0435\u0441\u044c \u0438 \u0441\u0435\u0439\u0447\u0430\u0441<\/strong>\u00a0(\u0432\u0435\u0435\u0440 \u2192 \u0434\u0436\u043e\u0439\u043d). Aggregator \u2014 \u0434\u0440\u0443\u0433\u043e\u0439 \u043f\u0430\u0442\u0442\u0435\u0440\u043d: \u043e\u043d \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u00a0<strong>\u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438<\/strong>\u00a0\u043f\u043e correlation\u2011\u043a\u043b\u044e\u0447\u0443 \u0438 \u043e\u0442\u0434\u0430\u0451\u0442 \u0441\u043a\u043b\u0435\u0439\u043a\u0443, \u043a\u043e\u0433\u0434\u0430 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u043b \u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f.<\/p>\n<p>\u041a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Processors\/AggregatorProcessor.cs\" rel=\"noopener noreferrer nofollow\"><code>AggregatorProcessor<\/code><\/a>\u00a0\u2014 \u0447\u0435\u0442\u044b\u0440\u0435 \u0432\u0435\u0449\u0438:\u00a0<code>correlationKey<\/code>,\u00a0<code>aggregationStrategy<\/code>,\u00a0<code>completionPredicate<\/code>, target. \u042f\u0434\u0440\u043e:<\/p>\n<pre><code>public async Task Process(IExchange exchange, CancellationToken ct = default){    var key = _correlationKey(exchange);    IExchange? completed = null;    lock (_lock)    {        if (_aggregated.TryGetValue(key, out var existing))        {            var merged = _aggregationStrategy(existing, exchange);            _aggregated[key] = merged;            if (_completionPredicate(merged))     \/\/ \u0433\u043e\u0442\u043e\u0432\u0430 \u043b\u0438 \u0433\u0440\u0443\u043f\u043f\u0430?            {                completed = merged;                _aggregated.Remove(key);            }        }        else        {            _aggregated[key] = exchange;          \/\/ \u043f\u0435\u0440\u0432\u0430\u044f \u0432 \u0433\u0440\u0443\u043f\u043f\u0435            if (_completionPredicate(exchange)) { completed = exchange; _aggregated.Remove(key); }        }    }    if (completed != null)        await _target.Process(completed, ct);     \/\/ \u043d\u0430\u0440\u0443\u0436\u0443 \u0443\u0445\u043e\u0434\u044f\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u044b\u0435 \u0433\u0440\u0443\u043f\u043f\u044b}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0416\u0438\u0432\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u0434\u0435\u043c\u043e\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/demos\/redb.Route.Demo\/Routes\/EipRoutes.cs\" rel=\"noopener noreferrer nofollow\"><code>EipRoutes.cs<\/code><\/a>\u00a0\u2014 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u043f\u043e 3 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0441 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c\u00a0<code>batchId<\/code>:<\/p>\n<pre><code>From(\"timer:\/\/agg-source?period=2000&amp;repeatCount=9\")    .RouteId(\"demo-aggregator\")    .SetHeader(\"batchId\", e =&gt; $\"batch-{DateTime.UtcNow.Second % 3}\")    .SetBody(e =&gt; $\"event-{DateTime.UtcNow:ss.fff}\")    .Aggregate(        correlationKey:      e =&gt; GetHeader(e, \"batchId\") ?? \"default\",        aggregationStrategy: (oldEx, newEx) =&gt;        {            oldEx.In.Body = $\"{oldEx.In.Body} + {newEx.In.Body}\";            var count = oldEx.Properties.TryGetValue(\"agg.count\", out var c) ? (int)c! : 1;            oldEx.Properties[\"agg.count\"] = count + 1;            return oldEx;        },        completionPredicate:  e =&gt; e.Properties.TryGetValue(\"agg.count\", out var c) &amp;&amp; (int)c! &gt;= 3)    .Log(\"[AGG] \u2714 \u0421\u043e\u0431\u0440\u0430\u043b\u0438 3 \u0441\u043e\u0431\u044b\u0442\u0438\u044f: ${body}\");   \/\/ \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u043e\u0439 \u0433\u0440\u0443\u043f\u043f\u0435<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>.Aggregate(...)<\/code>\u00a0\u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 scope (<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Definitions\/AggregateDefinition.cs\" rel=\"noopener noreferrer nofollow\"><code>AggregateDefinition<\/code><\/a>) \u2014 \u0448\u0430\u0433\u0438 \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0451 (<code>.Log(...)<\/code>) \u0441\u0442\u0440\u043e\u044f\u0442\u00a0<strong>target\u2011\u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u044b\u0445 \u0433\u0440\u0443\u043f\u043f\u0430\u0445. \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0434\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u00ab\u0441\u044a\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u043c\u043e\u043b\u0447\u0430\u00bb.<\/p>\n<p>\u041d\u0430 Kafka \u044d\u0442\u043e \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u00abcollect N by key\u00bb:<\/p>\n<pre><code>From(\"kafka:order-lines?brokers=kafka:9092&amp;groupId=order-assembler\")    .Aggregate(        correlationKey:      e =&gt; e.In.Headers[\"redbKafka.Key\"]?.ToString() ?? \"x\",  \/\/ \u043a\u043b\u044e\u0447 = orderId        aggregationStrategy: (acc, cur) =&gt; AppendLine(acc, cur),        completionPredicate: e =&gt; IsOrderComplete(e))    .To(\"kafka:orders.assembled?brokers=kafka:9092&amp;acks=All\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<blockquote>\n<p>\u2139\ufe0f \u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432\u0445\u043e\u0434\u043d\u043e\u0439 \u043e\u0444\u0444\u0441\u0435\u0442 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0414\u043b\u044f \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0439 \u0441\u0432\u044f\u0437\u043a\u0438 \u00ab\u043e\u0444\u0444\u0441\u0435\u0442 + \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u0441\u043e\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u00bb \u2014\u00a0<code>enableAutoCommit=false<\/code>\u00a0+\u00a0<code>.Transacted()<\/code>.<\/p>\n<\/blockquote>\n<h4>\u0416\u0451\u0441\u0442\u043a\u043e\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043d\u0430\u0434\u043e \u043d\u0430\u0437\u0432\u0430\u0442\u044c \u0432\u0441\u043b\u0443\u0445<\/h4>\n<p>\u0425\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0433\u0440\u0443\u043f\u043f \u2014 \u043e\u0431\u044b\u0447\u043d\u044b\u0439\u00a0<code>Dictionary&lt;string, IExchange&gt;<\/code>\u00a0\u043f\u043e\u0434\u00a0<code>lock<\/code>:<\/p>\n<pre><code>private readonly Dictionary&lt;string, IExchange&gt; _aggregated = new(StringComparer.Ordinal);private readonly object _lock = new();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418\u0437 \u044d\u0442\u043e\u0433\u043e \u0441\u043b\u0435\u0434\u0443\u0435\u0442:<\/p>\n<ul>\n<li>\n<p><strong>\u0442\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0435<\/strong>\u00a0\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u2014 \u0432\u0441\u0435 \u043d\u0435\u0434\u043e\u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u044b\u0435 \u0433\u0440\u0443\u043f\u043f\u044b \u0438\u0441\u0447\u0435\u0437\u0430\u044e\u0442;<\/p>\n<\/li>\n<li>\n<p><strong>\u043d\u0435\u0442 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0430\/\u044d\u0432\u0438\u043a\u0446\u0438\u0438<\/strong>\u00a0\u0433\u0440\u0443\u043f\u043f \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f. \u0413\u0440\u0443\u043f\u043f\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0434\u043e\u0431\u0435\u0440\u0451\u0442 \u0434\u043e \u0443\u0441\u043b\u043e\u0432\u0438\u044f (\u0436\u0434\u0451\u043c 3 \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u043f\u0440\u0438\u0448\u043b\u043e 2, \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0443\u043c\u0435\u0440), \u0432\u0438\u0441\u0438\u0442 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438\u00a0<strong>\u0432\u0435\u0447\u043d\u043e<\/strong>. \u042d\u0442\u043e \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u0443\u0442\u0435\u0447\u043a\u0430, \u0435\u0441\u043b\u0438 \u043a\u043b\u044e\u0447\u0435\u0439 \u043c\u043d\u043e\u0433\u043e \u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u044e\u0442\u0441\u044f \u043e\u043d\u0438 \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u042d\u0442\u043e\u00a0<strong>\u043d\u0435<\/strong>\u00a0\u043f\u0435\u0440\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u044b\u0439 recovery\u2011\u0430\u0433\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0440 \u043a\u0430\u043a \u0432 \u00ab\u0431\u043e\u043b\u044c\u0448\u043e\u043c\u00bb Camel (\u0441 completion timeout, persistent repository, recovery). \u0414\u043b\u044f \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u00ab\u0441\u043e\u0431\u0440\u0430\u0442\u044c N \u043f\u043e \u043a\u043b\u044e\u0447\u0443 \u0438 \u044d\u043c\u0438\u0442\u043d\u0443\u0442\u044c \u043f\u0430\u0447\u043a\u0443, \u043f\u043e\u0442\u0435\u0440\u044f \u043f\u0440\u0438 \u0434\u0435\u043f\u043b\u043e\u0435 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u0430\u00bb \u2014 \u043e\u0442\u043b\u0438\u0447\u043d\u043e. \u0414\u043b\u044f \u00ab\u043a\u043e\u043f\u0438\u0442\u044c \u0441\u0443\u0442\u043a\u0438 \u0438 \u043d\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u0442\u044c \u043f\u0440\u0438 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0435\u00bb \u2014 \u043d\u0443\u0436\u0435\u043d \u043f\u0435\u0440\u0441\u0438\u0441\u0442, \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0437\u0434\u0435\u0441\u044c \u043d\u0435\u0442. \u0427\u0435\u0441\u0442\u043d\u043e. \u0415\u0441\u0442\u044c\u00a0<code>PendingGroupCount<\/code>\u00a0\u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0447\u0438\u0441\u043b\u0430 \u0432\u0438\u0441\u044f\u0449\u0438\u0445 \u0433\u0440\u0443\u043f\u043f \u2014 \u0445\u043e\u0442\u044f \u0431\u044b \u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u043a\u043e\u043f\u0438\u0442\u0441\u044f.<\/p>\n<hr\/>\n<h3>10. \u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438: \u0434\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438, \u0438 \u0432 \u044d\u0442\u043e\u043c \u0432\u0441\u044f \u0441\u043e\u043b\u044c<\/h3>\n<p>\u0412\u043e\u0442 \u043a\u043b\u044e\u0447\u0435\u0432\u0430\u044f \u0440\u0430\u0437\u0432\u0438\u043b\u043a\u0430. \u0412 redb.Route\u00a0<strong>\u0434\u0432\u0435 \u0440\u0430\u0437\u043d\u044b\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438<\/strong>, \u0438 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b \u0434\u0435\u043b\u044f\u0442\u0441\u044f \u043d\u0430 \u0434\u0432\u0430 \u043b\u0430\u0433\u0435\u0440\u044f. \u041f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u044d\u0442\u043e\u0439 \u0440\u0430\u0437\u0432\u0438\u043b\u043a\u0438 \u2014 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u043c.<\/p>\n<h4>\u041b\u0430\u0433\u0435\u0440\u044c 1: \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f (ITransactedAction) \u2014 \u041d\u0415 \u043b\u0435\u0437\u0443\u0442 \u0432\u00a0System.Transactions<\/h4>\n<p>\u0421\u044e\u0434\u0430 \u0432\u0445\u043e\u0434\u044f\u0442\u00a0<strong>\u0432\u0441\u0435 \u0431\u0440\u043e\u043a\u0435\u0440\u044b \u0438 redb<\/strong>: Kafka, Redis, RabbitMQ, AMQP, IBM MQ, Azure Service Bus \u0438 \u0441\u0430\u043c\u00a0<strong>redb<\/strong>. \u041c\u0435\u0445\u0430\u043d\u0438\u043a\u0430 \u043e\u0434\u043d\u0430 \u043d\u0430 \u0432\u0441\u0435\u0445. \u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u0442 \u00ab\u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0443\u044e\u00bb \u0440\u0430\u0431\u043e\u0442\u0443 \u0441\u0440\u0430\u0437\u0443 \u2014 \u043e\u043d \u043a\u043b\u0430\u0434\u0451\u0442\u00a0<code>ITransactedAction<\/code>\u00a0\u0432 \u043e\u0431\u0449\u0438\u0439 \u0441\u043b\u043e\u0432\u0430\u0440\u044c\u00a0<a href=\"http:\/\/exchange.Properties\" rel=\"noopener noreferrer nofollow\"><code>exchange.Properties<\/code><\/a><code>[\"TRANSACT_ACTION\"]<\/code>\u00a0(\u044d\u0442\u043e\u00a0<code>ConcurrentDictionary<\/code>) \u0441\u00a0<strong>\u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435<\/strong>\u00a0\u043a\u043b\u044e\u0447\u043e\u043c:<\/p>\n<pre><code>private static void RegisterTransactedAction(IExchange exchange, string key, ITransactedAction action){    if (!exchange.Properties.TryGetValue(\"TRANSACT_ACTION\", out var raw) ||        raw is not ConcurrentDictionary&lt;string, ITransactedAction&gt; dict)    {        dict = new ConcurrentDictionary&lt;string, ITransactedAction&gt;(StringComparer.OrdinalIgnoreCase);        exchange.Properties[\"TRANSACT_ACTION\"] = dict;    }    dict[key] = action;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u043b\u044e\u0447\u0438 \u2014\u00a0<code>kafka-send-{guid}<\/code>,\u00a0<code>redis-write-{guid}<\/code>,\u00a0<code>redb:{name}<\/code>, \u043e\u0444\u0444\u0441\u0435\u0442, deliveryTag. \u0423\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u0430: \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u0435\u0442\u043a\u0438 fan\u2011out \u043f\u0438\u0448\u0443\u0442 \u0432\u00a0<strong>\u043e\u0434\u0438\u043d \u043e\u0431\u0449\u0438\u0439<\/strong>\u00a0\u0441\u043b\u043e\u0432\u0430\u0440\u044c (<code>Clone()<\/code>\u00a0\u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442\u00a0<code>Properties<\/code>\u00a0\u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u043d\u043e \u2192 \u0441\u043b\u043e\u0432\u0430\u0440\u044c \u0443 \u0432\u0441\u0435\u0445 \u043a\u043b\u043e\u043d\u043e\u0432 \u043e\u0431\u0449\u0438\u0439), \u0438 \u0431\u0435\u0437 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0439 \u043e\u043d\u0438 \u0431\u044b \u0437\u0430\u0442\u0438\u0440\u0430\u043b\u0438 \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0430.<\/p>\n<p>\u041d\u0430 \u0433\u0440\u0430\u043d\u0438\u0446\u0435\u00a0<code>.Transacted()<\/code>\u00a0\u0437\u0430 \u0434\u0435\u043b\u043e \u0431\u0435\u0440\u0451\u0442\u0441\u044f\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Transactions\/TransactedProcessor.cs\" rel=\"noopener noreferrer nofollow\"><code>TransactedProcessor<\/code><\/a>:<\/p>\n<pre><code>public async Task Process(IExchange exchange, CancellationToken ct = default){    if (!exchange.Properties.ContainsKey(TransactActionPropertyKey))        exchange.Properties[TransactActionPropertyKey] = new ConcurrentDictionary&lt;string, ITransactedAction&gt;(...);    using var scope = _policy.CreateScope();   \/\/ System.Transactions.TransactionScope (AsyncFlow enabled)    try    {        await _inner.Process(exchange, ct);        await CommitActions(exchange, ct);      \/\/ \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043c \u0412\u0421\u0415 \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f        scope.Complete();    }    catch (Exception ex)    {        await RollbackActions(exchange, ct);    \/\/ \u043d\u0430 \u043e\u0448\u0438\u0431\u043a\u0435 \u2014 \u043e\u0442\u043a\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0432\u0441\u0435        throw;    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>CommitActions<\/code>\u00a0\u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0441\u043b\u043e\u0432\u0430\u0440\u044c\u00a0<strong>\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e<\/strong>\u00a0\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 \u043a\u0430\u0436\u0434\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u2014 \u044d\u0442\u043e \u043d\u0430\u043c \u0435\u0449\u0451 \u0430\u0443\u043a\u043d\u0435\u0442\u0441\u044f \u0432 \u0442\u0440\u0435\u0439\u0434\u2011\u043e\u0444\u0444\u0435 \u043d\u0438\u0436\u0435:<\/p>\n<pre><code>foreach (var kvp in actions)    await kvp.Value.Commit(ct);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u0438 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b\u00a0<code>Transaction.Current<\/code>\u00a0<strong>\u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u044e\u0442 \u0432\u043e\u043e\u0431\u0449\u0435<\/strong>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0439 fan\u2011out \u043f\u043e \u043d\u0438\u043c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u0435\u043d: \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u2192 \u043d\u0435\u0442 \u043a\u043e\u043b\u043b\u0438\u0437\u0438\u0439 \u0432 \u0441\u043b\u043e\u0432\u0430\u0440\u0435, \u043d\u0435\u0442 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0433\u043e enlistment \u0432 \u043e\u0434\u043d\u0443 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e\u00a0<code>System.Transactions<\/code>.<\/p>\n<p><strong>redb<\/strong>\u00a0\u0432 \u044d\u0442\u043e\u0442 \u043b\u0430\u0433\u0435\u0440\u044c \u0432\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Core\/Transactions\/RedbTransactedAction.cs\" rel=\"noopener noreferrer nofollow\"><code>RedbTransactedAction<\/code><\/a>\u00a0\u2014 \u043e\u043d \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 redb\u2011\u043d\u0430\u0442\u0438\u0432\u043d\u0443\u044e\u00a0<code>IRedbTransaction<\/code>\u00a0(\u0441\u0432\u043e\u0439\u00a0<code>BEGIN\/COMMIT<\/code>\u00a0\u043d\u0430 \u0441\u0432\u043e\u0451\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0435) \u0438 \u043a\u043b\u0430\u0434\u0451\u0442 \u0435\u0451 \u0432 \u0442\u043e\u0442 \u0436\u0435 \u0441\u043b\u043e\u0432\u0430\u0440\u044c \u043f\u043e\u0434 \u043a\u043b\u044e\u0447\u043e\u043c\u00a0<code>redb:{name}<\/code>:<\/p>\n<pre><code>public async Task Commit(CancellationToken ct = default){    if (Interlocked.Exchange(ref _completed, 1) != 0) return;  \/\/ single-use    try { if (_tx.IsActive) await _tx.CommitAsync(); }    finally { await _tx.DisposeAsync(); }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u041b\u0430\u0433\u0435\u0440\u044c 2: SQL \u2014 \u044d\u043d\u043b\u0438\u0441\u0442\u0438\u0442\u0441\u044f \u0432\u00a0System.Transactions<\/h4>\n<p>SQL\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0431\u044f\u00a0<strong>\u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0438\u0430\u043b\u044c\u043d\u043e \u0438\u043d\u0430\u0447\u0435<\/strong>\u00a0(<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Sql\/SqlProducer.cs\" rel=\"noopener noreferrer nofollow\"><code>SqlProducer<\/code><\/a>):<\/p>\n<pre><code>var connection = await factory.CreateConnectionAsync(readOnly: ..., ct);\/\/ \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c ambient TransactionScope (route-level .Transacted()), \u043a\u043e\u043d\u043d\u0435\u043a\u0442 \u0430\u0432\u0442\u043e-\u044d\u043d\u043b\u0438\u0441\u0442\u0438\u0442\u0441\u044f \u2014\/\/ \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043d\u0435 \u043d\u0443\u0436\u043d\u0430. \u0418\u043d\u0430\u0447\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e (\u043a\u0430\u043a EF SaveChanges).var hasAmbientTx = Transaction.Current != null;DbTransaction? tx = null;if (!hasAmbientTx)    tx = await connection.BeginTransactionAsync(ct);\/\/ ... \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 ...if (tx != null) await tx.CommitAsync(ct);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c:<\/p>\n<ul>\n<li>\n<p><strong>\u043d\u0435\u0442<\/strong>\u00a0ambient \u2014 \u043a\u0430\u0436\u0434\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439\u00a0<code>DbTransaction<\/code>\u00a0\u043d\u0430 \u0441\u0432\u043e\u0451\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0435 (\u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e, \u043a\u0430\u043a\u00a0<code>SaveChanges<\/code>\u00a0\u0432 EF, \u0431\u0435\u0437 \u0432\u0441\u044f\u043a\u043e\u0433\u043e\u00a0<code>?transacted=true<\/code>);<\/p>\n<\/li>\n<li>\n<p><strong>\u0435\u0441\u0442\u044c<\/strong>\u00a0ambient\u00a0<code>Transaction.Current<\/code>\u00a0(\u0435\u0433\u043e \u043e\u0442\u043a\u0440\u044b\u043b\u00a0<code>.Transacted()<\/code>\u00a0\/\u00a0<code>.BeginTransaction()<\/code>) \u2192 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442, \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u00a0<strong>\u0430\u0432\u0442\u043e\u2011\u044d\u043d\u043b\u0438\u0441\u0442\u0438\u0442\u0441\u044f<\/strong>\u00a0\u0432 ambient\u00a0<code>TransactionScope<\/code>.<\/p>\n<\/li>\n<\/ul>\n<p>SQL \u2014 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 in\u2011box \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0443\u0447\u0430\u0441\u0442\u0432\u0443\u0435\u0442 \u0432\u00a0<code>System.Transactions<\/code>. \u042d\u0442\u043e \u0438 \u0445\u043e\u0440\u043e\u0448\u043e (\u043e\u0434\u043d\u0430\u00a0<code>.Transacted()<\/code>\u00a0\u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u0432\u043e\u043a\u0440\u0443\u0433 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 SQL\u2011\u0437\u0430\u043f\u0438\u0441\u0435\u0439 = \u043e\u0434\u043d\u0430 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u0430\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f, \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e \u0434\u043b\u044f outbox\u2011write), \u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u0433\u0440\u0430\u043d\u0438\u0446.<\/p>\n<h4>\u041f\u043e\u0447\u0435\u043c\u0443 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0439 Scatter\u2011Gather \u043d\u0435 \u043f\u0430\u0434\u0430\u0435\u0442 \u043f\u043e\u0434 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0435\u0439<\/h4>\n<p>\u041b\u043e\u0433\u0438\u0447\u043d\u044b\u0439 \u0441\u0442\u0440\u0430\u0445: \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u0435\u0442\u043a\u0438 \u0431\u0435\u0433\u0443\u0442 \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043e\u0442\u043e\u043a\u0430\u0445,\u00a0<code>Transaction.Current<\/code>\u00a0\u0447\u0435\u0440\u0435\u0437\u00a0<code>ExecutionContext<\/code>\u00a0\u0442\u0435\u0447\u0451\u0442 \u0432 \u043a\u0430\u0436\u0434\u0443\u044e \u2014 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043b\u0438 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0434\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0438 \u043f\u0440\u043e\u043c\u043e\u0443\u0442\u0430 \u0432 MSDTC (\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430 PG\/Linux \u043f\u0440\u043e\u0441\u0442\u043e \u043a\u0438\u043d\u0435\u0442)?<\/p>\n<p>\u041d\u0435\u0442. \u0418 \u0432\u043e\u0442 \u043f\u043e\u0447\u0435\u043c\u0443 \u2014 \u043f\u043e \u043a\u043e\u0434\u0443:<\/p>\n<p><strong>1. \u0421\u043a\u043e\u0443\u043f\u044b \u0434\u0435\u043b\u0430\u044e\u0442\u0441\u044f per\u2011exchange.<\/strong>\u00a0\u0418\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0439 redb \u0440\u0435\u0437\u043e\u043b\u0432\u0438\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437\u00a0<code>GetRedbService(name, exchange)<\/code>\u00a0(<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route.Core\/Extensions\/RedbRouteExtensions.cs\" rel=\"noopener noreferrer nofollow\"><code>RedbRouteExtensions<\/code><\/a>), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a\u043b\u0430\u0434\u0451\u0442 DI\u2011scope \u0432\u00a0<a href=\"http:\/\/exchange.Properties\" rel=\"noopener noreferrer nofollow\"><code>exchange.Properties<\/code><\/a><code>[\"__redb_scope:{name}\"]<\/code>\u00a0\u0438 \u0442\u044f\u043d\u0435\u0442 \u0441\u0435\u0440\u0432\u0438\u0441 \u0438\u0437 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u044d\u0442\u043e\u0433\u043e \u0441\u043a\u043e\u0443\u043f\u0430:<\/p>\n<pre><code>var cacheKey = ScopeCachePrefix + cleanName;          \/\/ \"__redb_scope:orders-db\"if (exchange.Properties.TryGetValue(cacheKey, out var cached) &amp;&amp; cached is IServiceScope cachedScope)    return cachedScope.ServiceProvider.GetRequiredService&lt;IRedbService&gt;();var scope = factory.CreateScope();                    \/\/ \u043d\u0435\u0442 \u0432 \u043a\u0435\u0448\u0435 \u2014 \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0441\u0432\u043e\u0439exchange.Properties[cacheKey] = scope;return scope.ServiceProvider.GetRequiredService&lt;IRedbService&gt;();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>2.\u00a0<\/strong><code><strong>Clone()<\/strong><\/code><strong>\u00a0\u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e \u041d\u0415 \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0441\u043a\u043e\u0443\u043f\u044b.<\/strong>\u00a0\u0418\u0437\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Core\/Exchange.cs\" rel=\"noopener noreferrer nofollow\"><code>Exchange.cs<\/code><\/a>:<\/p>\n<pre><code>foreach (var kvp in _properties){    \/\/ \u0418\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0435 redb-\u0441\u043a\u043e\u0443\u043f\u044b \u2014 per-exchange; \u0440\u0435\u0431\u0451\u043d\u043e\u043a \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0441\u0432\u043e\u0439 \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438.    if (kvp.Key.StartsWith(\"__redb_scope:\", StringComparison.Ordinal))        continue;    clone._properties[kvp.Key] = kvp.Value;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u043d\u0430\u0447\u0438\u0442 \u043a\u0430\u0436\u0434\u0430\u044f \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u00a0<strong>\u0441\u0432\u043e\u0439<\/strong>\u00a0\u0441\u043a\u043e\u0443\u043f \u2192\u00a0<strong>\u0441\u0432\u043e\u0439<\/strong>\u00a0<code>IRedbService<\/code>\u00a0\u2192\u00a0<strong>\u0441\u0432\u043e\u0439 \u043a\u043e\u043d\u043d\u0435\u043a\u0442<\/strong>.<\/p>\n<p><strong>3. redb \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u044d\u043d\u043b\u0438\u0441\u0442\u0438\u0442\u0441\u044f \u0432\u00a0<\/strong><code><strong>System.Transactions<\/strong><\/code>\u00a0\u2014 \u043e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0432\u043e\u044e\u00a0<code>IRedbTransaction<\/code>, \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u0443\u044e \u0432\u00a0<code>TRANSACT_ACTION<\/code>.<\/p>\n<p>\u0418\u0442\u043e\u0433:\u00a0<strong>\u043d\u0435\u0442 \u043e\u0431\u0449\u0435\u0439\u00a0<\/strong><code><strong>System.Transactions<\/strong><\/code><strong>\u2011\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0432 \u0434\u0451\u0440\u0433\u0430\u044e\u0442 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e \u2192 \u043d\u0435\u0442 \u043f\u0440\u043e\u043c\u043e\u0443\u0442\u0430 \u0432 MSDTC, \u043d\u0435\u0442 \u00abtransaction context in use by another thread\u00bb, \u043d\u0435 \u043f\u0430\u0434\u0430\u0435\u0442.<\/strong>\u00a0\u0420\u0430\u0437\u043d\u044b\u0435 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u044b, \u043d\u0435 \u043e\u0434\u043d\u0430 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u2014 \u0438 \u0432 \u044d\u0442\u043e\u043c \u0444\u0438\u0448\u043a\u0430, \u0430 \u043d\u0435 \u0431\u0430\u0433.<\/p>\n<p><strong>\u0414\u043b\u044f SQL \u2014 \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435.<\/strong>\u00a0\u0412 Scatter\u2011Gather \u0431\u0435\u0437 \u043e\u0431\u0451\u0440\u0442\u043a\u0438\u00a0<code>.Transacted()<\/code>\u00a0\u043a\u0430\u0436\u0434\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442 \u0441\u0432\u043e\u0439 \u043a\u043e\u043d\u043d\u0435\u043a\u0442 (<code>CreateConnectionAsync<\/code>\u00a0\u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439\u00a0<code>Process<\/code>) \u0438 \u0441\u0432\u043e\u0439 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439\u00a0<code>DbTransaction<\/code>\u00a0\u2192 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0435 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u2192 \u043d\u0435 \u043f\u0430\u0434\u0430\u0435\u0442. \u0423\u043f\u0430\u0441\u0442\u044c \u043c\u043e\u0436\u043d\u043e\u00a0<strong>\u0442\u043e\u043b\u044c\u043a\u043e<\/strong>\u00a0\u0435\u0441\u043b\u0438 \u043e\u0441\u043e\u0437\u043d\u0430\u043d\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0439 fan\u2011out \u0432\u00a0<code>.Transacted()<\/code>\u00a0(ambient\u00a0<code>System.Transactions<\/code>) \u0438 \u0440\u0430\u0437\u043e\u0441\u043b\u0430\u0442\u044c \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c SQL\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0430\u043c: \u0442\u043e\u0433\u0434\u0430 \u043e\u043d\u0438 \u044d\u043d\u043b\u0438\u0441\u0442\u044f\u0442\u0441\u044f \u0432 \u043e\u0434\u043d\u0443 ambient\u2011\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e, \u0438 \u043d\u0430 PG\/Npgsql \u044d\u0442\u043e \u0443\u0435\u0434\u0435\u0442 \u0432 \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u0443\u044e \u0438 \u043a\u0438\u043d\u0435\u0442. \u042d\u0442\u043e \u0443\u0437\u043a\u0438\u0439 \u043a\u0440\u0430\u0439, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u043b\u0435\u0437\u0443\u0442 \u2014 \u0434\u043b\u044f \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u0432\/redb \u0435\u0441\u0442\u044c deferred\u2011\u043c\u043e\u0434\u0435\u043b\u044c.<\/p>\n<h4>\u0410\u00a0DependentTransactionBranch\u00a0\u0438\u0437 3.2.0 \u2014 \u044d\u0442\u043e \u043f\u0440\u043e \u0447\u0442\u043e \u0442\u043e\u0433\u0434\u0430?<\/h4>\n<p>\u0422\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 3.2.0\u2011\u0444\u0438\u043a\u0441 \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u0438 ambient\u2011\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043f\u043e \u0432\u0435\u0442\u043a\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f\u00a0<strong>\u0442\u043e\u043b\u044c\u043a\u043e \u0432\u00a0<\/strong><code><strong>Multicast<\/strong><\/code><strong>\u00a0\u0438\u00a0<\/strong><code><strong>Splitter<\/strong><\/code>\u00a0(\u043f\u043b\u044e\u0441 \u0441\u0430\u043c \u0445\u0435\u043b\u043f\u0435\u0440) \u2014 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0438\u0433\u0434\u0435. \u0412\u043e\u0442 \u0435\u0433\u043e \u044f\u0434\u0440\u043e (<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Transactions\/DependentTransactionBranch.cs\" rel=\"noopener noreferrer nofollow\"><code>DependentTransactionBranch<\/code><\/a>):<\/p>\n<pre><code>internal static async Task RunAsync(Func&lt;Task&gt; branch){    var ambient = Transaction.Current;    if (ambient is null) { await branch(); return; }   \/\/ \u043d\u0435\u0442 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u2014 \u043d\u043e\u043b\u044c \u043d\u0430\u043a\u043b\u0430\u0434\u043d\u044b\u0445    \/\/ \u0424\u043e\u0440\u043a\u0430\u0435\u043c dependent-\u043a\u043b\u043e\u043d: enlistment \u044d\u0442\u043e\u0439 \u0432\u0435\u0442\u043a\u0438 \u043f\u0440\u0438\u0432\u0430\u0442\u0435\u043d \u0435\u0451 \u043f\u043e\u0442\u043e\u043a\u0443.    var dependent = ambient.DependentClone(DependentCloneOption.BlockCommitUntilComplete);    using (var scope = new TransactionScope(dependent, TransactionScopeAsyncFlowOption.Enabled))    {        await branch();        scope.Complete();    }    dependent.Complete();   \/\/ \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u043c \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043e\u043c\u043c\u0438\u0442 (\u043e\u043d \u0436\u0434\u0430\u043b BlockCommitUntilComplete)}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u043d \u043d\u0443\u0436\u0435\u043d\u00a0<strong>\u0442\u043e\u043b\u044c\u043a\u043e<\/strong>\u00a0\u0434\u043b\u044f \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435\u00a0<strong>\u0440\u0435\u0430\u043b\u044c\u043d\u043e \u044d\u043d\u043b\u0438\u0441\u0442\u044f\u0442\u0441\u044f<\/strong>\u00a0\u0432\u00a0<code>Transaction.Current<\/code>\u00a0\u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u0434\u043b\u044f \u0441\u043b\u0443\u0447\u0430\u044f, \u043a\u043e\u0433\u0434\u0430 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u0435\u0442\u043a\u0438 Multicast\/Splitter \u0434\u0435\u043b\u0430\u044e\u0442 inline\u2011\u0440\u0430\u0431\u043e\u0442\u0443 \u043d\u0430 \u044d\u043d\u043b\u0438\u0441\u0442\u044f\u0449\u0435\u043c\u0441\u044f \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0435. Scatter\u2011Gather \u0432 \u043d\u0451\u043c\u00a0<strong>\u043d\u0435 \u043d\u0443\u0436\u0434\u0430\u0435\u0442\u0441\u044f<\/strong>: per\u2011exchange\u2011\u0441\u043a\u043e\u0443\u043f\u044b + deferred\u00a0<code>ITransactedAction<\/code>\u00a0\u0443\u0436\u0435 \u0434\u0430\u044e\u0442 \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u044e \u0431\u0435\u0437 \u0448\u0430\u0440\u0438\u043d\u0433\u0430 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0430. \u042d\u0442\u043e \u043d\u0435 \u0430\u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u044f\u2011\u043d\u0435\u0434\u043e\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u2014 \u044d\u0442\u043e \u0434\u0432\u0430 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u0430 \u043f\u043e\u0434 \u0434\u0432\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u043b\u0443\u0447\u0430\u044f.<\/p>\n<h4>\u0427\u0435\u0441\u0442\u043d\u044b\u0439 \u0442\u0440\u0435\u0439\u0434\u2011\u043e\u0444\u0444 (\u044d\u0442\u043e \u043d\u0435 \u0431\u0430\u0433, \u044d\u0442\u043e \u0434\u0438\u0437\u0430\u0439\u043d)<\/h4>\n<p><code>TransactedProcessor.CommitActions<\/code>\u00a0\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u00a0<strong>\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0432 \u0446\u0438\u043a\u043b\u0435<\/strong>, \u0430 \u0432\u0435\u0442\u043a\u0438 Scatter\u2011Gather\/Multicast \u2014 \u044d\u0442\u043e\u00a0<strong>\u0440\u0430\u0437\u043d\u044b\u0435 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u044b, \u0440\u0430\u0437\u043d\u044b\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438<\/strong>. \u0417\u043d\u0430\u0447\u0438\u0442: \u0435\u0441\u043b\u0438 \u0432\u0435\u0442\u043a\u0430 A \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b\u0430\u0441\u044c, \u0430 \u043a\u043e\u043c\u043c\u0438\u0442 \u0432\u0435\u0442\u043a\u0438 B \u0431\u0440\u043e\u0441\u0438\u043b \u2014 \u043f\u043e\u043b\u0443\u0447\u0438\u0448\u044c\u00a0<strong>\u0447\u0430\u0441\u0442\u0438\u0447\u043d\u044b\u0439 \u043a\u043e\u043c\u043c\u0438\u0442<\/strong>. \u041a\u0440\u043e\u0441\u0441\u2011\u0432\u0435\u0442\u043e\u0447\u043d\u043e\u0439 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0441\u0442\u0438 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0411\u0414 \u0437\u0434\u0435\u0441\u044c \u043d\u0435\u0442. \u042d\u0442\u043e \u043f\u043b\u0430\u0442\u0430 \u0437\u0430 \u00ab\u043d\u0435 \u043f\u0430\u0434\u0430\u0435\u0442, \u043d\u0435 \u043f\u0440\u043e\u043c\u043e\u0443\u0442\u0438\u0442\u0441\u044f \u0432 MSDTC\u00bb. \u041d\u0443\u0436\u043d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u043a\u0440\u043e\u0441\u0441\u2011\u0432\u0435\u0442\u043e\u0447\u043d\u0430\u044f \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e\u0441\u0442\u044c \u0441 enlistment \u2014 \u044d\u0442\u043e\u00a0<code>Multicast<\/code>\/<code>Splitter<\/code>\u00a0\u043f\u043e\u0434\u00a0<code>.Transacted()<\/code>\u00a0\u0441 \u0442\u0435\u043c \u0441\u0430\u043c\u044b\u043c 3.2.0\u2011\u0444\u0438\u043a\u0441\u043e\u043c, \u043d\u0435 Scatter\u2011Gather.<\/p>\n<h4>\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439<\/h4>\n<p><code>.Transacted()<\/code>\u00a0\u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Transactions\/TransactionPolicy.cs\" rel=\"noopener noreferrer nofollow\"><code>TransactionPolicy<\/code><\/a>\u00a0\u2014 \u0447\u0435\u0442\u044b\u0440\u0435 \u0433\u043e\u0442\u043e\u0432\u044b\u0445:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430<\/p>\n<\/th>\n<th>\n<p align=\"left\"><code>ScopeOption<\/code><\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Default<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>Required<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u044c\u0441\u044f \u043a ambient \u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u0443\u044e. \u0422\u0430\u0439\u043c\u0430\u0443\u0442 30\u0441,\u00a0<code>ReadCommitted<\/code>.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>RequiresNew<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>RequiresNew<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0412\u0441\u0435\u0433\u0434\u0430 \u043d\u043e\u0432\u0430\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f, ambient \u043f\u043e\u0434\u0432\u0435\u0448\u0438\u0432\u0430\u0435\u0442\u0441\u044f.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Suppress<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>Suppress<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0431\u0435\u0437 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 (ambient \u043f\u043e\u0434\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f).<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Mandatory<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">(\u043c\u0430\u0440\u043a\u0435\u0440)<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0422\u0440\u0435\u0431\u0443\u0435\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e ambient, \u0438\u043d\u0430\u0447\u0435\u00a0<code>InvalidOperationException<\/code>\u00a0\u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 scope.<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p><code>TransactionDefinition<\/code>\u00a0\u0435\u0449\u0451 \u0434\u0430\u0451\u0442 Camel\u2011parity \u0445\u0443\u043a\u0438:\u00a0<code>.Retry(attempts, delay)<\/code>\u00a0(\u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0442\u0435\u043b\u043e \u0432\u00a0<code>RetryProcessor<\/code>) \u0438\u00a0<code>.DeadLetterChannel(uri)<\/code>\u00a0(\u043d\u0430 \u043f\u0440\u043e\u0432\u0430\u043b\u0435 \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043a\u0430\u0442\u0430 \u0448\u043b\u0451\u0442 exchange \u0432 DLC). \u0422\u043e \u0435\u0441\u0442\u044c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0431\u043b\u043e\u043a \u0441 \u0440\u0435\u0442\u0440\u0430\u044f\u043c\u0438 \u0438 dead\u2011letter \u2014 \u044d\u0442\u043e:<\/p>\n<pre><code>From(\"kafka:orders?brokers=kafka:9092&amp;groupId=w\")    .Transacted()        .Retry(3, TimeSpan.FromMilliseconds(200))        .To(\"sql:INSERT INTO orders ...\")        .To(\"kafka:orders.done?brokers=kafka:9092&amp;transacted=true\")    .EndTransaction();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<h3>11. \u0420\u043e\u0434\u043d\u044f \u043f\u043e \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u0438: Splitter \u0438 Multicast<\/h3>\n<p>Scatter\u2011Gather \u2014 \u043d\u0435 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 fan\u2011out \u0432 \u0441\u0435\u043c\u0435\u0439\u0441\u0442\u0432\u0435. \u0420\u044f\u0434\u043e\u043c \u2014\u00a0<code>Splitter<\/code>\u00a0(\u0440\u0430\u0437\u0431\u0438\u0442\u044c \u0442\u0435\u043b\u043e \u043d\u0430 \u0447\u0430\u0441\u0442\u0438 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043a\u0430\u0436\u0434\u0443\u044e) \u0438\u00a0<code>Multicast<\/code>\u00a0(\u043f\u043e\u0441\u043b\u0430\u0442\u044c \u043a\u043e\u043f\u0438\u044e N \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u0430\u043c). \u0423 \u043e\u0431\u043e\u0438\u0445, \u043a\u0430\u043a \u0438 \u0443 Scatter\u2011Gather, \u0435\u0441\u0442\u044c\u00a0<strong>\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u0430\u044f<\/strong>\u00a0\u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044f, \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u00a0<code>stopOnException<\/code>. \u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u2014 \u0432 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0435 \u00ab\u0432\u0435\u0442\u043e\u043a\u00bb:<\/p>\n<ul>\n<li>\n<p><strong>Scatter\u2011Gather<\/strong>: \u0432\u0435\u0442\u043a\u0438 = \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b (URI \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u043e\u0432), \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0440\u00a0<strong>\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d<\/strong>.<\/p>\n<\/li>\n<li>\n<p><strong>Multicast<\/strong>: \u0432\u0435\u0442\u043a\u0438 = \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u044b\/\u0441\u0443\u0431\u2011\u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u044b, \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0440\u00a0<strong>\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u0435\u043d<\/strong>\u00a0(<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Processors\/MulticastProcessor.cs\" rel=\"noopener noreferrer nofollow\"><code>MulticastProcessor<\/code><\/a>).<\/p>\n<\/li>\n<li>\n<p><strong>Splitter<\/strong>: \u0432\u0435\u0442\u043a\u0438 = \u0447\u0430\u0441\u0442\u0438 \u0442\u0435\u043b\u0430 (<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Processors\/SplitterProcessor.cs\" rel=\"noopener noreferrer nofollow\"><code>SplitterProcessor<\/code><\/a>), \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043e\u0440\u00a0<strong>\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u0435\u043d<\/strong>, \u0438 \u0435\u0441\u0442\u044c \u043d\u044e\u0430\u043d\u0441\u044b Camel\u2011\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>Splitter, \u043a\u0441\u0442\u0430\u0442\u0438, \u2014 \u0441\u0430\u043c\u044b\u0439 \u00ab\u043d\u0430\u0432\u043e\u0440\u043e\u0447\u0435\u043d\u043d\u044b\u0439\u00bb \u043f\u043e \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u0438: \u0435\u0433\u043e \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044f\u00a0<code>(IExchange?, IExchange) \u2192 IExchange<\/code>\u00a0\u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f\u00a0<strong>\u0434\u0430\u0436\u0435 \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438<\/strong>\u00a0\u0441\u00a0<code>oldExchange == null<\/code>\u00a0(Camel\u2011\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 seed\/wrap), \u0438 \u0435\u0441\u0442\u044c \u0444\u043b\u0430\u0433\u0438\u00a0<code>parallelAggregate<\/code>\u00a0(\u0430\u0433\u0440\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u043b\u0430\u0439\u043d \u043f\u043e\u0434 \u043b\u043e\u043a\u043e\u043c \u0438\u0437 \u0432\u043e\u0440\u043a\u0435\u0440\u043e\u0432 \u0432\u043c\u0435\u0441\u0442\u043e \u0434\u0435\u0442\u0435\u0440\u043c\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e post\u2011pass) \u0438\u00a0<code>aggregateOnException<\/code>\u00a0(\u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0443\u043f\u0430\u0432\u0448\u0438\u0435 \u0447\u0430\u0441\u0442\u0438 \u0432 \u0430\u0433\u0440\u0435\u0433\u0430\u0442). \u0418 \u0438\u043c\u0435\u043d\u043d\u043e \u0443 Splitter\/Multicast \u0432 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u0443\u0442\u0438 \u0441\u0442\u043e\u0438\u0442 \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439\u00a0<code>DependentTransactionBranch.RunAsync<\/code>:<\/p>\n<pre><code>\/\/ MulticastProcessor.ProcessParallel \/ SplitterProcessor.ProcessParallelawait DependentTransactionBranch.RunAsync(() =&gt; _targets[idx].Process(clone, ct));<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 \u0443 \u043d\u0438\u0445 \u0435\u0441\u0442\u044c, \u0430 \u0443 Scatter\u2011Gather \u043d\u0435\u0442 \u2014 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 \u0432\u044b\u0448\u0435: Splitter\/Multicast \u0433\u043e\u043d\u044f\u044e\u0442\u00a0<strong>inline\u2011\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u044b<\/strong>\u00a0(\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u044d\u043d\u043b\u0438\u0441\u0442\u044f\u0449\u0438\u0439\u0441\u044f \u043a\u043e\u043d\u043d\u0435\u043a\u0442 \u043f\u0440\u044f\u043c\u043e \u0432 \u0432\u0435\u0442\u043a\u0435), \u0430 Scatter\u2011Gather \u0433\u043e\u043d\u044f\u0435\u0442\u00a0<strong>\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u044b \u043f\u043e URI<\/strong>\u00a0\u0441 per\u2011exchange \u0441\u043a\u043e\u0443\u043f\u0430\u043c\u0438. \u0420\u0430\u0437\u043d\u044b\u0435 \u0440\u0438\u0441\u043a\u0438 \u2014 \u0440\u0430\u0437\u043d\u044b\u0435 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u044b.<\/p>\n<p>\u041f\u0440\u043e\u0441\u0442\u043e\u0439 Multicast \u0438\u0437 \u0434\u0435\u043c\u043e:<\/p>\n<pre><code>From(\"direct:\/\/demo-multicast\")    .Multicast(new[] { \"direct:\/\/mcast-a\", \"direct:\/\/mcast-b\", \"direct:\/\/mcast-c\" }, parallelProcessing: true)    .Log(\"[MCAST] \u25c0 \u0412\u0441\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<h3>12. Saga \u2014 \u00ab\u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u0440\u0443\u0433\u0430\u044f\u00bb<\/h3>\n<p>Saga \u0432 redb.Route \u0435\u0441\u0442\u044c, \u043d\u043e \u044d\u0442\u043e\u00a0<strong>\u043d\u0435<\/strong>\u00a0MassTransit&#8217;\u043e\u0432\u0441\u043a\u0438\u0439 durable state\u2011machine. \u042d\u0442\u043e\u00a0<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Processors\/SagaProcessor.cs\" rel=\"noopener noreferrer nofollow\"><code>SagaProcessor<\/code><\/a>\u00a0\u2014 in\u2011process \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043e\u0434\u043d\u043e\u0433\u043e exchange:<\/p>\n<pre><code>public async Task Process(IExchange exchange, CancellationToken ct = default){    var completedCount = 0;    try    {        for (var i = 0; i &lt; _steps.Length; i++)        {            ct.ThrowIfCancellationRequested();            await _steps[i].Action(exchange, ct);            completedCount++;        }    }    catch (Exception ex) when (ex is not OperationCanceledException)    {        \/\/ \u041a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u044b\u0445 \u0448\u0430\u0433\u043e\u0432 \u2014 \u0432 \u041e\u0411\u0420\u0410\u0422\u041d\u041e\u041c \u043f\u043e\u0440\u044f\u0434\u043a\u0435        for (var i = completedCount - 1; i &gt;= 0; i--)        {            if (_steps[i].Compensate is null) continue;            try { await _steps[i].Compensate!(exchange, ct); }            catch (Exception compEx) { _logger?.LogError(compEx, \"Saga compensation for step {i} failed\", i); }        }        throw;    }    if (_onCompletion is not null) await _onCompletion(exchange, ct);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0430\u0434\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0439 \u043b\u043e\u0433\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0438\u00a0<strong>\u043d\u0435 \u0440\u0432\u0443\u0442<\/strong>\u00a0\u043e\u0442\u043a\u0430\u0442 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445. DSL (<a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/c%3A\/Work\/redb_code\/csharp\/redb\/redb.Route\/src\/redb.Route\/Definitions\/SagaDefinition.cs\" rel=\"noopener noreferrer nofollow\"><code>SagaDefinition<\/code><\/a>) \u2014 \u0434\u0432\u0430 \u0441\u0442\u0438\u043b\u044f, callback \u0438 fluent\u2011scope:<\/p>\n<pre><code>From(\"direct:checkout\")    .Saga(s =&gt; s        .Step(reserve, compensate: unreserve)        .Step(charge,  compensate: refund)        .Step(ship)                              \/\/ forward-only, \u0431\u0435\u0437 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0438        .OnCompletion(e =&gt; Log(\"\u0437\u0430\u043a\u0430\u0437 \u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\")));<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0416\u0451\u0441\u0442\u043a\u043e:\u00a0<strong>\u043d\u0435\u0442<\/strong>\u00a0\u043f\u0435\u0440\u0441\u0438\u0441\u0442\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f,\u00a0<strong>\u043d\u0435\u0442<\/strong>\u00a0\u043a\u043e\u0440\u0440\u0435\u043b\u044f\u0446\u0438\u0438 \u043f\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\/\u0432\u0440\u0435\u043c\u0435\u043d\u0438,\u00a0<strong>\u043d\u0435<\/strong>\u00a0\u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u0440\u0435\u0441\u0442\u0430\u0440\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430. \u042d\u0442\u043e Camel\u2011\u0441\u0442\u0438\u043b\u044c \u00abrouting slip \u0441 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0435\u0439\u00bb, \u0430 \u043d\u0435 state\u2011machine \u0441 \u043f\u0435\u0440\u0441\u0438\u0441\u0442\u043e\u043c. \u0421\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u043c\u044f. \u041c\u0435\u0442\u0440\u0438\u043a\u0438, \u043a\u0441\u0442\u0430\u0442\u0438, \u0435\u0441\u0442\u044c:\u00a0<code>SagaCompleted<\/code>\u00a0\/\u00a0<code>SagaCompensated<\/code>\u00a0\/\u00a0<code>SagaFailed<\/code>.<\/p>\n<p>\u0418 \u044d\u0442\u043e, \u043e\u043f\u044f\u0442\u044c \u0436\u0435, \u043d\u0435 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u043a \u2014 \u0434\u0440\u0443\u0433\u0430\u044f \u0448\u043a\u043e\u043b\u0430. \u041d\u0443\u0436\u0435\u043d durable\u2011saga \u0441 \u043f\u0435\u0440\u0441\u0438\u0441\u0442\u043e\u043c \u043f\u043e\u0432\u0435\u0440\u0445 Kafka \u2014 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0448\u044c \u0438\u0437\u00a0<code>Aggregator<\/code>\u00a0+ SQL\/Redis\u2011\u0441\u0442\u043e\u0440\u0430 + correlation\u2011\u043a\u043b\u044e\u0447\u0430. \u041a\u0443\u0431\u0438\u043a\u0438 \u043d\u0430 \u043c\u0435\u0441\u0442\u0435.<\/p>\n<hr\/>\n<h3>13. Outbox \u2014 \u0435\u0433\u043e \u043d\u0435\u0442, \u0438 \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e<\/h3>\n<p>Grep \u043f\u043e \u0432\u0441\u0435\u043c\u0443\u00a0<code>src<\/code>\u00a0\u043d\u0430\u00a0<code>Outbox<\/code>\u00a0\u2014\u00a0<strong>\u043d\u043e\u043b\u044c<\/strong>. \u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0433\u043e outbox \u043d\u0435\u0442. \u0418 \u044d\u0442\u043e\u00a0<strong>\u043d\u0435 \u043f\u0440\u043e\u0431\u0435\u043b<\/strong>, \u0430 \u043f\u043e\u0437\u0438\u0446\u0438\u044f: transactional outbox \u2014 \u044d\u0442\u043e \u043f\u0430\u0442\u0442\u0435\u0440\u043d, \u0430 \u043d\u0435 \u043a\u043d\u043e\u043f\u043a\u0430 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430. \u041b\u0435\u043f\u0438\u0442\u044c \u0435\u0433\u043e \u0432 \u044f\u0434\u0440\u043e \u2014 \u043e\u0432\u0435\u0440\u0438\u043d\u0436\u0438\u043d\u0438\u0440\u0438\u043d\u0433; \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442, \u0441\u043e\u0431\u0435\u0440\u0451\u0442 \u0441\u0432\u043e\u0439 \u0437\u0430 \u043f\u044f\u0442\u044c \u0441\u0442\u0440\u043e\u043a \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u0438\u043d\u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b:<\/p>\n<pre><code>\/\/ 1. \u0417\u0430\u043f\u0438\u0441\u044c: \u0431\u0438\u0437\u043d\u0435\u0441-\u0434\u0430\u043d\u043d\u044b\u0435 + \u0441\u0442\u0440\u043e\u043a\u0430 \u0432 outbox \u043f\u043e\u0434 \u043e\u0434\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0435\u0439.\/\/    \u0414\u043b\u044f SQL \u044d\u0442\u043e \u0440\u043e\u0432\u043d\u043e \u0442\u043e\u0442 \u043a\u0435\u0439\u0441, \u0433\u0434\u0435 System.Transactions-\u044d\u043d\u043b\u0438\u0441\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u0442\u0435\u0431\u044f:\/\/    \u043e\u0434\u043d\u0430 .Transacted() \u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u2192 \u043e\u0434\u0438\u043d ambient scope \u2192 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e.From(\"direct:place-order\")    .Transacted()        .To(\"sql:INSERT INTO orders(id, payload) VALUES (@id, @payload)\")        .To(\"sql:INSERT INTO outbox(id, topic, payload, sent) VALUES (@id, 'orders', @payload, false)\")    .EndTransaction();\/\/ 2. \u0414\u043e\u0441\u0442\u0430\u0432\u043a\u0430: \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u043e\u0443\u0442-\u043f\u043e\u043b\u043b\u0435\u0440 \u0447\u0438\u0442\u0430\u0435\u0442 outbox \u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u0442.From(\"sql:SELECT * FROM outbox WHERE sent = false ORDER BY id?outputType=SelectList&amp;delay=1000\")    .Split(body =&gt; (IEnumerable&lt;object?&gt;)body)        \/\/ \u043f\u043e \u0441\u0442\u0440\u043e\u043a\u0435 \u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435        .To(\"kafka:orders?brokers=kafka:9092\")        .To(\"sql:UPDATE outbox SET sent = true WHERE id = @id\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0425\u043e\u0447\u0435\u0448\u044c inbox\u2011\u043f\u0430\u0442\u0442\u0435\u0440\u043d, \u0434\u0435\u0434\u0443\u043f \u043f\u043e\u00a0<code>messageId<\/code>, TTL \u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0438, claim\u2011check \u0434\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u0445 payload&#8217;\u043e\u0432 \u2014 \u0434\u043e\u043f\u0438\u0448\u0435\u0448\u044c. \u041d\u0438\u043a\u0442\u043e \u043d\u0435 \u043d\u0430\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442 \u0441\u0432\u043e\u044e \u0441\u0445\u0435\u043c\u0443 outbox\u2011\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0438 \u0441\u0432\u043e\u044e \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\u0443 \u0440\u0435\u0442\u0440\u0430\u0435\u0432. \u042d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u00ab\u0438\u0434\u0451\u043c \u043a Camel\u00bb:\u00a0<strong>\u043c\u0435\u043d\u044c\u0448\u0435 \u0437\u0430\u0448\u0438\u0442\u043e\u0439 \u043c\u0430\u0433\u0438\u0438 \u2014 \u0431\u043e\u043b\u044c\u0448\u0435 \u044f\u0432\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438<\/strong>.<\/p>\n<hr\/>\n<h3>14. \u0418\u0442\u043e\u0433: \u0440\u0430\u0437\u043c\u0435\u043d, \u0430 \u043d\u0435 \u00ab\u043b\u0443\u0447\u0448\u0435\/\u0445\u0443\u0436\u0435\u00bb<\/h3>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\n<\/th>\n<th>\n<p align=\"left\">MassTransit<\/p>\n<\/th>\n<th>\n<p align=\"left\">redb.Route (Camel\u2011\u0448\u043a\u043e\u043b\u0430)<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>Saga<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">durable state\u2011machine, \u043f\u0435\u0440\u0441\u0438\u0441\u0442, \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u0440\u0435\u0441\u0442\u0430\u0440\u0442<\/p>\n<\/td>\n<td>\n<p align=\"left\">in\u2011process \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 exchange<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>Outbox<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0444\u0438\u0447\u0430 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0442\u0432\u043e\u0439 \u0440\u043e\u0443\u0442 \u0438\u0437 SQL\/Redis \u0437\u0430 \u043f\u044f\u0442\u044c \u0441\u0442\u0440\u043e\u043a<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0434\u0432\u0435 \u044f\u0432\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438: deferred\u00a0<code>ITransactedAction<\/code>\u00a0(\u0431\u0440\u043e\u043a\u0435\u0440\u044b+redb) \/\u00a0<code>System.Transactions<\/code>\u00a0(SQL)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>Kafka \u00abtransactional\u00bb<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">EOS \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440 + deferred\u2011commit (\u043d\u0435 EOS)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u0424\u0438\u043b\u043e\u0441\u043e\u0444\u0438\u044f<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0431\u0430\u0442\u0430\u0440\u0435\u0439\u043a\u0438 \u0432 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0442\u0435, \u0434\u0435\u043b\u0430\u0439 \u043a\u0430\u043a \u0440\u0435\u0448\u0438\u043b\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">EIP + \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b + DSL, \u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0448\u044c \u0441\u0430\u043c<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u042d\u0442\u043e \u043d\u0435 \u00abredb.Route \u043b\u0443\u0447\u0448\u0435 MassTransit\u00bb. \u042d\u0442\u043e\u00a0<strong>\u0434\u0440\u0443\u0433\u043e\u0439 \u0440\u0430\u0437\u043c\u0435\u043d<\/strong>: \u043c\u0435\u043d\u044c\u0448\u0435 \u0437\u0430\u0448\u0438\u0442\u043e\u0439 \u043c\u0430\u0433\u0438\u0438 \u2014 \u0431\u043e\u043b\u044c\u0448\u0435 \u044f\u0432\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438 \u0438\u0437 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u043e\u0432. \u0415\u0441\u043b\u0438 \u0442\u0432\u043e\u0438 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438 \u043d\u0435 \u043b\u0435\u0437\u0443\u0442 \u0432 \u0447\u0443\u0436\u0443\u044e \u043c\u043e\u0434\u0435\u043b\u044c saga\/outbox \u2014 Camel\u2011\u043f\u043e\u0434\u0445\u043e\u0434 \u0434\u0430\u0451\u0442 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0440\u043e\u0432\u043d\u043e \u0441\u0432\u043e\u0451. \u0415\u0441\u043b\u0438 \u043b\u0435\u0437\u0443\u0442 \u2014 \u043c\u043e\u0436\u0435\u0442, \u0442\u0435\u0431\u0435 \u0438 \u043d\u0435 \u043d\u0430\u0434\u043e \u043d\u0438\u043a\u0443\u0434\u0430 \u0443\u0445\u043e\u0434\u0438\u0442\u044c.<\/p>\n<p>Scatter\u2011Gather, \u043a \u0441\u043b\u043e\u0432\u0443, \u0432 \u043f\u0440\u043e\u0434\u0435 \u0443 \u043d\u0430\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u2014 \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438 \u0440\u0430\u0437\u0441\u044b\u043b\u0430\u0435\u0442, \u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442, \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e, \u0441 \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u044b\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u043e\u043c \u0441\u043a\u043b\u0435\u0439\u043a\u0438 \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\u043e\u0439. \u0420\u043e\u0432\u043d\u043e \u0442\u043e\u0442 \u0441\u043b\u0443\u0447\u0430\u0439, \u043a\u043e\u0433\u0434\u0430 EIP\u2011\u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443 \u0431\u0435\u0437 \u0435\u0434\u0438\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u0438 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430.<\/p>\n<p>\u0412\u0435\u0440\u0441\u0438\u044f \u0432 \u043f\u0440\u043e\u0434\u0435 \u2014\u00a0<strong>3.2.0<\/strong>,\u00a0<a href=\"https:\/\/github.com\/redbase-app\/redb-route\/blob\/main\/CHANGELOG.md\" rel=\"noopener noreferrer nofollow\">CHANGELOG \u0437\u0434\u0435\u0441\u044c<\/a>. \u041a\u0443\u0434\u0430 \u043d\u0430\u0441\u0442\u0443\u043f\u0430\u043b\u0438, \u0447\u0442\u043e \u043f\u0435\u0440\u0435\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c (\u043f\u0440\u0438\u0432\u0435\u0442, transacted\u2011Kafka), \u043a\u0430\u043a\u0438\u0435 EIP \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u0434\u0430\u043b\u044c\u0448\u0435 \u2014 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438, \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u043e \u043a\u043e\u0434\u0443.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1054148\/\">https:\/\/habr.com\/ru\/articles\/1054148\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>redb kafka connector\u0421\u0435\u0440\u0438\u044f: redb ecosystem \/ redb.Route deep-dive\u041e\u0447\u0435\u0440\u0435\u0434\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0438\u0437 \u0446\u0438\u043a\u043b\u0430 \u043f\u0440\u043e\u00a0redb.Route\u00a0\u2014 \u043d\u0430\u0448 Apache Camel \u043f\u043e\u0434 .NET. \u0415\u0441\u043b\u0438 \u0432\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043b\u0438\u0441\u044c, \u0432\u043e\u0442 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435 \u043d\u0430 \u0425\u0430\u0431\u0440\u0435:redb.Route \u2014 Apache Camel \u0434\u043b\u044f .NET, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0432\u044b\u0445\u043e\u0434\u0430 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043d\u0435 \u0431\u044b\u043b\u043e\u00a0\u2014 \u0441 \u0447\u0435\u0433\u043e \u0432\u0441\u0451 \u043d\u0430\u0447\u0430\u043b\u043e\u0441\u044c;redb.Route \u0438\u0437\u043d\u0443\u0442\u0440\u0438: \u0447\u0435\u0442\u044b\u0440\u0435 in\u2011memory \u043a\u0430\u043d\u0430\u043b\u0430 \u0438 Exchange, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0445 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442;redb.Route 3.0.1 \u2014 \u043f\u043b\u043e\u0441\u043a\u0430\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u043f\u043e DSL, \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433 CRTP \u0438 \u0442\u0438\u0445\u0438\u0439 null;Apache Camel \u043f\u043e\u0434 .NET, \u0440\u0430\u0437\u0431\u043e\u0440 \u043f\u043e \u043a\u043e\u0441\u0442\u043e\u0447\u043a\u0430\u043c: HTTP\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0431\u0435\u0437 ASP.NET MVC + \u043f\u0430\u0442\u0442\u0435\u0440\u043d Content\u2011Based Router\u00a0\u2014 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f \u00abEIP + \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u00bb.\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0437\u0430\u0445\u043e\u0434\u0438\u043c \u0441\u00a0Kafka\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430\u00a0\u2014 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u043c \u0435\u0433\u043e \u043f\u043e \u043a\u043e\u0441\u0442\u043e\u0447\u043a\u0430\u043c, \u043a\u0430\u043a \u0434\u0435\u043b\u0430\u043b\u0438 \u0441 HTTP, \u2014 \u0430 \u043f\u043e\u0442\u043e\u043c \u0441\u0430\u0436\u0430\u0435\u043c \u043d\u0430 \u043d\u0435\u0433\u043e \u0434\u0432\u0430 EIP\u2011\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430:\u00a0Scatter\u2011Gather\u00a0\u0438\u00a0Aggregator. \u0418 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u2014 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u043c \u0442\u043e, \u043e \u0447\u0451\u043c \u0432 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430\u0445 \u043c\u043e\u043b\u0447\u0430\u0442:\u00a0\u043a\u0430\u043a \u044d\u0442\u043e \u0436\u0438\u0432\u0451\u0442 \u043f\u043e\u0434 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u043c\u0438. \u0417\u0430\u043e\u0434\u043d\u043e \u0432\u044b\u0448\u0435\u043b\u00a03.2.0.\u0421\u0440\u0430\u0437\u0443 \u0441\u043f\u043e\u0439\u043b\u0435\u0440\u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u00abexactly\u2011once \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438\u00bb \u0432 Kafka \u0437\u0434\u0435\u0441\u044c \u043d\u0435\u0442 \u2014 \u0438 \u043d\u0438\u0436\u0435 \u043f\u043e \u043a\u043e\u0434\u0443 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u043d\u043e, \u043f\u043e\u0447\u0435\u043c\u0443 \u0438\u043c\u0435\u043d\u043d\u043e. \u0427\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0430 \u0447\u0442\u043e \u043d\u0435\u0442 \u2014 \u043f\u043e \u0444\u0430\u043a\u0442\u0443, \u0431\u0435\u0437 \u043e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0439.\u041e\u0433\u043b\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0417\u0430\u0447\u0435\u043c \u0443\u0445\u043e\u0434\u0438\u0442\u044c \u043e\u0442 MassTransit\u0427\u0442\u043e \u043f\u0440\u0438\u0435\u0445\u0430\u043b\u043e \u0432 3.2.0Kafka\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440: \u0430\u043d\u0430\u0442\u043e\u043c\u0438\u044f URI \u0438 \u0444\u0430\u0431\u0440\u0438\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439Kafka\u2011\u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440: \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430\u00a0.To(&#171;kafka:&#8230;&#187;)Kafka\u2011\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440: \u043f\u043e\u043b\u043b\u0438\u043d\u0433, \u0431\u0430\u0442\u0447\u0438, \u0440\u0435\u0431\u0430\u043b\u0430\u043d\u0441\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0438 \u0442\u0440\u0435\u0439\u0441\u0438\u043d\u0433 \u0441\u043a\u0432\u043e\u0437\u044c \u0431\u0440\u043e\u043a\u0435\u0440\u0427\u0435\u0441\u0442\u043d\u043e \u043f\u0440\u043e\u00a0transacted=trueEIP #1 \u2014 Scatter\u2011Gather: \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440, \u0438 \u0440\u0430\u0437\u043e\u0441\u043b\u0430\u043b, \u0438 \u0441\u043e\u0431\u0440\u0430\u043bEIP #2 \u2014 Aggregator: \u0441\u0431\u043e\u0440\u043a\u0430 \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438: \u0434\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438, \u0438 \u0432 \u044d\u0442\u043e\u043c \u0432\u0441\u044f \u0441\u043e\u043b\u044c\u0420\u043e\u0434\u043d\u044f \u043f\u043e \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u0438: Splitter \u0438 MulticastSaga \u2014 \u00ab\u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u0440\u0443\u0433\u0430\u044f\u00bbOutbox \u2014 \u0435\u0433\u043e \u043d\u0435\u0442, \u0438 \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0418\u0442\u043e\u0433: \u0440\u0430\u0437\u043c\u0435\u043d, \u0430 \u043d\u0435 \u00ab\u043b\u0443\u0447\u0448\u0435\/\u0445\u0443\u0436\u0435\u00bb1. \u0417\u0430\u0447\u0435\u043c \u0443\u0445\u043e\u0434\u0438\u0442\u044c \u043e\u0442 MassTransitMassTransit \u2014 \u043e\u0442\u043b\u0438\u0447\u043d\u0430\u044f \u0448\u0442\u0443\u043a\u0430, \u0438 \u044f \u043d\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0441\u044c \u0435\u0451 \u0445\u043e\u0440\u043e\u043d\u0438\u0442\u044c. \u041d\u043e \u044d\u0442\u043e\u00a0opinionated\u2011\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a: durable saga \u043a\u0430\u043a state\u2011machine, transactional outbox, retry\u2011\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u2014 \u0432\u0441\u0451 \u0437\u0430\u0448\u0438\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u044c, \u0438 \u0442\u044b \u0436\u0438\u0432\u0451\u0448\u044c \u043f\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u0430\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430. \u041a\u043e\u0433\u0434\u0430 \u0442\u0432\u043e\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u0435\u0433\u043e \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u0439 \u043c\u0438\u0440\u0430 \u2014 \u044d\u0442\u043e \u043a\u0430\u0439\u0444, \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u00ab\u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438\u00bb. \u041a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u2014 \u0442\u044b \u0432\u043e\u044e\u0435\u0448\u044c \u0441 \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0435\u0439, \u043b\u0435\u0437\u0435\u0448\u044c \u0432 \u043a\u0438\u0448\u043a\u0438 \u0438 \u043f\u0438\u0448\u0435\u0448\u044c \u043a\u043e\u0441\u0442\u044b\u043b\u0438 \u043f\u043e\u0432\u0435\u0440\u0445 \u0447\u0443\u0436\u0438\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u0439.Apache Camel \u2014 \u0434\u0440\u0443\u0433\u0430\u044f \u0448\u043a\u043e\u043b\u0430. \u0412 \u0444\u0443\u043d\u0434\u0430\u043c\u0435\u043d\u0442\u0435:\u00a0EIP\u2011\u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u044b + \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b + DSL. \u0422\u0435\u0431\u0435 \u043d\u0438\u043a\u0442\u043e \u043d\u0435 \u00ab\u0434\u0430\u0440\u0438\u0442\u00bb outbox \u043a\u0430\u043a \u0444\u0438\u0447\u0443 \u2014 \u0442\u0435\u0431\u0435 \u0434\u0430\u044e\u0442 Splitter, Aggregator, Scatter\u2011Gather, \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b, \u0438 \u0442\u044b\u00a0\u0441\u0430\u043c \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0448\u044c\u00a0\u0440\u043e\u0432\u043d\u043e \u0442\u043e, \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434 \u0442\u0432\u043e\u0438 \u0438\u043d\u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b. \u041c\u0435\u043d\u044c\u0448\u0435 \u043c\u0430\u0433\u0438\u0438 \u2014 \u0431\u043e\u043b\u044c\u0448\u0435 \u044f\u0432\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438 \u0438\u0437 \u043a\u0443\u0431\u0438\u043a\u043e\u0432.redb.Route \u0441\u0442\u043e\u0438\u0442 \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u0448\u043a\u043e\u043b\u0435. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 \u2014 \u00ab\u0438\u0434\u0451\u043c \u043a Apache Camel\u00bb: \u043d\u0435 \u00ab\u0443 \u043d\u0430\u0441 \u0432\u0441\u0451 \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438\u00bb, \u0430 \u00ab\u0443 \u043d\u0430\u0441 \u043a\u0443\u0431\u0438\u043a\u0438, \u0438 \u043e\u043d\u0438 \u0441\u0442\u044b\u043a\u0443\u044e\u0442\u0441\u044f \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u043e\u00bb. \u0414\u0430\u043b\u044c\u0448\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0434\u043d\u043e, \u0447\u0435\u043c \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e\u0442 \u0440\u0430\u0437\u043c\u0435\u043d \u043e\u043f\u043b\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438 \u0447\u0442\u043e \u0432\u0437\u0430\u043c\u0435\u043d \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0448\u044c.2. \u0427\u0442\u043e \u043f\u0440\u0438\u0435\u0445\u0430\u043b\u043e \u0432 3.2.0\u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u2014 \u0432\u00a0CHANGELOG. \u0414\u043b\u044f \u043d\u0430\u0448\u0435\u0439 \u0442\u0435\u043c\u044b \u0432\u0430\u0436\u043d\u044b \u0434\u0432\u0430 \u043f\u0443\u043d\u043a\u0442\u0430:\u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0435\u00a0Splitter\u00a0\/\u00a0Multicast\u00a0\u0442\u0435\u043f\u0435\u0440\u044c \u0438\u0437\u043e\u043b\u0438\u0440\u0443\u044e\u0442 ambient\u2011\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u043f\u043e \u0432\u0435\u0442\u043a\u0435.\u00a0\u041a\u0430\u0436\u0434\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u0432\u043e\u0439\u00a0DependentTransaction.DependentClone(BlockCommitUntilComplete); \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043e\u043c\u043c\u0438\u0442 \u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0432\u0435\u0442\u043e\u043a. \u0420\u0430\u043d\u044c\u0448\u0435 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u0435\u0442\u043a\u0438 \u0448\u0430\u0440\u0438\u043b\u0438 \u043e\u0434\u0438\u043d\u00a0Transaction.Current, \u0430\u00a0System.Transactions\u00a0\u0437\u0430\u043f\u0440\u0435\u0449\u0430\u0435\u0442 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043f\u043e\u0442\u043e\u043a\u043e\u0432. \u041d\u0438\u0436\u0435 \u043c\u044b \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u044d\u0442\u043e\u0442 \u0444\u0438\u043a\u0441 \u043f\u043e \u043a\u043e\u0434\u0443 \u0438 \u043f\u043e\u0439\u043c\u0451\u043c,\u00a0\u043f\u043e\u0447\u0435\u043c\u0443 Scatter\u2011Gather \u0432 \u043d\u0451\u043c \u043d\u0435 \u043d\u0443\u0436\u0434\u0430\u043b\u0441\u044f.Throttle\u00a0\u043f\u043e\u043b\u0443\u0447\u0438\u043b RFC 6585\u2011\u0440\u0435\u0436\u0438\u043c\u00a0.RejectOnOverflow()\u00a0(429 +\u00a0Retry\u2011After). \u041a \u043d\u0430\u0448\u0435\u0439 \u0442\u0435\u043c\u0435 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u043d\u0435 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0441\u044f, \u043d\u043e \u0435\u0441\u043b\u0438 \u0441\u0442\u0440\u043e\u0438\u0442\u0435 HTTP\u2011\u0444\u0430\u0441\u0430\u0434 \u043f\u0435\u0440\u0435\u0434 \u0432\u0441\u0435\u043c \u044d\u0442\u0438\u043c \u2014 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f.\u0412\u0441\u0435 \u043f\u0430\u043a\u0435\u0442\u044b\u00a0redb.Route.*\u00a0\u0432\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435:\u00a03.2.0.3. Kafka\u2011\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440: \u0430\u043d\u0430\u0442\u043e\u043c\u0438\u044f URI \u0438 \u0444\u0430\u0431\u0440\u0438\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439\u041d\u0430\u0447\u043d\u0451\u043c, \u043a\u0430\u043a \u043f\u0440\u043e\u0441\u0438\u043b\u0438, \u0441 Kafka. \u0418 \u043d\u0430\u0447\u043d\u0451\u043c \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0435\u0441\u0442\u044c \u2014\u00a0\u0441\u0442\u0440\u043e\u043a\u043e\u0432\u043e\u0433\u043e URI \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430. \u0412\u043e\u0442 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u0438 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440:\/\/ \u041a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440: \u0447\u0438\u0442\u0430\u0435\u043c \u0442\u043e\u043f\u0438\u043a orders, \u0433\u0440\u0443\u043f\u043f\u0430 order-workers, \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043d\u0430\u0447\u0430\u043b\u0430From(&#171;kafka:orders?brokers=kafka:9092&amp;groupId=order-workers&amp;autoOffsetReset=earliest&#187;)    .Log(&#171;\u041f\u043e\u043b\u0443\u0447\u0438\u043b\u0438: ${body}&#187;)    .To(&#171;direct:process&#187;);\/\/ \u041f\u0440\u043e\u0434\u044e\u0441\u0435\u0440: \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u043c \u0432 \u0442\u043e\u043f\u0438\u043a notifications \u0441 acks=allFrom(&#171;direct:notify&#187;)    .To(&#171;kafka:notifications?brokers=kafka:9092&amp;acks=All&#187;);\u0421\u0445\u0435\u043c\u0430 URI \u2014\u00a0kafka:&lt;topic&gt;?&lt;query&gt;. \u0418\u043c\u044f \u0442\u043e\u043f\u0438\u043a\u0430 \u0431\u0435\u0440\u0451\u0442\u0441\u044f \u0438\u0437 \u043f\u0443\u0442\u0438 (KafkaEndpoint):public KafkaEndpoint(EndpointUri uri, KafkaComponent component, KafkaEndpointOptions options)    : base(uri, component, options){    TopicName = uri.Path;                       \/\/ \u2190 \u0442\u043e\u043f\u0438\u043a = \u043f\u0443\u0442\u044c URI    \/\/ \u0415\u0441\u043b\u0438 \u0432 URI \u0443\u043a\u0430\u0437\u0430\u043d connectionFactory=&lt;\u0438\u043c\u044f&gt; \u2014 \u0440\u0435\u0437\u043e\u043b\u0432\u0438\u043c \u0444\u0430\u0431\u0440\u0438\u043a\u0443 \u0438\u0437 \u0440\u0435\u0435\u0441\u0442\u0440\u0430    if (!string.IsNullOrEmpty(options.ConnectionFactory) &amp;&amp; component.Context is not null)        ResolvedFactory = component.Context.GetFromRegistry&lt;KafkaConnectionFactory&gt;(options.ConnectionFactory);    \/\/ brokers \u0432 URI \u043d\u0435\u0442, \u043d\u043e \u0444\u0430\u0431\u0440\u0438\u043a\u0430 \u0438\u0445 \u0437\u043d\u0430\u0435\u0442 \u2014 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c    if (string.IsNullOrWhiteSpace(options.Brokers) &amp;&amp; ResolvedFactory is not null)        options.Brokers = ResolvedFactory.Brokers;}\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0448\u0442\u0440\u0438\u0445: \u043d\u0430 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u0435\u00a0groupId\u00a0\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d, \u0438 \u0435\u0441\u043b\u0438 \u0435\u0433\u043e \u0437\u0430\u0431\u044b\u0442\u044c \u2014 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 \u043d\u0435 \u0434\u0430\u0441\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440:public override IConsumer CreateConsumer(IProcessor processor){    if (string.IsNullOrWhiteSpace(Options.GroupId))        throw new InvalidOperationException(            $&#187;The &#8216;groupId&#8217; parameter is required for Kafka consumer on topic &#8216;{TopicName}&#8217;.&#187;);    return new KafkaConsumer(this, processor, Options);}\u0424\u0430\u0431\u0440\u0438\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u2014 \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041a\u043e\u0433\u0434\u0430 \u0443 \u0442\u0435\u0431\u044f \u0434\u0432\u0430\u0434\u0446\u0430\u0442\u044c Kafka\u2011\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432 \u043d\u0430 \u043e\u0434\u0438\u043d \u043a\u043b\u0430\u0441\u0442\u0435\u0440, \u0440\u0430\u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u00a0brokers=&#8230;&amp;securityProtocol=&#8230;&amp;saslMechanism=&#8230;\u00a0\u0432 \u043a\u0430\u0436\u0434\u043e\u043c URI \u2014 \u0441\u0430\u043c\u043e\u0443\u0431\u0438\u0439\u0441\u0442\u0432\u043e. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0435\u0441\u0442\u044c\u00a0KafkaConnectionFactory: \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0448\u044c \u0435\u0451 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435 \u043f\u043e\u0434 \u0438\u043c\u0435\u043d\u0435\u043c \u0438 \u0441\u0441\u044b\u043b\u0430\u0435\u0448\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437\u00a0connectionFactory=\u0438\u043c\u044f.\u041f\u043e\u043b\u043d\u044b\u0439 \u0435\u0451 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u2011\u0441\u0435\u0442 (\u0432\u0441\u0451 \u0438\u0437 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0430, \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0432\u044b\u0434\u0443\u043c\u0430\u043d\u043e):\u0413\u0440\u0443\u043f\u043f\u0430\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u0414\u0435\u0444\u043e\u043b\u0442\u0411\u0440\u043e\u043a\u0435\u0440\u044bBrokerslocalhost:9092\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044cSecurityProtocol,\u00a0SaslMechanism,\u00a0SaslUsername,\u00a0SaslPasswordPlaintextSSL\/TLSSslCaLocation,\u00a0SslCertificateLocation,\u00a0SslKeyLocation,\u00a0SslKeyPassword,\u00a0SslEndpointIdentificationAlgorithm\u2014\u041f\u0440\u043e\u0434\u044e\u0441\u0435\u0440Acks,\u00a0RetriesLeader,\u00a03\u041a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440GroupId,\u00a0AutoOffsetReset\u2014,\u00a0Latest\u0422\u044e\u043d\u0438\u043d\u0433 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u0430GroupInstanceId,\u00a0SessionTimeoutMs,\u00a0HeartbeatIntervalMs,\u00a0MaxPollIntervalMs,\u00a0PartitionAssignmentStrategy,\u00a0IsolationLevel\u2014\u0422\u044e\u043d\u0438\u043d\u0433 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u0430LingerMs,\u00a0BatchSize,\u00a0CompressionType,\u00a0MessageTimeoutMs\u2014ReconnectReconnectBackoffMs,\u00a0ReconnectBackoffMaxMs\u2014AdvancedClientId,\u00a0RequestTimeoutMs,\u00a0MetadataMaxAgeMs,\u00a0SocketTimeoutMs,\u00a0MaxInFlight,\u00a0AdditionalPropertiesredb.Route, 30000, 300000, 60000, 5\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435:context.AddToRegistry(&#171;prod-cluster&#187;, new KafkaConnectionFactory{    Brokers          = &#171;kafka1:9092,kafka2:9092,kafka3:9092&#187;,    SecurityProtocol = &#171;SaslSsl&#187;,    SaslMechanism    = &#171;ScramSha512&#187;,    SaslUsername     = &#171;svc-orders&#187;,    SaslPassword     = secret,    CompressionType  = &#171;Zstd&#187;,    MaxInFlight      = 5,});\/\/ \u0422\u0435\u043f\u0435\u0440\u044c \u0432 URI \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e, \u0447\u0442\u043e \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u043e \u0434\u043b\u044f \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430:From(&#171;kafka:orders?connectionFactory=prod-cluster&amp;groupId=order-workers&#187;);To(&#171;kafka:notifications?connectionFactory=prod-cluster&amp;acks=All&#187;);KafkaConnectionFactory\u00a0\u0443\u043c\u0435\u0435\u0442 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0442\u0440\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u2014\u00a0BuildConsumerConfig(),\u00a0BuildProducerConfig(),\u00a0BuildAdminConfig()\u00a0(\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u043e\u043f\u0438\u043a\u0430). \u042d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u2011\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0435 \u043e\u043f\u0446\u0438\u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f\u00a0\u043f\u043e\u0432\u0435\u0440\u0445\u00a0\u0444\u0430\u0431\u0440\u0438\u043a\u0438 (if (!string.IsNullOrWhiteSpace(Brokers)) config.BootstrapServers = Brokers;\u00a0\u0438 \u0442.\u0434.) \u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u0444\u0430\u0431\u0440\u0438\u043a\u0430 \u0437\u0430\u0434\u0430\u0451\u0442 \u0431\u0430\u0437\u0443, URI \u0435\u0451 \u0442\u043e\u0447\u0435\u0447\u043d\u043e \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u0435\u0442.\u0412\u0441\u0435 \u043e\u043f\u0446\u0438\u0438 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430\u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u2014\u00a0KafkaEndpointOptions. \u0413\u0440\u0443\u043f\u043f\u0430\u043c\u0438, \u0441 \u0434\u0435\u0444\u043e\u043b\u0442\u0430\u043c\u0438:\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \/ \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c:\u00a0brokers\u00a0(\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d, \u0438\u043d\u0430\u0447\u0435\u00a0Validate()\u00a0\u0431\u0440\u043e\u0441\u0438\u0442),\u00a0securityProtocol\u00a0(Plaintext\/Ssl\/SaslPlaintext\/SaslSsl),\u00a0saslMechanism\/saslUsername\/saslPassword,\u00a0sslCaLocation\/sslCertificateLocation\/sslKeyLocation\/sslKeyPassword,\u00a0connectionFactory.\u041a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440:\u00a0groupId,\u00a0autoOffsetReset\u00a0(Latest\/Earliest\/Error, \u0434\u0435\u0444\u043e\u043b\u0442\u00a0Latest),\u00a0enableAutoCommit\u00a0(\u0434\u0435\u0444\u043e\u043b\u0442\u00a0true, framework-level \u2014 \u0441 3.2.1),\u00a0maxPollRecords\u00a0(0 = \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c, &gt;0 = \u0431\u0430\u0442\u0447),\u00a0pollTimeoutMs\u00a0(1000),\u00a0breakOnFirstError,\u00a0seekTo\u00a0(beginning\/end),\u00a0topicIsPattern,\u00a0groupInstanceId,\u00a0sessionTimeoutMs,\u00a0heartbeatIntervalMs,\u00a0maxPollIntervalMs,\u00a0partitionAssignmentStrategy\u00a0(Range\/RoundRobin\/CooperativeSticky),\u00a0isolationLevel\u00a0(ReadUncommitted\/ReadCommitted).\u041f\u0440\u043e\u0434\u044e\u0441\u0435\u0440:\u00a0acks\u00a0(None\/Leader\/All, \u0434\u0435\u0444\u043e\u043b\u0442\u00a0Leader),\u00a0retries\u00a0(3),\u00a0recordMetadata,\u00a0key,\u00a0partitionNumber,\u00a0transacted,\u00a0transactionIdPrefix\u00a0(redb-kafka).\u0422\u044e\u043d\u0438\u043d\u0433 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u0430:\u00a0lingerMs,\u00a0batchSize,\u00a0compressionType\u00a0(None\/Gzip\/Snappy\/Lz4\/Zstd),\u00a0messageTimeoutMs.Advanced:\u00a0additionalProperties\u00a0\u2014 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0435 librdkafka\u2011\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f\u00a0\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u043c\u0438\u00a0\u0438 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u044e\u0442 \u0432\u0441\u0451 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435.Validate()\u00a0\u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0440\u043e\u0432\u043d\u043e \u0442\u0440\u0438 \u0432\u0435\u0449\u0438 (\u043f\u043e \u043a\u043e\u0434\u0443):\u00a0brokers\u00a0\u043d\u0435\u043f\u0443\u0441\u0442\u043e\u0439,\u00a0maxPollRecords &gt;= 0,\u00a0pollTimeoutMs &gt;= 0,\u00a0retries &gt;= 0. \u0412\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043b\u0438\u0431\u043e \u0438\u043c\u0435\u0435\u0442 \u0434\u0435\u0444\u043e\u043b\u0442, \u043b\u0438\u0431\u043e \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e.\u041e\u0434\u0438\u043d \u043d\u044e\u0430\u043d\u0441 \u2014 \u043f\u0440\u043e \u043a\u043e\u043c\u043c\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442\u0430, \u0438 \u043e\u043d \u0432\u0430\u0436\u043d\u044b\u0439. \u041d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435\u00a0librdkafka\u00a0EnableAutoCommit\u00a0\u043f\u0440\u043e\u0448\u0438\u0442 \u0432\u00a0false\u00a0\u0432\u0441\u0435\u0433\u0434\u0430\u00a0\u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0441\u0430\u043c\u0430, \u043f\u043e \u0442\u0430\u0439\u043c\u0435\u0440\u0443, \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 (\u0438\u043d\u0430\u0447\u0435 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b\u0430 \u0431\u044b \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043d\u043e\u0435). \u0418\u0437\u00a0BuildConsumerConfig:var cfg = new ConsumerConfig{    BootstrapServers = Brokers,    GroupId          = GroupId,    AutoOffsetReset  = ParseAutoOffsetReset(),    EnableAutoCommit = false,   \/\/ librdkafka-\u0443\u0440\u043e\u0432\u0435\u043d\u044c: \u0440\u0443\u0447\u043d\u043e\u0439 \u043a\u043e\u043c\u043c\u0438\u0442 \u0432\u0441\u0435\u0433\u0434\u0430};\u041d\u043e \u043a\u043e\u043c\u043c\u0438\u0442, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u0435\u0441\u0442\u044c \u2014 \u043f\u043e\u0432\u0435\u0440\u0445, \u043d\u0430\u00a0\u0443\u0440\u043e\u0432\u043d\u0435 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430. \u0421\u00a03.2.1\u00a0\u0443 \u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440\u0430 \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u043e\u043f\u0446\u0438\u044f\u00a0EnableAutoCommit\u00a0(\u0434\u0435\u0444\u043e\u043b\u0442\u00a0true): \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e\u00a0Process\u00a0\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u043a\u043e\u043c\u043c\u0438\u0442\u0438\u0442 \u043e\u0444\u0444\u0441\u0435\u0442\u00a0\u0438\u043d\u043b\u0430\u0439\u043d\u00a0\u2014 \u0440\u043e\u0432\u043d\u043e \u043a\u0430\u043a RabbitMQ\u2011\u043a\u043e\u043d\u0441\u044c\u044e\u043c\u0435\u0440 \u0430\u0446\u043a\u0430\u0435\u0442 \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0421\u0442\u0430\u0432\u0438\u0442\u0441\u044f \u0438\u0437 URI (?enableAutoCommit=false) \u0438\u043b\u0438 \u0444\u043b\u0435\u043d\u0442\u043e\u043c (.EnableAutoCommit(false)). \u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0438\u043c\u0435\u0435\u0442 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442: \u0435\u0441\u043b\u0438 \u043e\u0444\u0444\u0441\u0435\u0442 \u0443\u0436\u0435 \u0437\u0430\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043b\u0430\u00a0.Transacted(), \u0438\u043d\u043b\u0430\u0439\u043d\u2011\u0432\u0435\u0442\u043a\u0430 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f. \u041c\u0435\u0445\u0430\u043d\u0438\u043a\u0443 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u0432 \u00a75.\u0422\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0444\u043b\u0435\u043d\u0442\u043e\u043c\u0421\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0435 URI \u0445\u043e\u0440\u043e\u0448\u0438 \u0434\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0438 \u0434\u043b\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u0432. \u0412 \u043a\u043e\u0434\u0435 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b \u043e\u0431\u044b\u0447\u043d\u043e \u0441\u0442\u0440\u043e\u044f\u0442 \u0444\u043b\u0435\u043d\u0442\u043e\u043c \u2014\u00a0Kafka.Topic(&#8230;):From(Kafka.Topic(&#171;orders&#187;)        .Brokers(&#171;kafka1:9092,kafka2:9092&#187;)        .GroupId(&#171;order-workers&#187;)        .AutoOffsetReset(&#171;Earliest&#187;)        .MaxPollRecords(500)        .PartitionAssignmentStrategy(&#171;CooperativeSticky&#187;)        .IsolationLevel(&#171;ReadCommitted&#187;)        .SessionTimeout(30_000)        .MaxPollInterval(300_000));To(Kafka.Topic(&#171;notifications&#187;)        .Brokers(&#171;kafka1:9092&#187;)        .Acks(&#171;All&#187;)        .Compression(&#171;zstd&#187;)        .Linger(20)        .BatchSize(64 * 1024)        .Key(&#171;${header.orderId}&#187;));   \/\/ \u043a\u043b\u044e\u0447 \u043f\u0430\u0440\u0442\u0438\u0446\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-485715","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/485715","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=485715"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/485715\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=485715"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=485715"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=485715"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}