Orleans 2.0官方文档(闫辉的个人翻译)——4.9.2 JournaledGrain 基础知识

JournaledGrain基础知识

Journaled grain继承JournaledGrain<StateType,EventType>,它具有以下类型参数:

  • StateType代表grain的状态。它必须是一个类,该类具有默认的public的构造函数。
  • EventType 是一个针对该grain激发的所有事件的通用超类,它可以是任何类或接口。

所有状态和事件对象都应该是可序列化的(因为日志一致性的提供程序可能需要持久化它们,和/或将它们发送到通知消息中)。

对于事件为POCO(普通的、老的C#对象)的grain, JournaledGrain<StateType>可以用作JournaledGrain<StateType,Object>的简写。

读取grain的状态

要读取当前grain状态,并确定其版本号,JournaledGrain具有属性

GrainState State { get; }
int Version { get; }

版本号始终等于已确认事件的总数。状态是将所有已确认事件,应用于初始状态的结果。初始状态具有版本0(因为没有任何事件被应用),由GrainState类的默认构造函数确定。

重要说明:应用程序永远不应该直接修改State返回的对象。它只用于阅读。相反,当应用程序想要修改状态时,它必须通过激发事件,来间接地做到。

激发事件

通过调用RaiseEvent方法来完成事件的激发。例如,代表聊天的grain,可以激发一个PostedEvent,来表示用户提交了帖子:

RaiseEvent(new PostedEvent() { Guid = guid, User = user, Text = text, Timestamp = DateTime.UtcNow });

请注意,RaiseEvent启动对存储的写入访问,但不等待写入完成。对于许多应用程序而言,等待确认事件的写入完成,是很重要的。在这种情况下,我们总是紧跟着等待ConfirmEvents

RaiseEvent(new DepositTransaction() { DepositAmount = amount, Description = description });
await ConfirmEvents();

请注意,即使您没有显式地调用ConfirmEvents,事件最终也将得到确认 —— 它会在后台自动发生。

状态变迁的方法

每当激发事件时,运行时都会自动更新grain的状态。应用程序不需要在激发事件后,显式地更新状态。但是,应用程序仍然必须提供代码,指定如何更新状态,来响应事件。这可以通过两种方式完成。

(a) GrainState类可以在StateType上,实现一个或多个Apply方法。通常,会创建多个重载,并为事件的运行时类型,选择最接近的匹配项:

class GrainState {

   Apply(E1 @event)  
   {
     // code that updates the state
   }
   Apply(E2 @event)  
   {
     // code that updates the state
   }
}

(b) grain可以重写TransitionState方法:

protected override void TransitionState(State state, EventType @event)
{
   // code that updates the state
}

除了修改状态对象之外,TransitionState方法不应该有任何副作用,并且应该是确定性的(否则,结果是不可预测的)。如果代码抛出异常,则捕获该异常,并将其包含进Orleans日志中的警告,该警告由日志一致性的提供程序发出。

确切地说,运行时调用变迁方法的具体时间,取决于所选的日志一致性的提供程序及其配置。除非日志一致性的提供程序特别保证,否则应用程序最好不要依赖于特定的时间。

某些提供程序(例如日志一致性的提供程序LogStorage)会在每次加载grain时,重演事件序列。因此,只要事件对象仍然可以从存储中适当地反序列化,就可以从根本上修改GrainState类和变迁方法。但对于其他提供程序(例如日志一致性提供程序StateStorage),只有GrainState对象被持久化,因此开发人员必须确保从存储中读取时,可以正确地反序列化它。

激发多个事件

在调用ConfirmEvents之前,可以多次调用RaiseEvent:

RaiseEvent(e1);
RaiseEvent(e2);
await ConfirmEvents();

但是,这可能会导致两次连续的存储访问,并且会招致grain在仅写入第一个事件后失败的风险。因此,最好一次激发多个事件,使用:

RaiseEvents(IEnumerable<EventType> events)

这保证了给定的事件序列以原子方式写入存储。请注意,由于版本号始终与事件序列的长度匹配,因此,一次激发多个事件,会使版本号增加多个。

检索事件序列

基类JournaledGrain中的以下方法,允许应用程序检索所有已确认事件序列的指定段:

Task<IReadOnlyList<EventType>> RetrieveConfirmedEvents(int fromVersion, int toVersion)

但是,并非所有的日志一致性提供程序都支持它。如果不受支持,或者序列的指定段不再可用,则抛出一个NotSupportedException

要检索所有事件(直至最新的确认的版本),可以调用

await RetrieveConfirmedEvents(0, Version);

只能检索已确认的事件:如果toVersion大于属性Version的当前值,则抛出异常。

由于已确认的事件永远不会改变,即使存在多个实例或延迟的确认,也不需要担心发生竞争。但是,在这种情况下,在await恢复时,属性Version的值,与在调用awaitRetrieveConfirmedEvents时相比,可能会更大。因此建议将该值保存在变量中。另请参阅“并发保证”一节。

猜你喜欢

转载自blog.csdn.net/uddiqpl/article/details/86424411