请求上下文

RequestContext 是 Orleans 的一项功能,它使应用程序元数据(例如跟踪 ID)能够与请求一起流动。 可以在客户端添加应用程序元数据;它们将与 Orleans 请求一起流向接收方 grain。 该功能由 Orleans 命名空间中的公共静态类 RequestContext 实现。 此类公开两个简单方法:

void Set(string key, object value)

上面的 API 用于在请求上下文中存储一个值。 该值可以是任何可序列化类型。

object Get(string key)

上面的 API 用于从当前请求上下文中检索一个值。

RequestContext 的后备存储是异步本地存储。 当某个调用方(不管是客户端还是 Orleans 内部)发送请求时,调用方的 RequestContext 的内容将包含在该请求的 Orleans 消息中;当 grain 代码接收请求时,可以从本地 RequestContext 访问该元数据。 如果 grain 代码未修改 RequestContext,则它请求的任何 grain 都将收到相同的元数据,依此类推。

当使用 StartNewContinueWith 计划将来的计算时,也会保留应用程序元数据;在这两种情况下,延续将使用计划代码在计划计算时具有的相同元数据来执行(也就是说,系统会复制当前元数据并将其传递给延续,因此在调用 StartNewContinueWith 后,延续不会看到更改)。

重要

应用程序元数据不会随响应一起流回;也就是说,由于接收响应(在 ContinueWith 延续中或者在调用 Task.Wait()GetValue 之后)而运行的代码仍将在原始请求设置的当前上下文中运行。

例如,若要将客户端中的跟踪 ID 设置为新 GuidID,请调用:

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

在 grain 代码(或者在 Orleans 中的计划程序线程上运行的其他代码)中,可以使用原始客户端请求的跟踪 ID,例如,在写入日志时:

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 参数,然后从请求上下文中检索跟踪 ID。 如果未找到跟踪 ID,则粒度会记录“无跟踪 ID”。

示例客户端代码

在调用SayHelloHelloGrain方法之前,客户端能够在请求上下文中设置跟踪 ID。 以下客户端代码演示如何在请求上下文中设置跟踪 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!

在此示例中,客户端在调用 SayHello 方法 HelloGrain之前将跟踪 ID 设置为“example-id-set-by-client”。 粒度从请求上下文中检索跟踪 ID 并记录它。