Regjistrimi në një mjedis mikroshërbimesh .Net në praktikë

Regjistrimi në një mjedis mikroshërbimesh .Net në praktikë

Regjistrimi është një instrument shumë i rëndësishëm për zhvilluesit, por kur krijoni sisteme të shpërndara, ai bëhet një gur themeli që duhet të vendoset direkt në themelin e aplikacionit tuaj, ndryshe kompleksiteti i zhvillimit të mikroshërbimeve do t'i japë shpejt shenjat e veta.

Në .Net Core 3 është shtuar një mundësi e shkëlqyer për transmetimin e kontekstit të korrelacionit në header-at HTTP, prandaj nëse aplikacionet tuaja përdorin thirrje direkte HTTP për ndërveprimin midis shërbimeve, atëherë mund të shfrytëzoni këtë funksionalitet të gatshëm. Megjithatë, nëse arkitektura e backend-it tuaj parashikon ndërveprimin përmes një broker-i mesazhesh (si RabbitMQ, Kafka etj.), atëherë ju ende duhet të merrni parasysh çështjen e transmetimit të kontekstit të korrelacionit përmes këtyre mesazheve vetë.

Në këtë artikull do të marrim një aplikacion të thjeshtë të web-api dhe do të organizojmë regjistrimin, i cili do të

  • ruajĂ« korrelacionin e plotĂ« midis regjistrimeve tĂ« shĂ«rbimeve tĂ« pavarura, nĂ« mĂ«nyrĂ« qĂ« tĂ« jetĂ« lehtĂ« tĂ« shikoni tĂ« gjitha aktivitetet qĂ« janĂ« thirrur nga njĂ« kĂ«rkesĂ« e veçantĂ« nga klienti

  • tĂ« kemi njĂ« pikĂ« hyrjeje tĂ« vetme me analizĂ« tĂ« lehtĂ«, nĂ« mĂ«nyrĂ« qĂ« mjeti i regjistrimit tĂ« mund tĂ« pĂ«rdoret edhe nga mbĂ«shtetja, e cila merr pyetje si "mĂ« doli njĂ« gabim nĂ« aplikacion me kĂ«tĂ« ID kĂ«rkese".

Së pari, duhet të përcaktojmë furnizuesin e regjistrimit në aplikacionin tonë. Kërkesa kryesore për regjistrimin e sotëm është strukturaliteti, pra, ne duhet të punojmë jo me mesazhe të sheshta tekstuale, por me objekte. Falë këtyre regjistrimeve, ne mund të ndërtojmë lehtësisht pamjet e mesazheve tona në forma të ndryshme dhe të bëjmë analitikë.

Për aplikacionin tonë do të përdorim paketën Serilog (Serilogu), e cila ka mbështetje të shkëlqyer për regjistrimin strukturor dhe një sistem të pasur me shtesat. Do ta anashkaloj fazën bazike të konfigurimit të tij (mund të gjeni një sasi të madhe artikujsh mbi këtë temë) dhe do të supozoj se

  • Serilog Ă«shtĂ« tashmĂ« i konfiguruar dhe Ă«shtĂ« regjistruesi nga pikĂ«pamja e zakonshme te furnizuesi juaj i inkuadrimit tĂ« varĂ«sive

  • nĂ« konfigurimin e tij Ă«shtĂ« pĂ«rfshirĂ« pasurimi i mesazheve me vetitĂ« e kontekstit (Enrich.FromLogContext)

Hapi tjetĂ«r Ă«shtĂ« tĂ« zgjidhni nĂ« cilin sistem tĂ« grumbullimit tĂ« regjistrimeve tĂ« centralizuara do tĂ« dĂ«rgoni mesazhet nga Serilog. Sigurisht, opsioni mĂ« i zakonshĂ«m aktualisht nga programi i hapur Ă«shtĂ« staku ELK (Elasticsearch, Logstash dhe Kibana), atĂ«herĂ« do ta marrim kĂ«tĂ«. PĂ«r kĂ«tĂ« do tĂ« shfrytĂ«zojmĂ« ofertĂ«n nga Logz.IO — pas regjistrimit nĂ« planin falas, nĂ« duar kemi tĂ« gjithĂ« fuqinĂ« e motorit tĂ« kĂ«rkimit Lucene.

Na mbetet të shtojmë në projektin tonë paketën Serilog.Sinks.Logzio

Install-Package Serilog.Sinks.Logzio

Dhe të shtojmë pasuruesin përkatës në konfigurimin e regjistruesit tonë, duke i dhënë atij token-in e aksesit

LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);

