
Գրանցումը շատ կարևոր գործիք է մշակողի համար, բայց բաշխված համակարգեր կառուցելիս այն դառնում է այն քարը, որը պետք է դրվի ձեր ծրագրի հիմքում, հակառակ դեպքում միկրոսերվիսների մշակման բարդությունը արագորեն ակնհայտ կդառնա։
.Net Core 3-ը ավելացրել է հիանալի , այնպես որ, եթե ձեր ծրագրերը օգտագործում են ուղիղ HTTP զանգեր միջծառայողական հաղորդակցության համար, դուք կարող եք օգտագործել այս պատրաստի ֆունկցիոնալությունը: Այնուամենայնիվ, եթե ձեր backend ճարտարապետությունը ենթադրում է հաղորդակցություն հաղորդագրությունների միջնորդի միջոցով (RabbitMQ, Kafka և այլն), ապա դուք դեռ պետք է անհանգստանաք այդ հաղորդագրությունների միջոցով փոխկապակցման համատեքստը ինքներդ փոխանցելու մասին:
Այս հոդվածում մենք կվերցնենք պարզ վեբ API ծրագիր և կկազմակերպենք գրանցում, որը կկատարվի
պահպանել անկախ ծառայությունների գրանցամատյանների միջև ծայրից ծայր փոխկապակցվածությունը, որպեսզի հեշտությամբ կարողանաք դիտել հաճախորդի կողմից տրված կոնկրետ հարցման հետևանքով առաջացած բոլոր գործողությունները։
ունենալ մեկ մուտքի կետ՝ հարմար վերլուծությամբ, որպեսզի նույնիսկ Աջակցության ծառայությունը, որը ստանում է այնպիսի հարցեր, ինչպիսիք են՝ «Ես սխալ եմ ստացել ծրագրում այսինչ հարցման ID-ով», կարողանա օգտագործել գրանցման գործիքը։
Նախ, մենք պետք է որոշենք մեր ծրագրում գրանցման մատակարարը։ Ժամանակակից գրանցման հիմնական պահանջը կառուցվածքն է, այսինքն՝ մենք պետք է աշխատենք ոչ թե հարթ տեքստային հաղորդագրությունների, այլ օբյեկտների հետ։ Նման գրանցումների շնորհիվ մենք կարող ենք հեշտությամբ կառուցել մեր հաղորդագրությունների ներկայացումները տարբեր բաժիններում և անցկացնել վերլուծություններ։
Մեր ծրագրի համար մենք կօգտագործենք Serilog փաթեթը, որն ունի կառուցվածքային գրանցման գերազանց աջակցություն և հարուստ լրացուցիչ համակարգ։ Ես կբաց թողնեմ դրա կարգավորման հիմնական քայլերը (կարող եք գտնել այս թեմայով բազմաթիվ հոդվածներ) և կանեմ այն ենթադրությունը, որ
Serilog-ը արդեն կարգավորված է և ձեր կախվածության ներարկման մատակարարի լռելյայն գրանցողն է։
դրա կարգավորումը ներառում է հաղորդագրությունների հարստացում համատեքստային հատկություններով (Enrich.FromLogContext):
Հաջորդ քայլը Serilog-ից հաղորդագրություններ ուղարկելու կենտրոնացված գրանցամատյանների հավաքագրման որ համակարգին ընտրելն է: Այսօր, թերևս, ամենատարածված բաց կոդով տարբերակը ELK stack-ն է (Elasticsearch, Logstash և Kibana), այնպես որ, եկեք վերցնենք դա: Դրա համար մենք կօգտագործենք առաջարկը — անվճար պլանին գրանցվելուց հետո Lucene որոնողական համակարգի ողջ հզորությունը մեր ձեռքերում է։
Մեզ մնում է միայն մեր նախագծին փաթեթ ավելացնել
Install-Package Serilog.Sinks.Logzio
Եվ ավելացրեք համապատասխան հարստացուցիչը մեր լոգերի կոնֆիգուրացիային՝ դրան տալով մուտքի տոկենը։
LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);
Ծրագիրը գործարկելով՝ մենք կկարողանանք տեսնել մեր հաղորդագրությունները ոչ միայն կոնսոլում, այլև Կիբանայում։

