您現在的位置是:首頁 > 遊戲

如何利用OpenTelemetry識別資料庫依賴關係?

由 51CTO 發表于 遊戲2022-09-23
簡介工資單端點生成的span詳細資訊我們可以從屬性得到一些關鍵資訊:資料庫名稱資料庫操作中使用的SQL語句SQL語句的型別(文字或儲存過程)發出請求的服務的主機名我們從emsbilling操作的子span中找到了一組類似的詳細資訊

資料庫依賴關係是什麼意思

譯者:布加迪

隨著組織將單體應用程式分解成微服務,遇到的主要障礙之一就是識別資料庫依賴關係。

如何利用OpenTelemetry識別資料庫依賴關係?

資料庫共享可能是複雜的挑戰。資料庫不允許您定義什麼是共享的、什麼不是。在修改模式以更好地服務於一個微服務時,您可能無意中破壞另一微服務使用這同一資料庫的方式。

此外,常常很難識別資料所有者、確定處理資料的業務邏輯。

本文探討如何使用OpenTelemetry來識別共享同一資料庫和資料庫物件(比如表)的元件。

可觀察性和OpenTelemetry:基礎

在構建演示應用程式之前,不妨先討論可觀察性和OpenTelemetry。

什麼讓應用程式高度可觀察?

如果可以透過研究在任何時間點的輸出來推斷系統的內部狀態,該系統就被稱為高度可觀察。

比如說,與多個服務互動的可觀察移動應用程式可以重建生成錯誤響應的事務,以便開發人員識別失敗的根本原因。

如何利用OpenTelemetry識別資料庫依賴關係?

圖1。 可觀察應用程式示例

可觀察應用程式為每個事務收集三種類型的資訊:

日誌:記錄構成事務的各個事件。

指標:記錄構成事務的群體事件。

跟蹤:記錄操作延遲,以識別事務中的瓶頸。

OpenTelemetry簡介

OpenTelemetry 是一個以整合方式生成日誌、指標和跟蹤的系統。OpenTelemetry定義了一個標準來捕獲可觀察性資料。OpenTelemetry資料模型有幾個關鍵部分。

屬性

OpenTelemetry中的每個資料結構都由屬性組成,屬性是鍵值對。OpenTelemetry標準定義了任何元件(比如SQL客戶端或HTTP請求)可以指定的屬性。

活動

事件就是時間戳和一組屬性。您可以記錄事件的詳細資訊,比如訊息和異常細節。

上下文

上下文包括一組事件共有的屬性。上下文有兩種:靜態上下文(或資源)定義了事件的位置。在應用程式可執行檔案啟動後,它們的值不變,比如包括服務的名稱或版本或者庫名稱。

動態上下文(或span)定義了包含事件的活動操作。span屬性的值在操作執行時發生變化。一些常見的span屬性包括請求的開始時間、HTTP響應狀態程式碼或HTTP請求路徑。

在分散式事務中,上下文需要傳遞給所有關聯的服務。在這種情況下,接收方使用上下文生成新的span。跨越服務邊界的跟蹤稱為分散式跟蹤,將上下文傳輸到其他服務的過程名為上下文傳播。

日誌

日誌是僅伴隨資源的事件。一個例子是程式啟動時發出的事件。

跟蹤

事件可以組織成與資源相關的操作圖。跟蹤是顯示與事務相關的事件的圖形。

指標

一個事件可能在任何應用程式中發生多次,或者它的值可能會變。指標是一種事件,其值可以是相關事件的計數或事件值的某種計算。指標的一個例子是系統記憶體事件,它的屬性是使用和利用率。

使用OpenTelemetry識別資料庫依賴關係

我們之前討論過,OpenTelemetry規定了應用程式各元件應捕獲的屬性。許多流行語言提供了開箱即用的工具庫,以收集用於資料庫操作的遙測資料。

本文演示使用面向OpenTelemetry的。NET SQLClient工具,以及用於遙測資料儲存和分析的Lightstep。

不妨討論演示應用程式的架構,以瞭解遙測資料到達Lightstep的路徑。我們僅討論跟蹤,因為跟蹤足以識別資料庫和單體元件之間的依賴關係。

然而,任何企業應用程式都會生成相關的日誌和指標以及跟蹤以實現完整的可見性。

如何利用OpenTelemetry識別資料庫依賴關係?

圖2。 從。NET應用程式匯出OTEL跟蹤

首先,我們使用OpenTelemetry SDK檢測單體應用程式,以發出可觀察性訊號。雖然檢測應用程式是。NET應用程式的手動過程,但使用Golang或Java等語言構建的應用程式可使用自動檢測。

