請求上下文

RequestContext 是一項 Orleans 功能,允許應用程式元數據,例如追蹤 ID,在請求中傳遞。 您可以在用戶端上新增應用程式元數據,它會隨著 Orleans 要求流向接收目標。 此功能是由 RequestContext 命名空間中的公用靜態類別 Orleans 所實作。 此類別會公開兩個簡單的方法:

void Set(string key, object value)

使用上述 API 將值儲存在要求內容中。 此值可以是任何可序列化型別。

object Get(string key)

使用上述 API 從目前的要求內容擷取值。

RequestContext 的儲存體是非同步且本地性。 當呼叫端 (用戶端或內部 Orleans) 傳送要求時,呼叫端 RequestContext 的內容會包含在 Orleans 要求的訊息中。 當粒紋碼收到要求時,可從本機 RequestContext存取該元數據。 如果穀物代碼沒有修改RequestContext,則其請求的任何穀物都會收到相同的元數據,依此類推。

當您使用 StartNew 或 ContinueWith排程未來的計算時,也會維護應用程式元數據。 在這兩種情況下,延續的程式執行時會使用與計算被排程時相同的元數據。 也就是說,系統會複製目前的元數據,並將其傳遞至接續,因此接續不會在呼叫 StartNew 或 ContinueWith之後看到所做的變更。

重要

應用程式元數據不會隨著回應而回流。 由於接收到回應而執行的程式代碼(無論是在 ContinueWith 的延續中,或在呼叫 Wait 或 Result 之後),仍會在原始要求所設定的當前上下文中執行。

例如,若要將用戶端中的追蹤識別碼設定為新的 System.Guid,請呼叫:

RequestContext.Set("TraceId", Guid.NewGuid());

在粒紋程式代碼內(或其他在排程器線程上 Orleans 執行的程式代碼),您可以在撰寫記錄檔時,使用原始用戶端要求的追蹤識別碼:

Logger.LogInformation(
    "Currently processing external request {TraceId}",
    RequestContext.Get("TraceId"));

雖然您可以傳送任何可串行化的object作為應用程式的元數據,但值得一提的是,大型或複雜的物件可能會使訊息串行化時間增加明顯的額外負荷。 基於這個理由,我們建議使用簡單類型(字串、GUID 或數值類型)。

範例紋理程式碼

為了協助說明請求上下文的使用,請考慮下列範例程式碼:

using GrainInterfaces;
using Microsoft.Extensions.Logging;

namespace Grains;

public class HelloGrain(ILogger<HelloGrain> logger) : Grain, IHelloGrain
{
    ValueTask<string> IHelloGrain.SayHello(string greeting)
    {
        _logger.LogInformation("""
            SayHello message received: greeting = "{Greeting}"
            """,
            greeting);

        var traceId = RequestContext.Get("TraceId") as string
            ?? "No trace ID";

        return ValueTask.FromResult($"""
            TraceID: {traceId}
            Client said: "{greeting}", so HelloGrain says: Hello!
            """);
    }
}

public interface IHelloGrain : IGrainWithStringKey
{
    ValueTask<string> SayHello(string greeting);
}

方法 SayHello 會記錄傳入 greeting 參數,然後從要求內容擷取追蹤標識碼。 如果找不到追蹤標識碼,穀粒會記錄「無追蹤標識碼」。

範例客戶端程序代碼

用戶端可以在要求內容中設定追蹤標識碼,然後再呼叫 SayHello 上的 HelloGrain方法。 下列用戶端程式碼示範如何在請求上下文中設定追蹤 ID,並於 SayHello 上呼叫 HelloGrain 方法:

using GrainInterfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using var host = Host.CreateDefaultBuilder(args)
    .UseOrleansClient(clientBuilder =>
        clientBuilder.UseLocalhostClustering())
    .Build();

await host.StartAsync();

var client = host.Services.GetRequiredService<IClusterClient>();

var grain = client.GetGrain<IHelloGrain>("friend");

var id = "example-id-set-by-client";

RequestContext.Set("TraceId", id);

var message = await friend.SayHello("Good morning!");

Console.WriteLine(message);
// Output:
//   TraceID: example-id-set-by-client
//   Client said: "Good morning!", so HelloGrain says: Hello!

在此範例中,用戶端會先將追蹤標識符設定為「example-id-set-by-client」,再在SayHello上呼叫HelloGrain方法。 Grain 會從請求上下文擷取追蹤 ID 並記錄下來。

範例放置存取碼

RequestContext資料可在放置或過濾過程中透過PlacementTargetRequestContextData進行存取。 目前靜電 RequestContext 尚未被填滿,因為顆粒尚未被激活。 以下配置過濾器程式碼示範如何在處理位置時取得 RequestContext 資料:

internal sealed class ExamplePlacementFilterDirector(ILogger<ExamplePlacementFilterDirector> logger)
    : IPlacementFilterDirector
{
    public IEnumerable<SiloAddress> Filter(
        PlacementFilterStrategy filterStrategy,
        PlacementTarget target,
        IEnumerable<SiloAddress> silos)
    {
        if (target.RequestContextData.TryGetValue("somekey", out var somevalue) 
            && somevalue is string somestring)
        {
            logger.LogInformation("Read {Value} for {Key} from the RequestContext", somestring, "somekey");
            // somestring is available for the filtering logic
        }
        return silos;
    }
}

在這個例子中,「somekey」值是在放置過濾過程中從PlacementTargetRequestContextData中讀取的。