Ինտերֆեյս

Ծառայողական տիպի հավելվածում մենք կարող ենք տարբերակել արտաքին աշխարհի հետ դրա փոխազդեցության երկու հիմնական ինտերֆեյս, որոնք մենք կնշանակենք ուղղահայաց և հորիզոնական: Ուղղահայաց ինտերֆեյսը վեբ API է, որի միջոցով ստացվում են հաճախորդային հավելվածից ստացված զանգերը: Հորիզոնականը հաղորդագրությունների բրոքեր է, որն օգտագործվում է այլ ներքին ծառայությունների հետ տվյալներ փոխանակելու համար:
Եկեք քննարկենք այս ինտերֆեյսներից յուրաքանչյուրի վրա կորելյացիայի իրականացման փուլերը։
HTTP հարցումներում փոխհարաբերությունը
Որքան հնարավոր է շատ տեղեկատվություն ստանալու համար մենք պետք է ստեղծենք կորելյացիայի նույնականացուցիչը գործունեության մեկնարկին որքան հնարավոր է մոտ, այսինքն՝ դարպասի վրա կամ անմիջապես հաճախորդի վրա (բջջային կամ վեբ): Քանի որ այսօր մենք գործ ունենք backend հավելվածի հետ, մենք պարզապես կնշենք պարտադիր «X-Correlation-ID» վերնագրի պահանջը վեբ API-ին ուղղված բոլոր հարցումներում:
Փաթեթի ավելացում , որի գործառույթն է վերցնել մեզ անհրաժեշտ վերնագրից արժեքը
Install-Package CorrelationID
Եկեք այն ավելացնենք հարցման մշակման խողովակաշարին
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application
.UseCorrelationId(new CorrelationIdOptions
{
Header = "X-Correlation-ID",
IncludeInResponse = false,
UpdateTraceIdentifier = false,
UseGuidForCorrelationId = false
});
}
}
Հիմա եկեք օգտագործենք այն պարզ գործողությունների ֆիլտր պատրաստելու համար։
public sealed class ApiRequestFilter : ActionFilterAttribute
{
public ApiRequestFilter(IApiRequestTracker apiRequestTracker, ICorrelationContextAccessor correlationContextAccessor)
{
_correlationContextAccessor = correlationContextAccessor ?? throw new ArgumentNullException(nameof(correlationContextAccessor));
}
private readonly ICorrelationContextAccessor _correlationContextAccessor;
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!Guid.TryParse(_correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId))
{
context.Result = new BadRequestResult();
return;
}
await next.Invoke();
}
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await next.Invoke();
}
}
Եվ եկեք այն ավելացնենք կառավարիչին
[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
public class CarsController : ControllerBase
{
}
Արդյունքում, կառավարիչը կարտադրի 400 վատ հարցում բոլոր այն հարցումների համար, որոնք չունեն համապատասխան նույնականացուցիչով վերնագիր։
Հաճախորդից նույնականացուցիչը ստանալուց հետո, մենք պետք է այն ավելացնենք գրանցման համատեքստին, եկեք ստեղծենք շրջանակային շերտ դրա համար՝
public class CorrelationIdContextLogger
{
public CorrelationIdContextLogger(RequestDelegate next)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
}
readonly RequestDelegate _next;
public async Task InvokeAsync(HttpContext httpContext, ILogger<CorrelationIdContextLogger> logger, ICorrelationContextAccessor correlationContextAccessor)
{
if (Guid.TryParse(correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId))
{
using (logger.BeginScopeWith(("CorrelationId", correlationId)))
{
await _next(context);
}
}
else
{
await _next(context);
}
}
}
Մեր ծրագրում մենք օգտագործում ենք Microsoft.Extensions.Logging.Abstractions փաթեթի ստանդարտ ILogger-ը, ուստի արժեքը կավելացնենք դրան՝ օգտագործելով պարզ ընդլայնում։
public static IDisposable BeginScopeWith(this ILogger logger, params (string key, object value)[] keys)
{
return logger.BeginScope(keys.ToDictionary(x => x.key, x => x.value));
}
Մենք հարցման մշակման խողովակաշարին ավելացնում ենք շերտ և ստանում ենք ցանկալի արդյունքը։
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseMiddleware<CorrelationIdContextLogger>();
}
}
Այժմ մեր վեբ API-ին ուղղված հարցումների միջոցով ստեղծված բոլոր գործողությունները պարունակում են համահարաբերակցության նույնականացուցիչ, որի միջոցով դրանք կարող են հեշտությամբ կապվել։

