با مهاجرت سازمانها از معماریهای مونولیتیک به میکروسرویسها، پیچیدگی عیبیابی و نظارت به صورت نمایی افزایش مییابد. در یک محیط میکروسرویس، یک درخواست کاربری میتواند از دهها سرویس، پایگاه داده و صف پیام عبور کند. بدون دید به این تعاملات، شناسایی گلوگاههای عملکرد یا علل ریشهای شکستها به یک مشکل سوزن در انبار کاه تبدیل میشود. اینجاست که ردیابی توزیعشده و قابلیت مشاهدهپذیری وارد عمل میشوند. این راهنما بررسی میکند که چگونه میتوان قابلیت مشاهدهپذیری قوی را در سرویسهای Go gRPC با استفاده از ابزارهای استاندارد صنعتی مانند OpenTelemetry و Jaeger پیادهسازی کرد.
چرا لاگنویسی استاندارد کافی نیست
لاگنویسی سنتی یک نمای لحظهای از رویدادهای فردی درون یک سرویس ارائه میدهد. با این حال، لاگها ایزوله هستند. وقتی یک درخواست در چندین سرویس شکست میخورد، همبستگی بین ورودیهای لاگ برای بازسازی مسیر کامل تراکنش به طور مشهور دشوار است. ردیابی توزیعشده این مشکل را با اختصاص یک trace_id منحصر به فرد به هر درخواست ورودی و انتشار آن در مرزهای سرویس حل میکند. این امر به مهندسان اجازه میدهد تا چرخه عمر کامل یک درخواست را تجسم کنند، سهم تأخیر را از هر پله درک کنند و دقیقاً مشخص کنند که کجاها اشتباه رخ داده است.
راهاندازی زیرساخت ابزارگذاری (Instrumentation)
برای این پیادهسازی، از OpenTelemetry (OTel)، چارچوب استاندارد مستقل از ارائهدهنده برای قابلیت مشاهدهپذیری، و Jaeger به عنوان بکاند ما برای جمعآوری و تجسم ردیابیها استفاده خواهیم کرد. ابتدا، مطمئن شوید که Jaeger در محیط شما در حال اجراست، معمولاً از طریق Docker:
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:latest
ابزارگذاری سرور gRPC
پیادهسازی ردیابی در سرویسهای Go gRPC نیاز به تزریق میانافزار به زنجیره اینترسپتور gRPC دارد. هدف این است که به طور خودکار زمینه ردیابی، اطلاعات اسپن و متادیتا را برای هر تماس RPC ضبط کنیم. ما از بسته go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc استفاده خواهیم کرد که بیشتر کارهای سنگین را انجام میدهد.
در زیر یک مثال عملی از نحوه پیکربندی یک سرور gRPC با ابزارگذاری OpenTelemetry آورده شده است:
package main
import (
"context"
"log"
"net"
pb "your-project/proto" // Generated gRPC code
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"google.golang.org/grpc"
)
func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
// Create Jaeger exporter
exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.ServiceNameKey.String("my-grpc-server"),
)),
)
otel.SetTracerProvider(tp)
return tp, nil
}
func main() {
ctx := context.Background()
tp, err := initTracer(ctx)
if err != nil {
log.Fatalf("Could not initialize tracer: %v", err)
}
defer tp.Shutdown(ctx)
// Create gRPC server with otelgrpc instrumentation middleware
opts := []grpc.ServerOption{
grpc.StatsHandler(otelgrpc.NewServerHandler()),
}
s := grpc.NewServer(opts...)
pb.RegisterMyServiceServer(s, &myServer{})
listen, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
log.Printf("server listening at %v", listen.Addr())
if err := s.Serve(listen); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
خط کلیدی در اینجا grpc.StatsHandler(otelgrpc.NewServerHandler()) است. این به طور خودکار اسپنهایی برای RPCهای ورودی و خروجی ایجاد میکند، زمینه ردیابی را به متادیتا متصل میکند و معیارهای تأخیر را ضبط میکند.
ابزارگذاری کلاینت gRPC
در سمت کلاینت، پیکربندی متقارن است. باید مطمئن شوید که زمینه ردیابی به سرویس بعدی در زنجیره منتشر میشود. با استفاده از همان کتابخانه otelgrpc، دیالر (Dialer) کلاینت را پیکربندی میکنیم:
func newClient(ctx context.Context) (pb.MyServiceClient, *grpc.ClientConn, error) {
conn, err := grpc.DialContext(ctx, "localhost:50051",
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return nil, nil, err
}
client := pb.NewMyServiceClient(conn)
return client, conn, nil
}
با تزریق هندلر آمار کلاینت، کتابخانه gRPC به طور خودکار هدرهای والد ردیابی را به درخواستهای خروجی تزریق میکند. این تضمین میکند که وقتی سرور تماس را دریافت میکند، میتواند ردیابی موجود را ادامه دهد به جای اینکه یک ردیابی جدید شروع کند.
پیشرفته: ویژگیهای سفارشی و اسپنها
در حالی که ابزارگذاری خودکار موارد پایه را پوشش میدهد، قابلیت مشاهدهپذیری پیشرفته اغلب نیاز به افزودن زمینههای خاص کسبوکار دارد. شما میتوانید این کار را با ایجاد اسپنهای دستی انجام دهید. به عنوان مثال، اگر هندلر gRPC شما یک کوئری پایگاه داده انجام میدهد، ممکن است بخواهید یک اسپن فرزند برای پیگیری آن عملیات خاص اضافه کنید:
tracer := otel.Tracer("my-service")
ctx, span := tracer.Start(ctx, "database_query")
defer span.End()
// Perform DB operation here
// Span will automatically record start/end time and status
علاوه بر این، میتوانید ویژگیهایی را روی اسپنها تنظیم کنید تا متادیتایی مانند شناسههای کاربری یا پیامهای خطا را ضبط کنید که این امر فیلتر کردن و جستجو در Jaeger را بسیار قدرتمندتر میکند.
نتیجهگیری
پیادهسازی ردیابی توزیعشده در میکروسرویسهای Go gRPC نه تنها یک بهترین شیوه، بلکه ضروری است تا قابلیت اطمینان سیستم را در مقیاس حفظ کنیم. با بهرهگیری از OpenTelemetry و Jaeger، توسعهدهندگان میتوانند بینش عمیقی درباره رفتار سیستم کسب کنند، میانگین زمان رفع اختلال (MTTR) برای حوادث را کاهش دهند و عملکرد را بهینهسازی کنند. قطعه کدهای ارائه شده نشان میدهند که با کتابخانههای مدرن، مانع ورود پایین است. از امروز سرویسهای خود را ابزارگذاری کنید و زیرساخت قابلیت مشاهدهپذیری که معماری میکروسرویس شما شایسته آن است را بنا کنید.