我們使用SDK含有的OpenTelemetry Protocol(OTLP)Exporter。該匯出工具讓我們可以將資料直接傳送到遙測資料攝取服務。Jaeger和Lightstep等OpenTelemetry平臺聚合跟蹤,幫助您獲得洞察力。

與SDK整合後,應用程式的各個部分(比如ASP。NET Core請求處理程式和SQL客戶端)會自動開始生成含有相關資訊的跟蹤。您的程式碼可以生成其他跟蹤,以豐富可用資訊。

以。NET為例,OpenTelemetry實現基於System。Diagnostics。*名稱空間中的現有型別,如下所示:

System。Diagnostics。ActivitySource代表負責生成Span的OpenTelemetry跟蹤器。

System。Diagnostics。Activity代表 Span。

您可以使用AddTag函式為span新增屬性。此外,您可以使用AddBaggage功能新增行李。行李被運送到子活動,使用W3C標頭的其他服務中有子活動。

檢測應用程式後,您可以執行自動化測試,或允許使用者使用您的應用程式來覆蓋應用程式和資料庫之間的所有互動路徑。

演示

不妨建立一個簡單的單體員工管理服務(EMS),以SP。NET Core minimal API為模型。我們的API將具有以下端點:

POST /ems/billing:記錄員工為專案所花的工時。

GET /ems/billing/{employeeId}:獲取員工為不同專案所花的工時。

POST /ems/payroll/add:將員工新增到工資單上。

GET /ems/payroll/{employeeId}:獲取員工的工資單資料。

您會注意到單體應用程式服務於兩個不同的領域:計費和工資單。這種依賴關係在複雜的單體應用程式中可能不是很明顯,將它們分離開來可能需要大量的程式碼重構。

然而,如果研究依賴關係,您可以輕鬆地將它們分離開來。EMS應用程式的完整原始碼可在該GitHub儲存庫中找到。

啟動資料庫

我們先在Docker中啟動一個SQL server例項:

複製

docker run \-e “ACCEPT_EULA=Y” \-e “SA_PASSWORD=Str0ngPa$$w0rd” \-p 1433:1433 \——name monolith-db \——hostname sql1 \-d mcr。microsoft。com/mssql/server:2019-latest1。2。3。4。5。6。7。

我們使用下列SQL指令碼來建立我們的應用程式所使用的EMS資料庫和表:

複製

IF NOT EXISTS(SELECT * FROM sys。databases WHERE name = ‘EMSDb’)BEGIN CREATE DATABASE EMSDbENDGOUSE EMSDbIF OBJECT_ID(‘[dbo]。[Timekeeping]’, ‘U’) IS NULLBEGIN CREATE TABLE [Timekeeping] ( [EmployeeId] INT NOT NULL, [ProjectId] INT NOT NULL, [WeekClosingDate] DATETIME NOT NULL, [HoursWorked] INT NOT NULL, CONSTRAINT [PK_Timekeeping] PRIMARY KEY CLUSTERED ([EmployeeId] ASC, [ProjectId] ASC, [WeekClosingDate] ASC) )ENDGOIF OBJECT_ID(‘[dbo]。[Payroll]’, ‘U’) IS NULLBEGIN CREATE TABLE [Payroll] ( [EmployeeId] INT NOT NULL, [PayRateInUSD] MONEY DEFAULT 0 NOT NULL, CONSTRAINT [PK_Payroll] PRIMARY KEY CLUSTERED ([EmployeeId] ASC) )ENDGO1。2。3。4。5。6。7。8。9。10。11。12。13。14。15。16。17。18。19。20。21。22。23。24。25。26。27。28。29。

實現API服務

接下來,我們為API端點編寫程式碼。我們把Program類中的樣板程式碼換成以下程式碼:

複製

