粒度引用

在对粒度调用方法之前,首先需要对该粒度进行引用。 粒度引用是一个代理对象,它实现与相应粒度类相同的力度接口。 它封装目标 grain 的逻辑标识(类型和唯一键)。 粒度引用用于向目标粒度发出调用。 每个 grain 引用指向单个 grain(grain 类的单个实例),但用户可以创建对同一个 grain 的多个独立引用。

由于 grain 引用表示目标 grain 的逻辑标识,因此它独立于 grain 的物理位置,即使在系统完全重启后也仍然有效。 开发人员可以像使用任何其他 .NET 对象一样使用 grain 引用。 它可以传递给方法、用作方法返回值,甚至可以保存到持久性存储中。

可以通过将 grain 的标识传递给 IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) 方法来获取 grain 引用,其中 T 是 grain 接口,key 是 grain 在类型中的唯一键。

下面是如何获取上面定义的 IPlayerGrain 接口的 grain 引用的示例。

从粒度类中:

// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);

从 Orleans 客户端代码:

// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);

粒度引用包含三条信息:

  1. 粒度类型,可唯一标识粒度类。
  2. 粒度键,可唯一标识该粒度类的逻辑实例。
  3. 粒度引用必须实现的接口。

注意

粒度 类型和密钥构成粒度标识

请注意,上述对 IGrainFactory.GetGrain 的调用仅接受这三项中的两项:

  • 由粒度引用实现的接口IPlayerGrain
  • 粒度键,即 playerId 的值。

尽管声明了粒度引用包含粒度类型、键和接口,但示例仅将 Orleans 和键、接口一起提供。 这是因为 Orleans 在粒度接口和粒度类型之间保持映射。 向粒度工厂询问 IShoppingCartGrain时,Orleans 会查阅其映射,查找相应的粒度类型,以令它可以创建引用。 这在只有一个粒度接口的实现时有效,但如果有多个实现,则需要在 GetGrain 调用中消除它们的歧义。 有关详细信息,请参阅下一部分:消除粒度类型解析的歧义

注意

在编译期间,Orleans 为应用程序中的每个粒度接口生成粒度引用实现类型。 这些粒度引用实现继承自 Orleans.Runtime.GrainReference 类。 GetGrain 返回生成的 Orleans.Runtime.GrainReference 实现的实例,与所请求的粒度接口相对应。

消除粒度类型解析

如果粒度接口有多个实现(例如以下示例中所示),则 Orleans 在创建粒度引用时尝试确定预期实现。 请考虑以下示例,其中有两个 ICounterGrain 接口的实现:

public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

public class UpCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

public class DownCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}

以下对 GetGrain 的调用将引发异常,因为 Orleans 不知道如何明确地将 ICounterGrain 映射到其中一个粒度类。

// This will throw an exception: there is no unambiguous mapping from ICounterGrain to a grain class.
ICounterGrain myCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

将引发 System.ArgumentException 和以下消息:

Unable to identify a single appropriate grain type for interface ICounterGrain. Candidates: upcounter (UpCounterGrain), downcounter (DownCounterGrain)

错误消息会说明哪个粒度实现的 Orleans 有与请求的粒度接口类型相匹配的 ICounterGrain。 它会显示粒度类型名称(upcounterdowncounter)以及粒度类(UpCounterGrainDownCounterGrain)。

注意

上述错误消息中的粒度类型名称(upcounterdowncounter)分别派生自粒度类名 UpCounterGrainDownCounterGrain。 这是 Orleans 中的默认行为,可以通过向粒度类添加 [GrainType(string)] 属性来自定义。 例如:

[GrainType("up")]
public class UpCounterGrain : IUpCounterGrain { /* as above */ }

可通过多种方式解决以下小节中详述的这种歧义。

使用唯一标记接口消除粒度类型的歧义

最直白的消除这些粒度的歧义的方法是为它们提供唯一的粒度接口。 例如,如果将接口 IUpCounterGrain 添加到 UpCounterGrain 类并将接口 IDownCounterGrain 添加到 DownCounterGrain 类(如以下示例所示),则可以通过将 IUpCounterGrainIDownCounterGrain 传递给 GetGrain<T> 调用(而不是传递不明确的 ICounterGrain 类型)来解析正确的粒度引用。

public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

// Define unique interfaces for our implementations
public interface IUpCounterGrain : ICounterGrain, IGrainWithStringKey {}
public interface IDownCounterGrain : ICounterGrain, IGrainWithStringKey {}

public class UpCounterGrain : IUpCounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

public class DownCounterGrain : IDownCounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}

若要创建对任一上述粒度的引用,请考虑以下代码:

// Get a reference to an UpCounterGrain.
ICounterGrain myUpCounter = grainFactory.GetGrain<IUpCounterGrain>("my-counter");

// Get a reference to a DownCounterGrain.
ICounterGrain myDownCounter = grainFactory.GetGrain<IDownCounterGrain>("my-counter");

注意

在前面的示例中,你创建了两个具有相同键但粒度类型不同的粒度引用。 第一个存储在 myUpCounter 变量中,是对 ID 为 upcounter/my-counter 的粒度的引用。 第二个存储在 myDownCounter 变量中,是对 ID 为 downcounter/my-counter 的粒度的引用。 它是粒度类型和粒度键的组合,可唯一标识粒度。 因此,myUpCountermyDownCounter 引用不同的粒度。

通过提供粒度类前缀来消除粒度类型的歧义

可以向 IGrainFactory.GetGrain 提供粒度类名称前缀,例如:

ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Up");
ICounterGrain myDownCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Down");

使用命名约定指定默认粒度实现

当消除同一粒度接口的多个实现的歧义时,Orleans 将使用去除接口名称开头的“I”的约定来选择实现。 例如,如果接口名称是 ICounterGrain 并且有两个实现(CounterGrainDownCounterGrain),Orleans 会在请求引用 ICounterGrain 时选择 CounterGrain,如以下示例所示:

/// This will refer to an instance of CounterGrain, since that matches the convention.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

使用属性指定默认粒度类型

可以将 Orleans.Metadata.DefaultGrainTypeAttribute 特性添加到粒度接口,以指定该接口的默认实现的粒度类型,如以下示例所示:

[DefaultGrainType("up-counter")]
public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

[GrainType("up-counter")]
public class UpCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

[GrainType("down-counter")]
public class DownCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}
/// This will refer to an instance of UpCounterGrain, due to the [DefaultGrainType("up-counter"')] attribute
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

通过提供解析的粒度 ID 来消除粒度类型的歧义

IGrainFactory.GetGrain 的某些重载接受 Orleans.Runtime.GrainId 类型的参数。 使用这些重载时,Orleans 不需要从接口类型映射到粒度类型,因此无需解决任何歧义。 例如:

public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

[GrainType("up-counter")]
public class UpCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

[GrainType("down-counter")]
public class DownCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}
// This will refer to an instance of UpCounterGrain, since "up-counter" was specified as the grain type
// and the UpCounterGrain uses [GrainType("up-counter")] to specify its grain type.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>(GrainId.Create("up-counter", "my-counter"));