本文主要講解使用ZipKin構建NetCore分散式鏈路跟蹤
場景
因為最近公司業務量增加,而專案也需要增大部署數量,K8S中Pod基本都擴容了一倍,新增了若干物理機,部分物理機網路通訊存在問題,導致部分請求打入有問題的物理機時總會出現超時的情況,由於之前系統中沒有使用鏈路跟蹤,導致排查問題比較慢,所以就去研究了市面上的鏈路框架,結果發現了ZipKin這款比較輕量級的鏈路跟蹤框架。
例項程式碼
本文日誌系統採用Exceplesstion
示例程式碼請求鏈路為SimpleZipkin(閘道器服務)--->WebApi(Api服務)--->OrderApi(訂單服務)
首先建立公用類庫,引用以下包(本文以1.5.0版本為例)
如果部署Zipkin使用的是Mysql作為儲存,切記Mysql版本不要高於8.0,Zipkin暫不支援8.0的版本
zipkin4net
zipkin4net.middleware.aspnetcore
建立ZipKin幫助類
public static class ZipKinExtensions
{
public static IServiceCollection AddZipKin(this IServiceCollection services)
{
return services.AddSingleton<HttpDiagnosticSourceObserver >();
}
public static IApplicationBuilder UseZipKin(this IApplicationBuilder app, IHostApplicationLifetime lifetime, ILoggerFactory loggerFactory, string serviceName, string zipKinUrl)
{
DiagnosticListener.AllListeners.Subscribe(app?.ApplicationServices?.GetService<TraceObserver>());
lifetime.ApplicationStarted.Register(() =>
{
TraceManager.SamplingRate = 1.0f;//記錄資料密度,1.0代表全部記錄
var logger = new TracingLogger(loggerFactory, "zipkin4net");
var httpSender = new HttpZipkinSender(zipKinUrl, "application/json");
var tracer = new ZipkinTracer(httpSender, new JSONSpanSerializer(), new Statistics());
var consoleTracer = new zipkin4net.Tracers.ConsoleTracer();
TraceManager.RegisterTracer(tracer);
TraceManager.RegisterTracer(consoleTracer);
TraceManager.Start(logger);
});
lifetime.ApplicationStopped.Register(() => TraceManager.Stop());
app.UseTracing(serviceName);//這邊的名字可自定義
return app;
}
}
Exceptionless幫助類
/// <summary>
/// 日誌擴充套件類
/// </summary>
public static class LogHelper
{
/// <summary>
/// 記錄Info日誌
/// </summary>
/// <param name="logger"></param>
/// <param name="message"></param>
public static void InformationToException(this ILogger logger, string message)
{
var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
logger.LogInformation($@"tranceId={tranceId},日誌主體為:{message}");
}
/// <summary>
/// 記錄Debug日誌
/// </summary>
/// <param name="logger"></param>
/// <param name="message"></param>
public static void DebugToException(this ILogger logger, string message)
{
var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
logger.LogDebug($@"tranceId={tranceId},日誌主體為:{message}");
}
/// <summary>
/// 記錄錯誤日誌
/// </summary>
/// <param name="logger"></param>
/// <param name="message"></param>
public static void ErrorToException(this ILogger logger, string message)
{
var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
logger.LogError($@"tranceId={tranceId},日誌主體為:{message}");
}
/// <summary>
/// 記錄追蹤日誌
/// </summary>
/// <param name="logger"></param>
/// <param name="message"></param>
public static void TraceToException(this ILogger logger, string message)
{
var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
logger.LogTrace($@"tranceId={tranceId},日誌主體為:{message}");
}
/// <summary>
/// 記錄警告日誌
/// </summary>
/// <param name="logger"></param>
/// <param name="message"></param>
public static void WarningToException(this ILogger logger, string message)
{
var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
logger.LogWarning($@"tranceId={tranceId},日誌主體為:{message}");
}
}
接下來建立SimpleZipkin、WebApi、OrderApi等專案(因為結構一致,所以本文只建立一個),首先引用Exceplesstion
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging((hostContext, configLogging) =>
{
configLogging.AddConfiguration(hostContext.Configuration.GetSection("Logging"));
configLogging.AddConsole();
configLogging.AddDebug();
configLogging.AddExceptionless();
ExceptionlessClient.Default.Configuration.SetDefaultMinLogLevel(Exceptionless.Logging.LogLevel.Debug);
configLogging.SetMinimumLevel(LogLevel.Debug);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
接下來在Startup中引入ZipKin
public void ConfigureServices(IServiceCollection services)
{
// 注入Rpc
//AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
//services.AddGrpcClient<HelloServer.HelloServerClient>((p, o) =>
//{
// o.Address = new Uri("http://127.0.0.1:3848");
//});
//.AddHttpMessageHandler(provider => TracingHandler.WithoutInnerHandler("RpcService"));
services.AddControllers();
services.AddZipKin();
services.AddSingleton<IDiagnosticSource, HttpDiagnosticSourceDemo>();
services.AddHttpClient("webApi", client => { client.BaseAddress = new Uri($"http://localhost:5001"); });
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "SimpleZipKin", Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory,
IHostApplicationLifetime lifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "SimpleZipkin v1"));
}
Configuration.GetSection("ExceptionLess").Bind(ExceptionlessClient.Default.Configuration);
ExceptionlessClient.Default.Configuration.SetDefaultMinLogLevel(Exceptionless.Logging.LogLevel.Debug);
app.UseZipKin(lifetime, loggerFactory, "SimpleZip", "http://127.0.0.1:9411");//SimpleZip修改為對應的應用名稱,127.0.0.1地址切換為自己的zipkin地址
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
接下來建立對應的Controller
[Route("/api/Home")]
public class HomeController : Controller
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
/// <summary>
/// 建構函式
/// </summary>
/// <param name="httpClientFactory"></param>
/// <param name="logger"></param>
public HomeController(IHttpClientFactory httpClientFactory, ILogger<HomeController> logger)
{
_httpClientFactory = httpClientFactory;
//_helloServerClient = helloServerClient;
_logger = logger;
}
[HttpGet("GetZipKin")]
public async Task<string> GetZipKin()
{
_logger.InformationToException($@"這裡是SimpleZipKinApi");
var httpClient = _httpClientFactory.CreateClient("webApi");
var httpResult = await httpClient.GetAsync($"api/order/getorder");
var result = await httpResult.Content.ReadAsStringAsync();
return result;
}
}
最後在appsettings.json中加入對應的Exceplesstionless配置
"ExceptionLess": {
"ApiKey": "****************************",
"ServerUrl": "http://127.0.0.1:5000"
}
OrderApi、WebApi如法炮製,修改對應的請求鏈路資訊
接下來我們使用DiagnosticAdapter做鏈路記載,在公共類庫中建立HttpDiagnosticListener類
DiagnosticSource是Runtime層提供,應用層可以通過它與系統整合、事件日誌、以及效能計數器進行互動。
DiagnosticSource官方介紹:https://docs.microsoft.com/zh-cn/dotnet/api/system.diagnostics.diagnosticsource?view=net-5.0
關於DiagnosticSource設計參考原有yi念之間大佬的文章:https://www.cnblogs.com/wucy/p/13532534.html
public class HttpDiagnosticSourceDemo : IDiagnosticSourceDemo
{
public string DiagnosticName => "HttpDiagnosticSourceDemo";
private ClientTrace _clientTrace;
private readonly IInjector<HttpHeaders> _injector = Propagations.B3String.Injector<HttpHeaders>((carrier, key, value) => carrier.Add(key, value));
[DiagnosticName("System.Net.Http.Request")]
public void HttpRequest(HttpRequestMessage request)
{
_clientTrace = new ClientTrace("simpleZipKin", request.Method.Method);
if (_clientTrace.Trace != null)
{
_injector.Inject(_clientTrace.Trace.CurrentSpan, request.Headers);
}
}
[DiagnosticName("System.Net.Http.Response")]
public void HttpResponse(HttpResponseMessage response)
{
if (_clientTrace.Trace != null)
{
_clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_PATH, response.RequestMessage.RequestUri.LocalPath));
_clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_METHOD, response.RequestMessage.Method.Method));
_clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_HOST, response.RequestMessage.RequestUri.Host));
if (!response.IsSuccessStatusCode)
{
_clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_STATUS_CODE, ((int)response.StatusCode).ToString()));
}
}
}
[DiagnosticName("System.Net.Http.Exception")]
public void HttpException(HttpRequestMessage request, Exception exception)
{
}
}
IDiagnosticSourceDemo介面資訊如下:
public interface IDiagnosticSourceDemo
{
string DiagnosticName { get; }
}
HttpDiagnosticSourceObserver方法如下:
public class HttpDiagnosticSourceObserver : IObserver<DiagnosticListener>
{
private IEnumerable<IDiagnosticSourceDemo> _diagnosticSourceDemo;
public HttpDiagnosticSourceObserver (IEnumerable<IDiagnosticSourceDemo> diagnosticSourceDemo)
{
_diagnosticSourceDemo= diagnosticSourceDemo;
}
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(DiagnosticListener listener)
{
var diagnosticSource= _diagnosticSourceDemo.FirstOrDefault(i => i.DiagnosticName == listener.Name);
if (traceDiagnostic != null)
{
//適配訂閱
listener.SubscribeWithAdapter(diagnosticSource);
}
}
}
最終執行結果如下:
Zipkin為:
Exceplesstion日誌記錄資訊為
通過Exceptionless記錄的環境資訊也能將對應的機器定位
參考
本文引用文章連線如下:
DiagnosticSource官方介紹:https://docs.microsoft.com/zh-cn/dotnet/api/system.diagnostics.diagnosticsource?view=net-5.0
DiagnosticSource參考連線:https://www.cnblogs.com/wucy/p/13532534.html
https://sudonull.com/post/3671-Using-the-DiagnosticSource-in-NET-Core-Theory
https://andrewlock.net/logging-using-diagnosticsource-in-asp-net-core/
docker部署Zipkin:https://www.cnblogs.com/binz/p/12658020.html
docekr部署exceptionless:https://www.cnblogs.com/edisonchou/p/exceptionless_v5_deployment_introduction.html
如有哪裡講得不是很明白或是有錯誤,歡迎指正
如您喜歡的話不妨點個贊收藏一下吧?