var builder = WebApplication。CreateBuilder(args);builder。Services。AddScoped(_ => new SqlConnection(builder。Configuration。GetConnectionString(“EmployeeDbConnectionString”)));var app = builder。Build();app。UseSwagger();app。UseSwaggerUI();app。MapPost(“/ems/billing”, async (Timekeeping timekeepingRecord, SqlConnection db) => { await db。ExecuteAsync( “INSERT INTO Timekeeping Values(@EmployeeId, @ProjectId, @WeekClosingDate, @HoursWorked)”, timekeepingRecord); return Results。Created($“/ems/billing/{timekeepingRecord。EmployeeId}”, timekeepingRecord); }) 。WithName(“RecordProjectWork”) 。Produces(StatusCodes。Status201Created);app。MapGet(“/ems/billing/{empId}/”, async (int empId, SqlConnection db) => { var result = await db。QueryAsync(“SELECT * FROM Timekeeping WHERE EmployeeId=@empId”, empId); return result。Any() ? Results。Ok(result) : Results。NotFound(); }) 。WithName(“GetBillingDetails”) 。Produces>() 。Produces(StatusCodes。Status404NotFound);app。MapPost(“/ems/payroll/add/”, async (Payroll payrollRecord, SqlConnection db) => { await db。ExecuteAsync( “INSERT INTO Payroll Values(@EmployeeId, @PayRateInUSD)”, payrollRecord); return Results。Created($“/ems/payroll/{payrollRecord。EmployeeId}”, payrollRecord); }) 。WithName(“AddEmployeeToPayroll”) 。Produces(StatusCodes。Status201Created);app。MapGet(“/ems/payroll/{empId}”, async (int empId, SqlConnection db) => { var result = await db。QueryAsync(“SELECT * FROM Payroll WHERE EmployeeId=@empId”, empId); return result。Any() ? Results。Ok(result) : Results。NotFound(); }) 。WithName(“GetEmployeePayroll”) 。Produces>() 。Produces(StatusCodes。Status404NotFound);app。Run();public class Timekeeping{ public int EmployeeId { get; set; } public int ProjectId { get; set; } public DateTime WeekClosingDate { get; set; } public int HoursWorked { get; set; }}public class Payroll{ public int EmployeeId { get; set; } public decimal PayRateInUSD { get; set; }}1。2。3。4。5。6。7。8。9。10。11。12。13。14。15。16。17。18。19。20。21。22。23。24。25。26。27。28。29。30。31。32。33。34。35。36。37。38。39。40。41。42。43。44。45。46。47。48。49。50。51。52。53。54。55。56。57。58。59。60。61。

此時,我們可以執行應用程式,測試各端點,並檢視儲存在資料庫中的記錄。雖然各端點和請求路徑的資料庫依賴關係在這個演示示例中很明顯,但在大型應用程式中實際情況並非如此。

接下來,不妨使發現數據庫依賴關係的過程實現自動化。

新增檢測

我們使用OpenTelemetry SDK和麵向。NET的SqlClient檢測庫來檢測應用程式。我們先將以下NuGet包引用新增到API的專案檔案中:

複製

1。2。3。4。5。6。

SDK為我們提供了幾種擴充套件方法,我們可以使用這些方法將OpenTelemetry快速接入到請求處理管道。

以下程式碼在我們的API中檢測OpenTelemetry。它還將檢測SqlClient以發出詳細的遙測資料。來自SqlClient的遙測資料是詳細識別資料庫依賴關係的關鍵。

複製

// Configure tracingbuilder。Services。AddOpenTelemetryTracing(builder => builder // Customize the traces gathered by the HTTP request handler 。AddAspNetCoreInstrumentation(options => { // Only capture the spans generated from the ems/* endpoints options。Filter = context => context。Request。Path。Value?。Contains(“ems”) ?? false; options。RecordException = true; // Add metadata for the request such as the HTTP method and response length options。Enrich = (activity, eventName, rawObject) => { switch (eventName) { case “OnStartActivity”: { if (rawObject is not HttpRequest httpRequest) { return; } activity。SetTag(“requestProtocol”, httpRequest。Protocol); activity。SetTag(“requestMethod”, httpRequest。Method); break; } case “OnStopActivity”: { if (rawObject is HttpResponse httpResponse) { activity。SetTag(“responseLength”, httpResponse。ContentLength); } break; } } }; }) // Customize the telemetry generated by the SqlClient 。AddSqlClientInstrumentation(options => { options。EnableConnectionLevelAttributes = true; options。SetDbStatementForStoredProcedure = true; options。SetDbStatementForText = true; options。RecordException = true; options。Enrich = (activity, x, y) => activity。SetTag(“db。type”, “sql”); }) 。AddSource(“my-corp。ems。ems-api”) // Create resources (key-value pairs) that describe your service such as service name and version 。SetResourceBuilder(ResourceBuilder。CreateDefault()。AddService(“ems-api”) 。AddAttributes(new[] { new KeyValuePair(“service。version”, “1。0。0。0”) })) // Ensures that all activities are recorded and sent to exporter 。SetSampler(new AlwaysOnSampler()) // Exports spans to Lightstep 。AddOtlpExporter(otlpOptions => { otlpOptions。Endpoint = new Uri(“https://ingest。lightstep。com:443/traces/otlp/v0。9”); otlpOptions。Headers = $“lightstep-access-token={lsToken}”; otlpOptions。Protocol = OtlpExportProtocol。HttpProtobuf; }));1。2。3。4。5。6。7。8。9。10。11。12。13。14。15。16。17。18。19。20。21。22。23。24。25。26。27。28。29。30。31。32。33。34。35。36。37。38。39。40。41。42。43。44。45。46。47。48。49。50。51。52。53。54。55。56。57。58。59。

雖然檢測在當前狀態下對我們來說足夠了,還是不妨新增相關跟蹤,進一步豐富資料。

