什么是grain?
grain是Orleans编程模型的关键原语。grain是Orleans应用程序的构建块,它们是隔离的、分布式的、持久性的原子单元。grain是表示应用程序实体的对象。就像在经典的面向对象编程中一样,grain封装了实体的状态,并在代码逻辑中对其行为进行编码。Grains可以保持彼此的引用,并通过调用彼此通过接口公开的方法,来进行交互。
Orleans旨在大大简化构建可伸缩应用程序,并通过以下方式消除大多数并发性挑战:
- 除了消息传递,不在grain实例之间共享数据。
- 为每个单独的grain提供单线程执行保证。
典型的grain封装了单个实体(例如特定用户或设备或会话)的状态和行为。
grain身份
单个grain是grain类型(类)的唯一可寻址实例。每种grain在其类型中具有唯一的身份,也称为grain键。其类型中的grain标识可以是长整数、GUID、字符串或long + string或GUID + string的组合。
访问一个grain
一个grain类实现一个或多个grain接口,作为与该类型的grain交互的正式代码契约。要调用grain,调用者需要知道grain类实现的grain接口,其中包括调用者想要调用的方法,以及目标grain的唯一标识(key)。例如,如果将电子邮件用作用户身份,则可以使用以下方式,调用用户配置文件grain,以更新用户的地址。
var user = grainFactory.GetGrain<IUserProfile>(userEmail);
await user.UpdateAddress(newAddress);
对GetGrain的调用是一种廉价的本地操作,它使用嵌入的标识和目标grain类型,来构造一个grain引用。
请注意,无需创建或实例化目标grain。我们调用它来更新用户的地址,就好像用户的grain已经为我们实例化了一样。这是Orleans编程模型的最大优势之一 —— 我们永远不需要创建、实例化或删除grain。我们可以编写代码,好像所有可能的grain(例如,数百万的用户配置文件grain)始终在内存中等待我们调用它们。在幕后,Orleans运行时执行所有繁重的资源管理,以便在需要时透明地将grain加载进内存。
幕后花絮 - grain生命周期
grain生活在称为silo的执行容器中。silo形成一个集群,将多个物理或虚拟机资源组合在一起。当grain有工作(请求)时,Orleans确保集群中的某个silo上有一个grain实例。如果任何silo上都没有grain实例,Orleans运行时会创建一个。此过程称为激活。在grain使用grain 持久性的情况下,运行时会在激活时自动从后端存储中读取状态。
一旦在silo上激活,grain就会处理来自其他grain或来自集群外部(通常来自前端Web服务器)的传入请求(方法调用)。在处理请求的过程中,grain可能会调用其他grain或一些外部服务。如果grain停止接收请求并保持空闲,则在一段可配置的非活动时段之后,Orleans会从内存中移除grain(将其停用),以释放其他grain的资源。如果有新的grain请求,Orleans会再次激活它,可能是在不同的silo上,所以调用者会得到这样的印象:grain一直留在记忆中。只有当存储中的持久化状态(如果有的话)存储在内存中实例化以从内存中移除时,粒子才会从生存周期中经历生命周期。一个grain会经历这样的生命周期:从它在存储中的持久状态(如果有的话)到在内存中被实例化,再到从内存中删除。
Orleans透明地控制着grain的激活和停用过程。当对grain进行编码时,开发人员可以假定所有grain始终处于激活状态。
grain生命周期中的关键事件的顺序如下所示。
- 另一个grain或client调用了grain的一个方法(通过grain引用)
- grain被激活(如果它尚未在集群中的某个位置激活)并且创建了一个grain类的实例(称为grain激活)
- 利用依赖注入(如果适用)执行grain的构造函数
- 如果使用了声明性持久性,则从存储中读取grain状态。
- 如果有重写
OnActivateAsync
,则调用它。
- grain处理传入的请求
- grain闲置一段时间
- silo运行时决定停用grain
- 如果有重写
OnDeactivateAsync
,silo运行时则调用它。 - silo运行时从内存中移除grain
当一个silo正常关闭后,它所持有的所有激活的grain都会被停用。等待在grain队列中处理的任何请求,都会被转发到集群中的其他silo,在那里根据需要,为所停用grain创建新的激活。如果一个silo不是正常关闭或者杀掉,集群中的其他silo会检测到故障,并随着对这些grain的新请求的到来,开始为故障silo上丢失的grain创建新的激活。请注意,检测silo故障需要一些时间(可配置),因此重新激活丢失的grain的过程不是即时的。
grain的执行
激活的grain以块的形式执行工作,并在每个块移动到下一个块之前完成每个块。工作块包括响应来自其他grain或外部client端请求的方法调用,以及在完成前一个块时安排的闭包。与一个工作块相对应的基本执行单元称为一个回合。
虽然Orleans可以并行执行属于不同激活的许多回合,但每次激活总是一次执行一个。这意味着不需要使用锁或其他同步方法来防止数据争用和其他多线程危险。