Duke e nisur aplikacionin, mund të vëzhgojmë mesazhet tona jo vetëm në konsolë, por edhe në Kibana.

Regjistrimi në një mjedis mikroshërbimesh .Net në praktikë

Interfacet

Regjistrimi në një mjedis mikroshërbimesh .Net në praktikë

Në një aplikacion tip shërbimi, mund të dallojmë dy ndërfaqet kryesore të ndërveprimit të tij me botën e jashtme, t'i shënojmë ato si ndërfaqe vertikale dhe horizontale. Ndërfaqja vertikale është web-api, përmes të cilit vijnë thirrjet nga aplikacioni klient. Ndërfaqja horizontale është broker-i i mesazheve, i cili përdoret për shkëmbim të të dhënave me shërbime të tjera të brendshme.

Le të shqyrtojmë hapat për zbatimin e korrelacionit në secilën nga këto ndërfaqe.

Korrelacioni në HTTP kërkesat

Për të marrë sa më shumë informacion, duhet të gjenerojmë një identifikues korrelacioni sa më afër fillimit të aktivitetit, pra, në gateway ose direkt në klient (mobile ose web). Duke qenë se sot po merremi me një aplikacion backend, do të shënojmë thjesht kërkesën për një header të detyrueshëm "X-Correlation-ID" në të gjitha kërkesat ndaj web-api.

Shtojmë paketën CorrelationID, funksioni i së cilës është të nxjerrë vlerën nga header-i që na nevojitet

Install-Package CorrelationID

Do ta shtojmë atë në procesin e përpunimit të kërkesës

public class Startup
{
    public void Configure(IApplicationBuilder application)
    {
        application
	    .UseCorrelationId(new CorrelationIdOptions
        {
            Header = "X-Correlation-ID",
            IncludeInResponse = false,
            UpdateTraceIdentifier = false,
            UseGuidForCorrelationId = false
        });
    }
}

Tani me ndihmën e tij do të bëjmë një filter të thjeshtë veprimi:

klasë publike të vulosur 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();
    }
}

Dhe do ta shtojmë atë në kontrollues