Հարաբերակցություն միջնորդային հաղորդագրություններում
Հաջորդ քայլը կապի նույնականացուցիչի փոխանցումը և ընդունումը կարգավորելն է հաղորդագրությունների բրոքերի միջոցով: Մեր օրինակում մենք կօգտագործենք RabbitMQ-ը, իսկ որպես հաճախորդ կվերցնենք MassTransit framework-ը: Կրկին, մենք կբաց թողնենք MassTransit-ի հետ աշխատանքի սկզբնական կարգավորումը և անմիջապես կանցնենք գրանցման կարգավորմանը:
Սկսելու համար մենք կարող ենք միացնել MassTransit-ի տեղեկամատյանները, դրա համար մենք մեր ծրագրին կավելացնենք փաթեթ։
Install-Package MassTransit.SerilogIntegration
Հիմա, MassTransit-ի կարգավորումներում լոգերը ավելացնելուց հետո, մենք կկարողանանք տեսնել շրջանակի լոգերը։
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
});
});
Ենթադրենք, որ մեր ծրագիրը POST հարցմանը պատասխանում է SomethingDoneMessage իրադարձություն՝ «done» արժեքով: Նման հաղորդագրության կոնտրակտը կարելի է նկարագրել հետևյալ կերպ.
namespace MbMessages
{
public interface ISomethingDoneMessageV1
{
string Value { get; }
}
}
MassTransit հաղորդագրությունները, ըստ էության, ծրար են, որոնց մեջ փակցված են միջնորդի հաղորդագրությունները: Ծրարը մոտավորապես այսպիսի տեսք ունի՝
{
"messageId": "59020000-5dba-0015-10b8-08d77ec28593",
"requestId": "59020000-5dba-0015-5674-08d77ec28592",
"conversationId": "59020000-5dba-0015-bca8-08d77ec28594",
"destinationAddress": "rabbitmq://bear.rmq.cloudamqp.com/aelzlsta/ya.servicetemplate.receiveendpoint",
"headers": {},
"messageType": [
"urn:message:MbMessages:ISomethingDoneMessageV1"
],
"message": {
"value": "done"
}
}
Հաղորդագրությունը ցույց է տալիս սպասարկման դաշտեր, որոնք անհրաժեշտ են շրջանակի աշխատանքի համար, բայց մենք ունենք հնարավորություն ավելացնելու մեր սեփական լրացուցիչ հատկությունները այս ծրարին: Ավելին, MassTransit-ն ունի ներկառուցված գործիքներ որոշ լրացուցիչ դաշտերի հետ աշխատելու համար, որոնցից մեզ ամենաշատը հետաքրքրում է CorrelationId կորելյացիայի նույնականացուցիչը:
Եկեք հաղորդագրության պայմանագրին ավելացնենք CorrelatedBy ինտերֆեյսը։
namespace MbMessages
{
public interface ISomethingDoneMessageV1 : CorrelatedBy<Guid>
{
string Value { get; }
}
}
Եկեք իրականացնենք այն և հաղորդագրություն ստեղծելիս արժեք վերագրենք CorrelationId հատկությանը։
internal class SomethingDoneMessageV1 : ISomethingDoneMessageV1
{
internal SomethingDoneMessageV1(Guid correlationId, string value)
{
CorrelationId = correlationId;
Value = value;
}
public Guid CorrelationId { get; private set; }
public string Value { get; private set; }
}
Եթե նայենք թարմացված հաղորդագրությանը, կտեսնենք, որ կորելյացիայի նույնականացուցիչը դարձել է ոչ միայն մեր հաղորդագրության, այլև ծրարի մաս. այս նույնականացուցիչն այժմ կօգտագործվի նաև MassTransit-ի բոլոր գրանցամատյաններում, ինչը նշանակում է, որ մեզ համար շատ ավելի հեշտ կլինի լուծել հաղորդագրությունների բրոքերային մակարդակում առաջացած խնդիրները։
{
"messageId": "59020000-5dba-0015-10b8-08d77ec28593",
"requestId": "59020000-5dba-0015-5674-08d77ec28592",
"conversationId": "59020000-5dba-0015-bca8-08d77ec28594",
"correlationId": "c7ff562a-b639-415b-9add-c9e524a727cc",
"destinationAddress": "rabbitmq://bear.rmq.cloudamqp.com/aelzlsta/ya.servicetemplate.receiveendpoint",
"headers": {},
"messageType": [
"urn:message:MbMessages:ISomethingDoneMessageV1"
],
"message": {
"correlationId": "c7ff562a-b639-415b-9add-c9e524a727cc",
"value": "Hello"
}
}
Մենք դեռ պետք է կարգավորենք հաղորդագրության այս ծառայության հատկությունների գրանցումը, դրա համար մենք նախագծին կավելացնենք փաթեթ։ Փաթեթը MassTransit հաղորդագրությունների մշակման խողովակաշարին ավելացնում է ֆիլտր, որը հաղորդագրության համատեքստը տեղափոխում է թելերի համար անվտանգ կույտի մեջ։ Serialog-ը կարդում է համատեքստը կույտից և ավելացնում է այս լրացուցիչ հատկությունները մեր գրանցամատյանի օբյեկտներին։
Install-Package Serilog.Enrichers.MassTransitMessage
MassTransit-ում մենք տեղադրում ենք ֆիլտր
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
cfg.UseSerilogMessagePropertiesEnricher();
});
});
Եվ Serilog կոնֆիգուրացիայում մենք ավելացնում ենք Enricher-ը
Log.Logger = new LoggerConfiguration()
.Enrich.FromMassTransitMessage()
.CreateLogger();
Քանի որ RabbitMQ հերթից հաղորդագրություն ստացող հավելվածը հասանելիություն ունի MassTransit ծրարի բոլոր հատկություններին, մենք կարող ենք օգտագործել արդյունքում ստացված կորելացիայի նույնականացուցիչը սպառող հավելվածի ներսում և այն փոխանցել հետագա զանգերի շղթայով։
Արդյունքում, մեր գրանցամատյանները սկսեցին պարունակել CorrelationId ոչ միայն մեկ ծառայության շրջանակներում, այլև այլ ծրագրերի հետ փոխազդեցության ժամանակ։

Այսպիսով, .Net հավելվածներում ստացված գրանցման համակարգը թույլ է տալիս մեզ առանց որևէ խնդրի համեմատել բոլորովին տարբեր միկրոսպասարկումների գրանցամատյանները՝ նույնիսկ նրանց, որոնք աշխատում են հաղորդագրությունների բրոքերի միջոցով: Եվ Elasticsearch-ի օգնությամբ մենք կարող ենք արագ և հարմար կերպով վերլուծել գրանցամատյանները՝ կառուցելով Kibana-ում մեզ անհրաժեշտ վահանակները (օրինակը տրված է գրառմանը կից նկարում):
Իհարկե, այս ձևաթղթում մուտք գործելը չի ներառի ձեր ծառայությունների և տարբեր արտաքին համակարգերի միջև բարդ փոխազդեցությունները, բայց նախագծի մշակման հենց սկզբում նման կարգուկանոն հաստատելը այն բաներից մեկն է, որի համար դուք ինքներդ ձեզ մեկից ավելի անգամ շնորհակալ կլինեք։
Դուք կարող եք հասկանալ նախագծում ստացված համակարգի սկզբնական կոդը.
Source: www.habr.com