首先,我們定義跟蹤器,應用程式的span將來自該跟蹤器。

複製

var activitySource = new ActivitySource(“my-corp。ems。ems-api”);1。

接下來,我們建立一個span,並新增相關細節、屬性和事件:

複製

app。MapPost(“/ems/billing”, async (Timekeeping timekeepingRecord, SqlConnection db) => { using var activity = activitySource。StartActivity(“Record project work”, ActivityKind。Server); activity?。AddEvent(new ActivityEvent(“Project billed”)); activity?。SetTag(nameof(Timekeeping。EmployeeId), timekeepingRecord。EmployeeId); activity?。SetTag(nameof(Timekeeping。ProjectId), timekeepingRecord。ProjectId); activity?。SetTag(nameof(Timekeeping。WeekClosingDate), timekeepingRecord。WeekClosingDate); await db。ExecuteAsync( “INSERT INTO Timekeeping Values(@EmployeeId, @ProjectId, @WeekClosingDate, @HoursWorked)”, timekeepingRecord); return Results。Created($“/ems/billing/{timekeepingRecord。EmployeeId}”, timekeepingRecord); }) 。WithName(“RecordProjectWork”) 。Produces(StatusCodes。Status201Created);1。2。3。4。5。6。7。8。9。10。11。12。13。14。15。

我們遵循同樣的程式來檢測剩餘的端點。

連線到Lightstep

最後,我們需要一個API金鑰將跟蹤資訊傳送到Lightstep。我們先建立一個帳戶。在賬戶的Project設定頁面中,我們找到令牌(Token),它將充當API金鑰。

如何利用OpenTelemetry識別資料庫依賴關係?

圖3。 Lightstep中的API金鑰

我們複製令牌,並將它貼上到appsettings檔案中。

複製

{ “Logging”: { “LogLevel”: { “Default”: “Information”, “Microsoft。AspNetCore”: “Warning” } }, “AllowedHosts”: “*”, “ConnectionStrings”: { “EmployeeDbConnectionString”: “Server=localhost;Database=EMSDb;User Id=sa;Password=Str0ngPa$$w0rd;” }, “LsToken”: “”}1。2。3。4。5。6。7。8。9。10。11。12。13。

傳送請求

我們的應用程式已準備就緒。我們啟動應用程式,向每個端點發送一些請求。以下是我傳送到/ems/billing端點的請求。該請求應在資料庫的Timekeeping表中建立一條記錄。

如何利用OpenTelemetry識別資料庫依賴關係?

圖4。 將請求傳送到計費端點

這是我向/emp/payroll/add端點發出的另一個請求,用於將記錄新增到Payroll表:

如何利用OpenTelemetry識別資料庫依賴關係?

圖5。 將請求傳送到工資單端點

進入到Lightstep可觀察性入口網站後,我們可以點選Operations選項卡,檢視Lightstep從應用程式接收到的所有span。

如何利用OpenTelemetry識別資料庫依賴關係?

圖6。 Lightstep中檢視來自應用程式的span

我們點選/ems/payroll/add操作後,可以檢視端到端跟蹤。透過檢視span,我們可以確定任何請求的操作順序。點選span可顯示其事件和屬性,從中我們可以更深入地瞭解操作。

跟蹤中可見的最後一個span是EMSDb,它是由我們檢測的SQL客戶端生成的。點選span可檢視其屬性和事件,如下所示:

如何利用OpenTelemetry識別資料庫依賴關係?

圖7。 工資單端點生成的span詳細資訊

我們可以從屬性得到一些關鍵資訊:

資料庫名稱

資料庫操作中使用的SQL語句

SQL語句的型別(文字或儲存過程)

發出請求的服務的主機名

我們從/ems/billing操作的子span中找到了一組類似的詳細資訊。

如何利用OpenTelemetry識別資料庫依賴關係?

圖8。 計費端點生成的span的詳細資訊

如果梳理來自跟蹤的資訊,我們可以推斷出以下內容:

入站操作(接收外部請求的操作)

完成請求的一系列活動,包括外部服務呼叫和資料庫操作。

每個操作涉及的資料庫操作。

總之,這些資訊足以讓我們規劃服務和資料庫的分離,併為微服務之間的通訊建立聯絡。

結論

本文討論了開發人員將單體應用程式轉換成微服務時遇到的常見挑戰之一。在所有問題中,拆分資料庫是一項複雜的工作,因為訪問資料庫的任何服務都可以處理資料庫。

透過使用OpenTelemetry,我們可以識別各元件之間以及元件與資料庫之間的依賴關係。 瞭解依賴關係後,我們可以為自己的元件制定重構計劃,規劃它們作為獨立的微服務應如何與時俱進。

推薦文章