无状态工作线程 grain

默认情况下,Orleans 运行时不会在群集中创建 grain 的多个激活。 这是虚拟执行组件模型最直观的表达方式,其中每个 grain 对应于一个具有唯一类型/标识的实体。 但在某些情况下,应用程序需要执行与系统中特定实体不相关的功能性无状态操作。 例如,如果客户端发送包含压缩有效负载的请求,而在将这些有效负载路由到目标 grain 进行处理之前需要将其解压缩,那么,此类解压缩/路由逻辑并不会关联到应用程序中的特定实体,因此可以轻松横向扩展。

StatelessWorkerAttribute 应用于 grain 类时,它会向 Orleans 运行时指示该类的 grain 应被视为无状态工作线程 grain。 无状态工作线程 grain 具有以下属性,使其执行与普通 grain 类的执行有很大的不同。

  1. Orleans 运行时可以并且会在群集的不同接收器上创建多个无状态工作线程 grain 的激活。
  2. 只要接收器兼容,向无状态工作线程粒度发出的请求就在本地执行,因此它们不会产生网络或序列化成本。 如果本地接收器不兼容,会将请求转发到兼容的接收器。
  3. 如果现有的激活繁忙,Orleans 运行时会自动创建无状态工作程序 grain 的更多激活。 默认情况下,运行时为每个 silo 创建的无状态工作线程 grain 的最大激活数量受计算机上 CPU 核心数的限制,除非通过可选的 maxLocalWorkers 参数显式指定激活数量。
  4. 由于第 2 和第 3 点,无状态工作线程 grain 激活不可单独寻址。 对无状态工作线程 grain 的两个后续请求可由该 grain 的不同激活进行处理。

无状态工作线程 grain 提供了一种创建自动管理的 grain 激活池的直接方法,该池可根据实际负载自动纵向扩展和缩减。 运行时始终按相同的顺序扫描可用的无状态工作线程 grain 激活。 因此,它始终将请求分派给它可以找到的第一个空闲本地激活,并且只有在所有先前的激活都繁忙时才会分派给最后一个激活。 如果所有激活都处于繁忙状态且未达到激活限制,则运行时会在列表末尾再创建一个激活,并将请求分派给它。 这意味着,当对无状态工作线程 grain 的请求速率增大并且现有激活目前都处于繁忙状态时,运行时会将其激活池扩展到极限。 相反,当负载下降时,可以通过减少无状态工作线程 grain 激活的数量来处理请求,不会向列表尾部的激活分派请求。 尾部的这些激活将变为空闲状态,并最终由标准激活收集进程取消激活。 因此,激活池最终将会收缩以匹配负载。

以下示例定义了使用默认最大激活数限制的无状态工作线程 grain 类 MyStatelessWorkerGrain

[StatelessWorker]
public class MyStatelessWorkerGrain : Grain, IMyStatelessWorkerGrain
{
    // ...
}

调用无状态工作线程 grain 的方式与调用任何其他 grain 相同。 唯一的区别是,在大多数情况下会使用单个粒度 ID,例如 0Guid.Empty。 当有多个无状态工作线程 grain 池时,可以使用多个 grain ID,最好是每个 ID 有一个池。

var worker = GrainFactory.GetGrain<IMyStatelessWorkerGrain>(0);
await worker.Process(args);

此池定义了一个无状态工作线程 grain 类,并定义了每个 silo 不能有多个 grain 激活。

[StatelessWorker(1)] // max 1 activation per silo
public class MyLonelyWorkerGrain : ILonelyWorkerGrain
{
    //...
}

请注意,StatelessWorkerAttribute 不会改变目标 grain 类的可重入性。 与任何其他 grain 一样,无状态工作线程 grain 默认是不可重入的。 可以通过将 ReentrantAttribute 添加到 grain 类来显式使其可重入。

州省/自治区/直辖市

“无状态工作线程”的“无状态”部分并不是指无状态工作线程不能有状态而仅限执行功能操作。 与任何其他 grain 一样,无状态工作线程 grain 可以加载它所需的任何状态并将其保存在内存中。 只是因为可以在群集的相同和不同 silo 上创建无状态工作线程 grain 的多个激活,因此没有任何简单的机制可以协调不同激活持有的状态。

有多种有用模式涉及到持有状态的无状态工作线程。

横向扩展的热缓存项

对于面临高吞吐量的热缓存项,将每个此类项保存在无状态工作线程 grain 中能使该项:

  1. 在某个 silo 中以及群集上的所有 silo 中自动横向扩展,并且
  2. 始终在通过客户端网关收到客户端请求的 silo 本地提供数据,因此无需与另一个 silo 建立额外的网络跃点即可应答请求。

减少样式聚合

在某些情况下,应用程序需要计算群集中特定类型的所有 grain 的某些指标,并定期报告聚合。 示例包括报告每个游戏地图的多个玩家、VoIP 通话的平均持续时间,等等。如果有数千甚至数百万个 grain,而其中每个 grain 都要将其指标报告给单个全局聚合器,则聚合器马上就会过载,无法处理铺天盖地的报告。 替代的方法是将此任务转化为 2 个(或更多)步骤来减少样式聚合。 第一层聚合是通过将指标发送到无状态工作线程预聚合 grain 的报告 grain 来完成的。 Orleans 运行时将自动为每个接收器创建多个无状态工作线程 grain 激活。 由于所有此类调用都在本地处理,而无需进行远程调用或消息序列化,因此,此类聚合的成本远低于远程调用。 现在,每个预聚合无状态工作线程 grain 激活(独立状态或与其他本地激活协调)可将其聚合报告发送到全局最终聚合器(或在必要时发送到另一个缩减层),而不会导致该聚合器过载。