[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
public class CarsController : ControllerBase
{

}

Si rezultat, kontrolluesi do të kthejë 400 Bad request për të gjitha kërkesat pa një header me identifikuesin përkatës.

Pasi kemi filluar të marrim identifikuesin nga klienti, ne duhet ta shtojmë atë në kontekstin e regjistrimit, do ta bëjmë këtë me një shtresë të jashtme:

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

Në aplikacionin tonë përdorim ILogger standard nga paketa Microsoft.Extensions.Logging.Abstractions, prandaj do ta shtojmë vlerën me një zgjerim të thjeshtë të tij.

public static IDisposable BeginScopeWith(this ILogger logger, params (string key, object value)[] keys)
{
    return logger.BeginScope(keys.ToDictionary(x => x.key, x => x.value));
}

Shtojmë shtresën në pipeline e përpunimit të kërkesës dhe marrim rezultatet e dëshiruara.

public class Startup
{
    public void Configure(IApplicationBuilder application)
    {
        application.UseMiddleware<CorrelationIdContextLogger>();
    }
}

Tani, të gjitha aktivitetet që lindin nga kërkesat në API-në tonë të uebit përmbajnë identifikuesin korrelues me të cilin është e lehtë të lidhim ato.

Regjistrimi në një mjedis mikroshërbimesh .Net në praktikë

Korrelacioni në mesazhet e brokerit

Hapi tjetër është të vendosim transmetimin dhe marrjen e identifikuesit korrelues përmes brokerit të mesazheve. Në shembullin tonë do të përdorim RabbitMQ, dhe si klient do të marim framework-un MassTransit (MassaTransit). Po ashtu, do të anashkalojmë konfigurimin fillestar për të punuar me MassTransit dhe do të kalojmë direkt në konfigurimin e regjistrimit.

Së pari, mund ta aktivizojmë regjistrimin e vetë MassTransit, për këtë do të shtojmë në aplikacionin tonë paketën MassTransit.SerilogIntegration

Install-Package MassTransit.SerilogIntegration

Tani, pas shtimit të regjistruesit në konfigurimet e MassTransit, do të mund të shohim regjistrimet e framework-ut.

services
    .AddSingleton(provider =>
        {
            return Bus.Factory.CreateUsingRabbitMq(cfg =>
            {
                cfg.UseSerilog();
            });
        });

Le të themi se aplikacioni ynë, si reagim ndaj kërkesës POST, dërgon një ngjarje SomethingDoneMessage me vlerën "done". Kontrakti i tillë mesazhi mund të përshkruhet kështu:

namespace MbMessages
{
    public interface ISomethingDoneMessageV1
    {
        string Value { get; }
    }
}

Mesazhet e MassTransit në thelb janë një enë, në të cilën janë të vendosura mesazhet e brokerit. Enë duket më pak kështu:

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

Në mesazh janë të dukshme fushat shërbyese që janë të nevojshme për funksionimin e vetë framework-ut, por ne kemi mundësinë të shtojmë në këtë enë dhe pronësi shtesë. Më shumë se kaq, MassTransit ka mjete të integruara për të punuar me disa fusha opsionale, ndër të cilat identifikuesi i korrelacionit CorrelationId na intereson më shumë.

Shtojmë në kontraktin e mesazhit ndërfaqen CorrelatedBy:

namespace MbMessages
{
    public interface ISomethingDoneMessageV1 : CorrelatedBy<Guid>
    {
        string Value { get; }
    }
}

E realizojmë atë dhe do ta caktojmë vlerën e pronës CorrelationId kur të krijohet mesazhi:

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

NĂ«se e shohim mesazhin e pĂ«rditĂ«suar, do tĂ« shohim se identifikuesi i korrelacionit Ă«shtĂ« bĂ«rĂ« pjesĂ« jo vetĂ«m e mesazhit tonĂ«, por edhe e enĂ«s — ky identifikues tani do tĂ« pĂ«rdoret gjithashtu nĂ« tĂ« gjitha regjistrimet e MassTransit, duke e bĂ«rĂ« mĂ« tĂ« lehtĂ« pĂ«r ne tĂ« merremi me probleme nĂ« nivelin e brokerit tĂ« mesazheve.

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

Na kemi për të konfiguruar regjistrimin e këtyre pronave të shërbimit të mesazhit, prandaj do të shtojmë paketën Serilog.Enrichers.MassTransitMessage. Paketa shton një filtrin në kanalin e procesimit të mesazheve MassTransit, i cili përmbledh kontekstin e mesazhit në një grumbull të sigurt për përdorim. Serilog lexon kontekstin nga grumbulli dhe e shton këtë pronë shtesë në objektet tona të regjistrimit.

Install-Package Serilog.Enrichers.MassTransitMessage

NĂ« MassTransit vendosim filtrin

services
    .AddSingleton(provider =>
        {
            return Bus.Factory.CreateUsingRabbitMq(cfg =>
            {
                cfg.UseSerilog();
                cfg.UseSerilogMessagePropertiesEnricher();
            });
        });

Dhe në konfigurimin e Serilog shtojmë enriquecer-in

Log.Logger = new LoggerConfiguration()
    .Enrich.FromMassTransitMessage()
    .CreateLogger();

Duke qenë se aplikacioni që merr mesazhin nga RabbitMQ ka qasje në të gjitha pronat e konvertimit MassTransit, mund të përdorim identifikuesin e korelacionit të marrë brenda aplikacionit konsumator, si dhe ta kalojmë atë më tej në të gjithë zinxhirin e thirrjeve.

Si rezultat, regjistrimet tona filluan të përmbajnë CorrelationId jo vetëm brenda një shërbimi të vetëm, por edhe gjatë ndërveprimit me aplikacione të tjera.

Regjistrimi në një mjedis mikroshërbimesh .Net në praktikë

Pra, sistemi i regjistrimit tĂ« fituar nĂ« aplikacionet .Net na lejon tĂ« korelojmĂ« regjistrimet nga mikrosherbime tĂ« ndryshme — madje edhe ato qĂ« funksionojnĂ« pĂ«rmes njĂ« brokeri mesazhesh. Dhe me ndihmĂ«n e Elasticsearch, mund tĂ« analizojmĂ« shpejt dhe lehtĂ«sisht regjistrimet duke ndĂ«rtuar nĂ« Kibana panelin e nevojshĂ«m (shembulli Ă«shtĂ« dhĂ«nĂ« nĂ« figurĂ«n e postimit).

Sigurisht, në një format të tillë regjistrimi nuk do të mbulojë skenarët e komplikuar të ndërveprimit mes shërbimeve tuaja dhe sistemeve të jashtme të ndryshme, por vendosja e një rendi të tillë në fillim të zhvillimit të projektit është një nga ato gjëra për të cilat do t'i thoni vetes faleminderit më vonë.

Mund të hidhni një sy në kodin burimor të këtij sistemi në projektin: github.com/a-postx/YA.ServiceTemplate

Burimi: habr.com

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster