Σύνδεση σε περιβάλλον microservice .Net στην πράξη

Σύνδεση σε περιβάλλον microservice .Net στην πράξη

Η καταγραφή είναι ένα πολύ σημαντικό εργαλείο για έναν προγραμματιστή, αλλά όταν χτίζετε κατανεμημένα συστήματα, γίνεται μια πέτρα που πρέπει να τεθεί ακριβώς στη βάση της εφαρμογής σας, διαφορετικά η πολυπλοκότητα της ανάπτυξης μικροϋπηρεσιών θα γίνει γρήγορα εμφανής.

Το .Net Core 3 πρόσθεσε ένα υπέροχο Δυνατότητα μετάδοσης περιβάλλοντος συσχέτισης σε κεφαλίδες HTTP, επομένως, εάν οι εφαρμογές σας χρησιμοποιούν άμεσες κλήσεις HTTP για επικοινωνία μεταξύ υπηρεσιών, μπορείτε να επωφεληθείτε από αυτήν τη λειτουργικότητα. Ωστόσο, εάν η αρχιτεκτονική του backend σας περιλαμβάνει αλληλεπίδραση μέσω ενός μεσίτη μηνυμάτων (RabbitMQ, Kafka, κ.λπ.), τότε θα πρέπει να ανησυχείτε για τη μετάδοση του πλαισίου συσχέτισης μέσω αυτών των μηνυμάτων μόνοι σας.

Σε αυτό το άρθρο, θα πάρουμε μια απλή εφαρμογή web API και θα οργανώσουμε την καταγραφή που θα γίνει

  • να διατηρείτε συσχέτιση από άκρο σε άκρο μεταξύ αρχείων καταγραφής ανεξάρτητων υπηρεσιών, ώστε να μπορείτε να προβάλλετε εύκολα όλες τις δραστηριότητες που προκλήθηκαν από ένα συγκεκριμένο αίτημα από έναν πελάτη

  • έχουν ένα ενιαίο σημείο εισόδου με βολική ανάλυση, έτσι ώστε ακόμη και η Υποστήριξη, η οποία λαμβάνει ερωτήσεις όπως "Έχω ένα σφάλμα στην εφαρμογή με τέτοιο και άλλο αναγνωριστικό αιτήματος", μπορεί να χρησιμοποιήσει το εργαλείο καταγραφής

Αρχικά, πρέπει να αποφασίσουμε για τον πάροχο καταγραφής για την εφαρμογή μας. Η κύρια απαίτηση για τη σύγχρονη καταγραφή είναι η δομή, δηλαδή να μην εργαζόμαστε με επίπεδα μηνύματα κειμένου, αλλά με αντικείμενα. Χάρη σε τέτοια αρχεία καταγραφής, μπορούμε εύκολα να δημιουργήσουμε αναπαραστάσεις των μηνυμάτων μας σε διαφορετικές ενότητες και να πραγματοποιήσουμε αναλύσεις.

Για την εφαρμογή μας θα χρησιμοποιήσουμε το πακέτο Serilog, το οποίο έχει εξαιρετική υποστήριξη για δομημένη καταγραφή και πλούσιο σύστημα πρόσθετων. Θα παραλείψω τα βασικά βήματα για να το ρυθμίσω (μπορείτε να βρείτε πολλά άρθρα σχετικά με αυτό το θέμα) και θα κάνω την υπόθεση ότι

  • Το Serilog έχει ήδη διαμορφωθεί και είναι το προεπιλεγμένο καταγραφικό για τον πάροχο ένεσης εξάρτησης.

  • η διαμόρφωσή του περιλαμβάνει εμπλουτισμό μηνυμάτων με ιδιότητες περιβάλλοντος (Enrich.FromLogContext)

Το επόμενο βήμα είναι να επιλέξετε σε ποιο κεντρικό σύστημα συλλογής αρχείων καταγραφής θα στέλνετε μηνύματα από το Serilog. Ίσως η πιο κοινή επιλογή ανοιχτού κώδικα σήμερα είναι η στοίβα ELK (Elasticsearch, Logstash και Kibana), οπότε ας το πάρουμε αυτό. Για να το κάνουμε αυτό, θα χρησιμοποιήσουμε την προσφορά από Logz.IO — μετά την εγγραφή για ένα δωρεάν πρόγραμμα, η πλήρης ισχύς της μηχανής αναζήτησης Lucene βρίσκεται στα χέρια μας.

Το μόνο που μένει να κάνουμε είναι να προσθέσουμε ένα πακέτο στο έργο μας Serilog.Νεροχύτες.Logzio

Install-Package Serilog.Sinks.Logzio

Και προσθέστε τον αντίστοιχο εμπλουτιστή στη διαμόρφωση του καταγραφέα μας, τροφοδοτώντας του το διακριτικό πρόσβασης

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

Τρέχοντας την εφαρμογή, θα μπορούμε να βλέπουμε τα μηνύματά μας όχι μόνο στην κονσόλα, αλλά και στο Kibana.

Σύνδεση σε περιβάλλον microservice .Net στην πράξη

Διεπαφές

Σύνδεση σε περιβάλλον microservice .Net στην πράξη

Σε μια εφαρμογή τύπου υπηρεσίας, μπορούμε να διακρίνουμε δύο κύριες διεπαφές της αλληλεπίδρασής της με τον έξω κόσμο. θα τα ορίσουμε ως κάθετα και οριζόντια. Η κατακόρυφη διεπαφή είναι ένα web API μέσω του οποίου φτάνουν κλήσεις από την εφαρμογή πελάτη. Το Horizontal είναι ένας μεσίτης μηνυμάτων που χρησιμοποιείται για την ανταλλαγή δεδομένων με άλλες εσωτερικές υπηρεσίες.

Ας εξετάσουμε τα στάδια υλοποίησης της συσχέτισης σε καθεμία από αυτές τις διεπαφές.

Συσχέτιση σε αιτήματα HTTP

Για να λάβουμε όσο το δυνατόν περισσότερες πληροφορίες, πρέπει να δημιουργήσουμε το αναγνωριστικό συσχέτισης όσο το δυνατόν πιο κοντά στην έναρξη της δραστηριότητας, δηλαδή στην πύλη ή απευθείας στον πελάτη (κινητό ή web). Δεδομένου ότι σήμερα έχουμε να κάνουμε με μια εφαρμογή υποστήριξης, θα ορίσουμε απλώς μια απαίτηση για μια υποχρεωτική κεφαλίδα "X-Correlation-ID" σε όλα τα αιτήματα προς το web 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 Bad request για όλα τα αιτήματα χωρίς κεφαλίδα με το αντίστοιχο αναγνωριστικό.

Αφού αρχίσουμε να λαμβάνουμε το αναγνωριστικό από τον πελάτη, πρέπει να το προσθέσουμε στο περιβάλλον καταγραφής, ας φτιάξουμε ένα επίπεδο πλαισίου για αυτό:

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

Στην εφαρμογή μας χρησιμοποιούμε το τυπικό ILogger από το πακέτο Microsoft.Extensions.Logging.Abstractions, οπότε θα προσθέσουμε την τιμή χρησιμοποιώντας μια απλή επέκταση σε αυτό.

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 ιστού μας περιέχουν ένα αναγνωριστικό συσχέτισης μέσω του οποίου μπορούν εύκολα να συνδεθούν.

Σύνδεση σε περιβάλλον microservice .Net στην πράξη

Συσχέτιση σε μηνύματα μεσίτη

Το επόμενο βήμα που πρέπει να ρυθμίσουμε είναι η μετάδοση και η λήψη του αναγνωριστικού συσχέτισης μέσω του μεσίτη μηνυμάτων. Στο παράδειγμά μας θα χρησιμοποιήσουμε το RabbitMQ και ως πελάτης θα πάρουμε το πλαίσιο MassTransit. Και πάλι, ας παραλείψουμε την αρχική ρύθμιση της εργασίας με το MassTransit και ας πάμε κατευθείαν στη ρύθμιση της καταγραφής.

Αρχικά, μπορούμε να ενεργοποιήσουμε τα αρχεία καταγραφής του ίδιου του MassTransit, για αυτό θα προσθέσουμε ένα πακέτο στην εφαρμογή μας MassTransit.SerilogIntegration

Install-Package MassTransit.SerilogIntegration

Τώρα, αφού προσθέσουμε το καταγραφικό στις ρυθμίσεις MassTransit, θα μπορούμε να δούμε τα αρχεία καταγραφής πλαισίου.

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

Ας υποθέσουμε ότι η εφαρμογή μας στέλνει ένα συμβάν SomethingDoneMessage με την τιμή "έγινε" ως απάντηση σε ένα αίτημα POST. Η σύμβαση για ένα τέτοιο μήνυμα μπορεί να περιγραφεί ως εξής:

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

Πρέπει ακόμα να διαμορφώσουμε την καταγραφή αυτών των ιδιοτήτων υπηρεσίας του μηνύματος, για αυτό θα προσθέσουμε ένα πακέτο στο έργο Serilog.Enrichers.MassTransitMessage. Το πακέτο προσθέτει ένα φίλτρο στη διοχέτευση επεξεργασίας μηνυμάτων MassTransit που στοιβάζει το περιβάλλον του μηνύματος σε μια στοίβα ασφαλή για νήμα. Το Serilog διαβάζει το περιβάλλον από τη στοίβα και προσθέτει αυτές τις πρόσθετες ιδιότητες στα αντικείμενα καταγραφής μας.

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 όχι μόνο σε μία υπηρεσία, αλλά και κατά την αλληλεπίδραση με άλλες εφαρμογές.

Σύνδεση σε περιβάλλον microservice .Net στην πράξη

Έτσι, το προκύπτον σύστημα καταγραφής σε εφαρμογές .Net μάς επιτρέπει να συσχετίζουμε αρχεία καταγραφής από εντελώς διαφορετικές μικροϋπηρεσίες χωρίς κανένα πρόβλημα - ακόμη και αυτές που λειτουργούν μέσω ενός μεσίτη μηνυμάτων. Και με τη βοήθεια του Elasticsearch μπορούμε να αναλύσουμε γρήγορα και εύκολα τα αρχεία καταγραφής δημιουργώντας τους πίνακες εργαλείων που χρειαζόμαστε στο Kibana (ένα παράδειγμα φαίνεται στην εικόνα της ανάρτησης).

Φυσικά, η εγγραφή σε αυτή τη φόρμα δεν θα καλύπτει περίπλοκες αλληλεπιδράσεις μεταξύ των υπηρεσιών σας και των διαφόρων εξωτερικών συστημάτων, αλλά η καθιέρωση μιας τέτοιας τάξης στην αρχή της ανάπτυξης ενός έργου είναι ένα από εκείνα τα πράγματα για τα οποία θα ευχαριστήσετε τον εαυτό σας περισσότερες από μία φορές.

Μπορείτε να κατανοήσετε τον πηγαίο κώδικα του συστήματος που προκύπτει στο έργο: github.com/a-postx/YA.ServiceTemplate

Πηγή: www.habr.com

Αγοράστε αξιόπιστη φιλοξενία για ιστότοπους με προστασία DDoS, διακομιστές VPS VDS 🔥 Αγοράστε αξιόπιστη φιλοξενία ιστοσελίδων με προστασία DDoS, διακομιστές VPS VDS | ProHoster