粒度放置

Orleans 确保在进行 grain 调用时,群集中某些服务器的内存中提供该 grain 的实例来处理请求。 如果此 grain 当前在群集中未处于活动状态,则 Orleans 会选取其中一台服务器来激活 grain。 这称为粒度放置。 放置也是负载均衡的一种方式:即使繁忙粒度的放置也有助于均衡整个群集中的工作负载。

Orleans 的放置过程是完全可配置的:开发人员可以从一组现成的放置策略中进行选择,例如随机、本地优先和基于负载,或者可以配置自定义逻辑。 这样,就可以十分灵活地确定要在哪个位置创建粒度。 例如,可将粒度放置在与它们需要操作的资源相互靠近的服务器上,或者放置在与它们通信的其他粒度靠近的服务器上。 默认情况下,Orleans 将选取随机兼容的服务器。

Orleans 使用的放置策略可以全局或根据各个 grain 类进行配置。

随机放置

从群集中的兼容服务器随机选择服务器。 通过向粒度添加 RandomPlacementAttribute 来配置此放置策略。

本地放置

如果本地服务器兼容,请选择本地服务器,否则选择随机服务器。 通过向粒度添加 PreferLocalPlacementAttribute 来配置此放置策略。

基于哈希的放置

将粒度 ID 哈希到非负整数,并使用兼容的服务器数对其进行取模。 从按服务器地址排序的兼容服务器列表中选择相应的服务器。 请注意,这不能保证在群集成员身份发生变化时保持稳定。 具体而言,添加、删除或重启服务器可以更改为给定粒度 ID 选择的服务器。由于使用此策略放置的粒度在粒度目录中注册,因此,在成员关系更改时放置决策中的这种更改通常不会产生显著的影响。

通过向粒度添加 HashBasedPlacementAttribute 来配置此放置策略。

基于激活计数的放置

此放置策略旨在根据最近繁忙的粒度数,将新粒度激活放置在负载最少的服务器上。 它包含一种机制,其中所有服务器定期将其总激活计数发布到所有其他服务器。 然后,放置控制器通过检查最近报告的激活计数,并根据放置控制器在当前服务器上所做的最近激活计数对当前激活计数的预测,从而选择一个预计激活数最少的服务器。 控制器在进行此预测时随机选择多个服务器,试图避免多个单独的服务器重载同一服务器。 默认情况下,会随机选择两个服务器,但此值可通过 ActivationCountBasedPlacementOptions 进行配置。

此算法基于 Michael David Mitzenmacher 撰写的“随机负载均衡中的两种选择的力量”论文,还用于 Nginx 进行分布式负载均衡,如文章 NGINX 和“两种选择的力量”负载均衡算法中所述。

通过向粒度添加 ActivationCountBasedPlacementAttribute 来配置此放置策略。

无状态辅助角色放置

无状态辅助角色放置是无状态辅助角色粒度使用的特殊放置策略。 此放置与 PreferLocalPlacement 几乎完全相同,只是每个服务器可以有同一粒度的多个激活,并且粒度在粒度目录中没有注册,因为不需要。

通过向粒度添加 StatelessWorkerAttribute 来配置此放置策略。

基于接收器角色的放置

一种确定性放置策略,用于将粒度放置在具有特定角色的接收器上。 通过向粒度添加 SiloRoleBasedPlacementAttribute 来配置此放置策略。

选择放置策略

要在 Orleans 提供的默认值以外选择适当的 grain 放置策略,需要监视和开发人员评估。 放置策略的选择应基于应用的大小和复杂性、工作负载特征和部署环境。

随机放置依赖于大数定律,因此当不可预测的负载分布在大量粒度(超过 10,000)间时,这通常是很好的默认值。

基于激活计数的放置还具有一个随机元素(依赖于双选项作用原则),这是一种常用于分布式负载均衡的算法,且用于常用负载均衡器。 接收器经常将运行时统计信息发布到群集中的其他接收器,包括:

  • 可用内存、总物理内存和内存使用情况。
  • CPU 使用率。
  • 总激活计数和最近活动激活计数。
    • 在过去几秒内处于活动状态的激活滑动窗口,有时称为激活工作集。

根据这些统计信息,当前仅使用激活计数来确定给定接收器上的负载。

最终,应尝试不同的策略并监视性能指标,以确定最合适的策略。 通过选择合适的 grain 放置策略,可以优化 Orleans 应用的性能、可伸缩性和成本效益。

配置默认放置策略

Orleans 将使用随机放置,除非重写默认值。 可以通过在配置期间注册实现 PlacementStrategy 来重写默认放置策略:

siloBuilder.ConfigureServices(services =>
    services.AddSingleton<PlacementStrategy, MyPlacementStrategy>());

配置粒度放置策略

通过在粒度类上添加适当的属性来配置粒度类型的放置策略。 相关属性在放置策略部分指定。

自定义放置策略示例

首先定义实现 IPlacementDirector 接口的类,这需要单个方法。 在此示例中,假设你定义了函数 GetSiloNumber,它将返回给定将要创建的粒度的 Guid 的接收器编号。

public class SamplePlacementStrategyFixedSiloDirector : IPlacementDirector
{
    public Task<SiloAddress> OnAddActivation(
        PlacementStrategy strategy,
        PlacementTarget target,
        IPlacementContext context)
    {
        var silos = context.GetCompatibleSilos(target).OrderBy(s => s).ToArray();
        int silo = GetSiloNumber(target.GrainIdentity.PrimaryKey, silos.Length);

        return Task.FromResult(silos[silo]);
    }
}

需要定义两个类,以允许将粒度类分配给策略:

[Serializable]
public sealed class SamplePlacementStrategy : PlacementStrategy
{
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class SamplePlacementStrategyAttribute : PlacementAttribute
{
    public SamplePlacementStrategyAttribute() :
        base(new SamplePlacementStrategy())
    {
    }
}

然后,只需标记想要将此策略与属性一起使用的任何粒度类:

[SamplePlacementStrategy]
public class MyGrain : Grain, IMyGrain
{
    // ...
}

最后,在生成 SiloHost 时注册策略:

private static async Task<ISiloHost> StartSilo()
{
    var builder = new HostBuilder(c =>
    {
        // normal configuration methods omitted for brevity
        c.ConfigureServices(ConfigureServices);
    });

    var host = builder.Build();
    await host.StartAsync();

    return host;
}

private static void ConfigureServices(IServiceCollection services)
{
    services.AddSingletonNamedService<
        PlacementStrategy, SamplePlacementStrategy>(
            nameof(SamplePlacementStrategy));

    services.AddSingletonKeyedService<
        Type, IPlacementDirector, SamplePlacementStrategyFixedSiloDirector>(
            typeof(SamplePlacementStrategy));
}

有关显示进一步使用放置上下文的第二个简单示例,请参阅 Orleans 源存储库中的 PreferLocalPlacementDirector