orleans —————— 为什么有这个框架 [ 一]

前言

简单说明一下,为什么有orleans 这个框架。

正文

orleans 这个框架的理论基础是 actor, 在1973年提出,当初是为了大量处理高并发计算机的并行模型,其核心思想是将系统中独立的计算过程抽象为actor。

高并发场景有什么问题呢,那就是同时修改一个对象的时候,那么会出现线程不安全的情况。

actor 模型是怎么做的呢? actor 模型做法是同一时刻仅响应一个外部的请求。

当actor 模型同时受到多个外部的请求的时候,通过一定的规则(通常是先入先出)对外部请求进行排序一次执行。

有人觉得这样可能会很慢,那么如果有很多个actor模型呢? 那么其实这个压力就被平坦了。

如果每个都是actor模型的话,那么就有一个问题,actor之间是否能通信呢? 怎么通信?

actor 之间也是可以通信的,对于这一点来说,跟外部访问actor 没有什么区别。

那么orleans 就是为了实现这种思想的。

在orleans 中,actor模型的基础响应单位为grain。

每一个grain 由服务(service),状态(State) 及标识(identtiy)组成。

grain 示例:

那么grain 是怎么作为标识的呢?

当程序使用guid 进行grain 寻址的时候,n0 和 n1 分布由guid 字节数组的0-7 和 8-15 位组成。

采用长整数寻址的时候,n1 为地址位,n0 为0 位。

这个typecodeData 是什么呢? 是最低为4个字节的标识及其所标识对象的类别:

类别如下:

IGrainWithGuidKey:带有 Guid 键的 grain 标记接口。
IGrainWithIntegerKey:带有 Int64 键的 grain 标记接口。
IGrainWithStringKey:带有 string 键的 grain 标记接口。
IGrainWithGuidCompoundKey:带有组合键的 grain 标记接口。
IGrainWithIntegerCompoundKey:带有组合键的 grain 标记接口。

那个keyExt 就是我们可以额外扩展的字段。

var n0 = BitConverter.ToUInt64(new Guid(guididentity).ToByteArray(), 0);

var n1 = BitConverter.ToUInt64(new Guid(guididentity).ToByteArray(), 8);

var identitiy = string.Format("GrainReference={0:x16}{1:x16}{2}+{3}", n0, n1, "06ffffffe23dcf33", ln)

ln 是 keyext。

06ffffffe23dcf33 这个固定的是什么呢? 06 表示标识符。 后面的ffffffe23dcf33 表示了服务的标识。

grain 通过接口调用来定义接口的标识类型:

调用方式:

那么grain的唤醒和休眠是什么样的呢?

因为系统的资源有限,不可能在内存中加载全部的grain。

在orleans 应用程序的内部,grain 示例对象实际会自动在休眠状态(persisted) 和 活跃状态 (volatile) 间进行切换,

在空闲状态下释放对系统资源的占用。

在休眠状态的grain实例对象不占用任何运行时资源(cpu和内存),其内部状态由orleans 运行时通过存储服务进行存储。

当orleans 应用程序首次启动时,其内部所以的grain实例都默认处于休眠态。

Grain对象的休眠过程仅由Orleans运行时触发:当Orleans运行时监测到活跃态Grain实例的闲置时间超过阈值时,Orleans运行时将自动保存其当前状态并对其所占用的运行时资源进行回收。

这里就有一个问题,有一些比较热门的grain,如果监测到非活跃就回收了,那么可以多次激活的情况。

那么可以这样:

Task<string> IHello.SayHello(string greeting)
{
	TimeSpan timeSpan = TimeSpan.FromSeconds(500);
	DelayDeactivation(timeSpan);

	logger.LogInformation($"SayHello message received: greeting = '{greeting}'");
	return Task.FromResult($"You said: '{greeting}', I say: Hello!");
}

使用DelayDeactivation 延迟一下回收时间。

如果呢,你想马上回收,怎么处理呢?

public async Task<string> NoThing()
{
	DeactivateOnIdle();

	return string.Empty;
}

这样可以让其尽快回收,下一次gc 就会回收掉。

这里值得注意的是:DelayDeactivation(timeSpan); 这个timespan 可以设置为负数,如果为负数表示取消。

在orleans 中,状态也是有几种类型的。

比如继承: IpersistentStat 和 grain:

grain 基类中可以重载:

IPersistentState类则具有以下API可供调用及重载。

这里非常值得注意一点: grain 的初始化过程是先于onactivateasync 函数调用的,因此storageprovider 中任何读取错误都将直接导致grain 实例激活失败。

在此值和orleans 运行时并不会继续调用onactivateasync ,会由运行时抛出Orleans.BadProviderConfigException异常

对于状态写入场景而言,StorageProvider中的任何错误也都将由WriteStateAsync方法抛出,开发人员在业务逻辑中需要对依赖WriteStateAsync方法的逻辑分支进行单独的异常处理。

IGrainStorage接口的所有API参数列表中都接受一个类型为IGrainState的输入参数,而IGrainState接口中定义了一个字符串类型的属性ETag,该字段在IPersistentState<T>接口中是可见的,实际上ETag是存储服务在并发读写场景下用以区分状态版本的“数据版本标识位”字段:当需要对外部存储状态进行刷新时,可以将状态的ETag指定为上一次读取该状态时获得的ETag值,若该ETag与当前实际存储的ETag值相同,则证明在上一次状态读取到当前时刻外部存储的状态值没有发生变化,可以进行直接状态的刷新;若ETag不相符,则表明在此段时间内有其他逻辑对外部存储的状态进行了更新,此次状态更新操作面临数据不一致的情况(类似于CPU并发场景下的CAS操作);若业务场景中需要跳过外部状态的版本校验逻辑,则可以在调用外部状态写入API时将状态的ETag值保留为null。而在实现IGrainStorage接口时,任何在读写API方法中检测到读写ETag约束不一致时都需要抛出一个InconsistentStateException异常以终止该API调用并将异常信息传递给上层调用者。

下一节实操一些例子。

posted @ 2023-06-05 14:29  敖毛毛  阅读(460)  评论(0编辑  收藏  举报