无状态工作线程 grain
默认情况下,Orleans 运行时不会在群集中创建 grain 的多个激活。 这是虚拟执行组件模型最直观的表达方式,其中每个 grain 对应于一个具有唯一类型/标识的实体。 但在某些情况下,应用程序需要执行与系统中特定实体不相关的功能性无状态操作。 例如,如果客户端发送包含压缩有效负载的请求,而在将这些有效负载路由到目标 grain 进行处理之前需要将其解压缩,那么,此类解压缩/路由逻辑并不会关联到应用程序中的特定实体,因此可以轻松横向扩展。
将 StatelessWorkerAttribute 应用于 grain 类时,它会向 Orleans 运行时指示该类的 grain 应被视为无状态工作线程 grain。 无状态工作线程 grain 具有以下属性,使其执行与普通 grain 类的执行有很大的不同。
- Orleans 运行时可以并且会在群集的不同接收器上创建多个无状态工作线程 grain 的激活。
- 只要接收器兼容,向无状态工作线程粒度发出的请求就在本地执行,因此它们不会产生网络或序列化成本。 如果本地接收器不兼容,会将请求转发到兼容的接收器。
- 如果现有的激活繁忙,Orleans 运行时会自动创建无状态工作程序 grain 的更多激活。
默认情况下,运行时为每个 silo 创建的无状态工作线程 grain 的最大激活数量受计算机上 CPU 核心数的限制,除非通过可选的
maxLocalWorkers
参数显式指定激活数量。 - 由于第 2 和第 3 点,无状态工作线程 grain 激活不可单独寻址。 对无状态工作线程 grain 的两个后续请求可由该 grain 的不同激活进行处理。
无状态工作线程 grain 提供了一种创建自动管理的 grain 激活池的直接方法,该池可根据实际负载自动纵向扩展和缩减。 运行时始终按相同的顺序扫描可用的无状态工作线程 grain 激活。 因此,它始终将请求分派给它可以找到的第一个空闲本地激活,并且只有在所有先前的激活都繁忙时才会分派给最后一个激活。 如果所有激活都处于繁忙状态且未达到激活限制,则运行时会在列表末尾再创建一个激活,并将请求分派给它。 这意味着,当对无状态工作线程 grain 的请求速率增大并且现有激活目前都处于繁忙状态时,运行时会将其激活池扩展到极限。 相反,当负载下降时,可以通过减少无状态工作线程 grain 激活的数量来处理请求,不会向列表尾部的激活分派请求。 尾部的这些激活将变为空闲状态,并最终由标准激活收集进程取消激活。 因此,激活池最终将会收缩以匹配负载。
以下示例定义了使用默认最大激活数限制的无状态工作线程 grain 类 MyStatelessWorkerGrain
。
[StatelessWorker]
public class MyStatelessWorkerGrain : Grain, IMyStatelessWorkerGrain
{
// ...
}
调用无状态工作线程 grain 的方式与调用任何其他 grain 相同。
唯一的区别是,在大多数情况下会使用单个粒度 ID,例如 0
或 Guid.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 中能使该项:
- 在某个 silo 中以及群集上的所有 silo 中自动横向扩展,并且
- 始终在通过客户端网关收到客户端请求的 silo 本地提供数据,因此无需与另一个 silo 建立额外的网络跃点即可应答请求。
减少样式聚合
在某些情况下,应用程序需要计算群集中特定类型的所有 grain 的某些指标,并定期报告聚合。 示例包括报告每个游戏地图的多个玩家、VoIP 通话的平均持续时间,等等。如果有数千甚至数百万个 grain,而其中每个 grain 都要将其指标报告给单个全局聚合器,则聚合器马上就会过载,无法处理铺天盖地的报告。 替代的方法是将此任务转化为 2 个(或更多)步骤来减少样式聚合。 第一层聚合是通过将指标发送到无状态工作线程预聚合 grain 的报告 grain 来完成的。 Orleans 运行时将自动为每个接收器创建多个无状态工作线程 grain 激活。 由于所有此类调用都在本地处理,而无需进行远程调用或消息序列化,因此,此类聚合的成本远低于远程调用。 现在,每个预聚合无状态工作线程 grain 激活(独立状态或与其他本地激活协调)可将其聚合报告发送到全局最终聚合器(或在必要时发送到另一个缩减层),而不会导致该聚合器过载。