{% include JB/setup %}
grain持久化的目标
- 允许不同的grain类型使用不同的存储(例如,一个使用Azure table,一个使用ADO.NET的 Azure)或者同样类型的存储但是使用不同的配置(例如,都使用Azure table但是一个使用存储帐号#1,一个使用存储帐号#2)
- 允许仅仅改变配置文件而不改变代码来实现一个存储提供者的配置转换(例如,开发-测试-生产之间转换)。
- 提供一个框架来允许添加之后编写的额外的存储提供者,不管是Orleans团队编写的还是其他人编写的。
- 提供一小部分生产级别的存储提供者。
- 存储提供者对于如何在持久化后段存储存储grain的状态有完全的控制权。结论是:Orleans不提供全面的ORM解决方案,但是当有需要 时允许定制存储提供者来支持特定的ORM需求。
grain持久化API
grain类型可以用一下其中一种方式声明:
- 扩展
Grain
,如果他们没有任何持久化的状态或者他们自己能够处理所有持久化的状态,或者 - 扩展
Grain<T>
,如果他们有想要Orleans运行处理的持久化的状态。 换句话说,使用扩展Grain<T>
的方式声明grain类型就是自动选择了Orleans系统管理的持久化框架。
这节其余的部分,我们只考虑第二种情况Grain<T>
,因为第一种情况grain会继续运行不会有任何的行为变化。
grain状态存储
继承自Grain<T>
(T是一个派生自GrainState
的应用相关的状态数据)的grain类将会村特定的存储中自动地加载他们的状态。
grain将会被一个指定了存储提供者命名实例的[StorageProvider]
特性所标记,用来为grain读取/写入状态数据。
[StorageProvider(ProviderName="store1")]
public class MyGrain<MyGrainState> ...
{
...
}
Orleans提供者管理框架提供了一个指定&注册不同存储提供者的机制并且把选项存储silo的配置文件中。
<OrleansConfiguration xmlns="urn:orleans">
<Globals>
<StorageProviders>
<Provider Type="Orleans.Storage.MemoryStorage" Name="DevStore" />
<Provider Type="Orleans.Storage.AzureTableStorage" Name="store1"
DataConnectionString="DefaultEndpointsProtocol=https;AccountName=data1;AccountKey=SOMETHING1" />
<Provider Type="Orleans.Storage.AzureBlobStorage" Name="store2"
DataConnectionString="DefaultEndpointsProtocol=https;AccountName=data2;AccountKey=SOMETHING2" />
</StorageProviders>
配置存储提供者
AzureTableStorage
<Provider Type="Orleans.Storage.AzureTableStorage" Name="TableStore"
DataConnectionString="UseDevelopmentStorage=true" />
下面的特性可以被添加到<Provider />
元素中来配置提供者:
DataConnectionString="..."
(必选) - Azure storage的连接字符串TableName="OrleansGrainState"
(可选) - 表存储用的表名,默认是OrleansGrainState
DeleteStateOnClear="false"
(可选) - 如果是true,在清除的时候记录会被删除,否则会写入一条null数据,默认是false
UseJsonFormat="false"
(可选) - 如果是true,将使用json序列化,否则将会使用Orleans二进制序列化,默人是false
UseFullAssemblyNames="false"
(可选) - (如果UseJsonFormat="true"
) 序列化的类型带有完整的程序集名字(true)或者简单的名字(false), 默认是false
IndentJSON="false"
(可选) - (如果UseJsonFormat="true"
) 缩进序列化后的json,默人是false
注意: 状态不要超出64KB,Azure Table Storage的强制限制。
AzureBlobStorage
<Provider Type="Orleans.Storage.AzureTableStorage" Name="BlobStore"
DataConnectionString="UseDevelopmentStorage=true" />
下面的特性可以被添加到<Provider />
元素中来配置提供者:
DataConnectionString="..."
(必选) - Azure storage的连接字符串ContainerName="grainstate"
(可选) - 使用的blob storage container,默认是grainstate
UseFullAssemblyNames="false"
(可选) - 序列化的类型带有完整的程序集名字(true)或者简单的名字(false), 默认是false
IndentJSON="false"
(可选) - 序列化后的json包含缩进,默认是false
DynamoDBStorageProvider
<Provider Type="Orleans.Storage.DynamoDBStorageProvider" Name="DDBStore"
DataConnectionString="Service=us-wes-1;AccessKey=MY_ACCESS_KEY;SecretKey=MY_SECRET_KEY;" />
DataConnectionString="..."
(必选) - DynamoDB使用的连接字符串。 你可以设置Service
,AccessKey
,SecretKey
,ReadCapacityUnits
和WriteCapacityUnits
。TableName="OrleansGrainState"
(可选) - 表存储用的表名,默认是OrleansGrainState
DeleteStateOnClear="false"
(可选) - 如果是true,在清除的时候记录会被删除,否则会写入一条null数据,默认是false
UseJsonFormat="false"
(可选) - 如果是true,将使用json序列化,否则将会使用Orleans二进制序列化,默人是false
UseFullAssemblyNames="false"
(可选) - (如果UseJsonFormat="true"
) 序列化的类型带有完整的程序集名字(true)或者简单的名字(false), 默认是false
IndentJSON="false"
(可选) - (如果UseJsonFormat="true"
) 缩进序列化后的json,默人是false
ADO.NET Storage Provider (SQL Storage Provider)
Note that to use the it is necessary to deploy the database script to the database. It can be found in the The scripts are located in the Nuget library, similar to \packages\Microsoft.Orleans.OrleansSqlUtils.n.n.n\lib\net<version>\SQLServer\
depending on version and database vendor.
<Provider Type="Orleans.SqlUtils.StorageProvider.SqlStorageProvider" Name="SqlStore" DataConnectionString="Data Source = (localdb)\MSSQLLocalDB; Database = OrleansTestStorage; Integrated Security = True; Asynchronous Processing = True; Max Pool Size = 200;" />
DataConnectionString="..."
(mandatory) - The SQL connection string to use.UseJsonFormat="false"
(optional) - If true, the json serializer will be used, otherwise the Orleans binary serializer will be used, defaults tofalse
.UseXmlFormat="false"
(optional) - If true, the .NET XML serializer will be used, otherwise the Orleans binary serializer will be used, defaults tofalse
.UseBinaryFormat="false"
(the default) - If true, the Orleans binary data format will be used.
Note that pool size of 200 is quite a low figure.
The following is an example of programmatic configuration.
//props["RootDirectory"] = @".\Samples.FileStorage";
//config.Globals.RegisterStorageProvider<Samples.StorageProviders.OrleansFileStorage>("TestStore", props);
props[Orleans.Storage.AdoNetStorageProvider.DataConnectionStringPropertyName] = @"Data Source = (localdb)\MSSQLLocalDB; Database = OrleansTestStorage; Integrated Security = True; Asynchronous Processing = True; Max Pool Size = 200;";
props[Orleans.Storage.AdoNetStorageProvider.UseJsonFormatPropertyName] = "true"; //Binary, the default option, is more efficient. This is for illustrative purposes.
config.Globals.RegisterStorageProvider<Orleans.Storage.AdoNetStorageProvider>("TestStore", props);
A quick way to test this is to (see in the aforementioned the few commented lines)
- Open
\Samples\StorageProviders
. - On the package manager console, run:
Install-Package Microsoft.Orleans.OrleansSqlUtils -project Test.Client
. - Update all the Orleans packages in the solution, run:
Get-Package | where Id -like 'Microsoft.Orleans.*' | foreach { update-package $_.Id }
(this is a precaution to make sure the packages are on same version). - Go to
OrleansHostWrapper.cs
and to the following
The ADO.NET persistence has functionality to version data and define arbitrary (de)serializers with arbitrary application rules and streaming, but currently there is no method to expose them to application code. More information in ADO.NET Persistence Rationale.
MemoryStorage
<Provider Type="Orleans.Storage.MemoryStorage" Name="MemoryStorage" />
__Note:__ This provider persists state to volatile memory which is erased at silo shut down. Use only for testing.-->
注意: 这个提供者将状态持久化到独立内存中,在silo关闭的时候将被清除。只用来测试用。
NumStorageGrains="10"
(可选) - 用来存储状态的grain的个数,默认是10
ShardedStorageProvider
<Provider Type="Orleans.Storage.ShardedStorageProvider" Name="ShardedStorage">
<Provider />
<Provider />
<Provider />
</Provider>
一个简单的写入分片到若干存储提供者的数据的简单存储提供者。 使用一个一致性哈希函数(默认是Jenkins Hash)来决定哪个分片对指定的grain的状态数据存储进行相应,然后读取/写入/清除请求路由到适当的潜在的提供者来执行。
关于存储提供者特别说明的
如果没有给一个Grain<T>
grain类指定[StorageProvider]
特性,将会搜索使用名为Default
的提供者。
如果没有找到,就当作缺少存储提供者。
如果silo配置文件中只有一个提供者,它会被当作silo的Default
提供者。
一个使用不存在或者没有在silo配置中定义的提供者的grain将会在加载的时候失败,但是其他的grain还是会加载并且运行。
之后任何对这个grain的调用都会失败得到一个表示那个grain类型没有被加载的Orleans.Storage.BadProviderConfigException
错误。
一个grain类型最终使用的存储提供者是通过[StorageProvider]
特性中的提供者名称加上提供者的类型和silo配置中定义的提供者配置选项决定。
不同的grain类型使用不同配置的存储提供者,即使是同一个类型:例如,两个不同的Azure table存储提供者实例,连接到不同的Azure storge account(参考上面的配置文件例子)。
所有的存储提供者的配置细节静态地卸载silo配置中在silo启动时读取。 现在 没有 动态地更新或者改变silo的使用的存储提供者的机制。 然而,这是一个优先级/工作量限制,而不是一个基本的设计约束。
状态存储API
grain状态/持久化API有两个部分:Grain到与形时和运行时到存储提供者。
grain状态存储API
Orleans运行时的状态存储功能提供读取和写入操作来自动的填充/保存grain的GrainState
数据对象。
这些功能将会通过为grain配置的恰当的持久化提供者来默默地完成。
grain状态读写函数
当grain激活的时候grain状态会自动被读取,但是当需要的时候grain需要显示地触发任何grain状态的写操作。 阅读失败模式一节,了解更多错误处理机制的细节。
激活的时候,GrainState
(等同于base.ReadStateAsync()
)在OnActivateAsync()
方法被调用之前会被调用。
GrainState
在grain的任何方法被调用之前不会更新,除非这次调用的时候这个grain已经激活了。
在grain的任何方法被调用的时候,一个grain可以要求Orleans运行时把那个激活的当前的状态数据通过调用base.WriteStateAsync()
写入到指定的存储提供者中。
当grain状态数据发生显著的更新的时候,grain负责显示地执行写入操作。
大多说情况下,grain方法返回base.WriteStateAsync()
Task
作为最终结果Task
返回,但是它并不要求遵循此模式。
运行时在任何grain方法后不会自动更新存储的grain。
在任何grain方法或者timer回掉函数中,grain可以要求Orleans运行时通过调用base.ReadStateAsync()
从指定的存储提供者重读当前的grain激活的状态数据。
这将会使用从持久化存储中读出的最新值完全重写当前grain状态对象中存储的状态数据。
当状态读取时存储提供者 可能 将一个不透明的提供者指定的Etag
值(string
)作为grain状态数据的一部分填充进去。
一些不适用Etag
的提供者会选择将这个值留作null
。
概念上,Orleans运行在任何写操作的时候时会对grain状态数据对象进行深拷贝为自己使用。表面之下,运行时在一些环境中 可能 使用优化策略和启发式方法来避免进行一些或者全部的深拷贝,通过这个能实现期望的逻辑隔离语义。
Sample Code for Grain State Read / Write Operations
Grains must extend the Grain<T>
class in order to participate in the Orleans grain state persistence mechanisms.
The T
in the above definition will be replaced by an application-specific grain state class for this grain; see the example below.
The grain class should also be annotated with a [StorageProvider]
attribute that tells the runtime which storage provider (instance) to use with grains of this type.
public interface MyGrainState : GrainState
{
public int Field1 { get; set; }
public string Field2 { get; set; }
}
[StorageProvider(ProviderName="store1")]
public class MyPersistenceGrain : Grain<MyGrainState>, IMyPersistenceGrain
{
...
}
grain状态读取
最初的grain状态读取将会有Orleans运行在grain的OnActivateAsync()
方法调用前自动发生;不需要使用应用代码来触发。
此后,可以通过grain类中的Grain<T>.State
属性来读取grain的状态。
grain状态写入
在对grain的内存中的状态进行任何适当改动后,grain应该调用base.WriteStateAsync()
方法通过已经grain类型已经定义的存储提供者将改变写入到持久化存储。
这个方法是异步的并且返回一个Task
。这个Task
通常作为grain方法它自己的完成Task返回。
public Task DoWrite(int val)
{
State.Field1 = val;
return base.WriteStateAsync();
}
grain状态刷新
如果一个grain希望显示地从后存储中重新读取这个grain的最新状态,这个grain应该调用base.ReadStateAsync()
方法。
这将通过已为这个grain定义的存储提供者从持久化存储中重新加载grain状态,并且当ReadStateAsync()
Task
完成时。任何之前内存中的grain状态的拷贝会被重写和替换,
public async Task<int> DoRead()
{
await base.ReadStateAsync();
return State.Field1;
}
grain持久化操作的失败模式
grain状态读取操作的失败模式
存储提供者返回的在初始化读取特定grain的状态数据时的失败将会导致那个grain的激活操作失败;这样的话,那个grain的OnActivateAsync()
生命周期的回掉方法将 不会 被调用。
引起那个grain激活的原始请求也会失败,想其他grain激活期间的失败一样返回给调用者。
特定的grain的存储提供者读取状态数据遇到的失败将会导致ReadStateAsync()
Task
失败。
grain可以选择处理或者忽略这个失败Task
,就像Orleans中其他的Task
一样。
任何向silo启动时因缺少/错误的存贮提供者配置而不能加载的grain发送消息的尝试都将返回一个永久错误Orleans.BadProviderConfigException
。
grain状态写入操作的失败模式
特定的grain的存储提供者写入状态数据遇到的失败会导致WriteStateAsync()
Task
失败。
通常,这代表grain调用将会传递给提供了WriteStateAsync()
Task
的客户端调用者,并且作为grain方法最终返回的Task
.
然而,也可能在确定的高级场景中来编写grain代码特别处理这样的写入错误,就像能处理其他失败的Task
一样。
执行错误处理/回复的grain代码 必须 捕获WriteStateAsync()
Task
的异常/失败比去年给不重新抛出,表明已经成功处理了错误。
存储提供者框架
有一个服务提供者API来写额外的持久化存储提供者 – IStorageProvider
。
持久化提供者API包括对grain状态数据的读取和写入操作。
public interface IStorageProvider
{
Logger Log { get; }
Task Init();
Task Close();
Task ReadStateAsync(string grainType, GrainId grainId, GrainState grainState);
Task WriteStateAsync(string grainType, GrainId grainId, GrainState grainState);
}
存储提供者语义
当进行任何写操作的时候存储提供者检测到Etag
违反约束,就 应该 以一个瞬时错误 Orleans.InconsistentStateException
引发写Task
失败并且包装底层的存储异常。
public class InconsistentStateException : AggregateException
{
/// <summary>The Etag value currently held in persistent storage.</summary>
public string StoredEtag { get; private set; }
/// <summary>The Etag value currently held in memory, and attempting to be updated.</summary>
public string CurrentEtag { get; private set; }
public InconsistentStateException(
string errorMsg,
string storedEtag,
string currentEtag,
Exception storageException
) : base(errorMsg, storageException)
{
this.StoredEtag = storedEtag;
this.CurrentEtag = currentEtag;
}
public InconsistentStateException(string storedEtag, string currentEtag, Exception storageException)
: this(storageException.Message, storedEtag, currentEtag, storageException)
{ }
}
来自写操作的任何其他错误情况 应该 引发写Task
以一个包含了底层存储异常信息的异常来终止。
数据映射
独立的存储提供者应该决定如何存储grain状态 - blob(多种格式/序列化的形式)或者每列一个字段是显而易见的选择。
基本的Azure Table存储提供者将状态数据字段通过Orleans二进制序列化编码成单个表列。
ADO.NET Persistence Rationale
The principles for ADO.NET backed persistence storage are:
- Keep business critical data safe an accessible while data, the format of data and code evolve.
- Take advantenge of vendor and storage specific functionality.
In practice this means adhering to ADO.NET implementation goals and some added implementation logic in ADO.NET specific storage provider that allow evolving the shape of the data in the storage.
In addition to the usual storage provider capabilities, the ADO.NET provider has built-in capability to
- Change storage data format from one format to another format (e.g. from JSON to binary) when roundtripping state.
- Shape the type to be saved or read from the storage in arbitrary ways. This helps to evolve the version state.
- Stream data out of the database.
Both 1.
and 2.
can be applied on arbitrary decision parameters, such as grain ID, grain type, payload data.
This happen so that one chooses a format, e.g. Simple Binary Encoding (SBE) and implements
(IStorageDeserializer)[https://github.com/dotnet/orleans/blob/master/src/OrleansSQLUtils/Storage/Provider/IStorageDeserializer.cs] and IStorageSerializer.
The built-in (de)serializers have been built using this method. The OrleansStorageDefault
When the (de)serializers have been implemented, they need to ba added to the StorageSerializationPicker
property in AdoNetStorageProvider.
This is an implementation of IStorageSerializationPicker. By default
StorageSerializationPicker will be used. And example of changing data storage format
or using (de)serializers can be seen at [RelationalStorageTests]https://github.com/dotnet/orleans/blob/master/test/TesterInternal/StorageTests/Relational/RelationalStorageTests.cs).
Currently there is no method to expose this to Orleans application consumption as there is no method to access the framework created AdoNetStorageProvider instance.