对于持久化的grain状态,Orleans支持分布式ACID事务。
Orleans事务是选择性的。必须将silo配置为使用事务。如果不是,任何对grain事务性的方法的调用,都会收到OrleansTransactionsDisabledException
。要在silo上启用事务,请在silo host builder上,调用UseTransactions()
。
var builder = new SiloHostBuilder().UseTransactions();
要使用事务,用户需要配置数据存储。为了支持具有事务的各种数据存储,引入了存储抽象ITransactionalStateStorage
。与泛型的grain存储(IGrainStorage
)不同,这种抽象特定于交易需求。要使用特定于事务的存储,用户可以使用ITransactionalStateStorage
的任何实现,例如Azure(AddAzureTableTransactionalStateStorage
),来配置silo。
例:
var builder = new SiloHostBuilder()
.AddAzureTableTransactionalStateStorage("TransactionStore", options =>
{
options.ConnectionString = ”YOUR_STORAGE_CONNECTION_STRING”);
})
.UseTransactions();
出于开发目的,如果特定于事务的存储不适用于您需要的数据存储,则可以使用IGrainStorage
实现。对于没有为其配置存储的任何事务性的状态,事务将尝试使用桥接器,把故障转移到grain存储。通过桥接到grain存储,访问事务性的状态的效率较低,并不是我们打算长期支持的模式,因此建议仅将其用于开发目的。
为了让grain支持事务,必须使用“Transaction”属性,将grain接口上的事务性的方法,标记为事务的一部分。该属性需要通过以下事务选项,指示grain调用在事务环境中的行为:
TransactionOption.Create
- 调用是事务性的,并且将始终创建新的事务上下文(即,它将启动一个新的事务),即使在现有事务上下文中调用也是如此。TransactionOption.Join
- 调用是事务性的,但只能在现有事务的上下文中调用。TransactionOption.CreateOrJoin
- 调用是事务性的。如果在事务的上下文中调用,它将使用该上下文,否则它将创建新的上下文。TransactionOption.Suppress
- 调用不是事务性的,但可以从事务内部调用。如果在事务的上下文中调用,则不会将上下文传递给调用。TransactionOption.Supported
- 调用不是事务性的,但支持事务。如果在事务的上下文中调用,则上下文将传递给调用。TransactionOption.NotAllowed
- 调用不是事务性的,不能从事务中调用。如果在事务的上下文中调用,它将抛出一个NotSupportedException
。呼叫可以标记为“Create
”,这意味着调用将始终启动自己的事务。例如,下面的ATM grain中的转账操作,将始终启动一个涉及两个引用帐户的新事务。
public interface IATMGrain : IGrainWithIntegerKey
{
[Transaction(TransactionOption.Create)]
Task Transfer(Guid fromAccount, Guid toAccount, uint amountToTransfer);
}
账户grain上的事务性操作Withdraw和Deposit标记为“Join
”,表示只能在现有事务的上下文中调用它们,如果在IATMGrain.Transfer(…)
期间调用它们,就是这种情况。GetBalance
调用被标记为CreateOrJoin
,因此可以从现有事务中调用它,例如通过IATMGrain.Transfer(…)
,或单独调用。
public interface IAccountGrain : IGrainWithGuidKey
{
[Transaction(TransactionOption.Join)]
Task Withdraw(uint amount);
[Transaction(TransactionOption.Join)]
Task Deposit(uint amount);
[Transaction(TransactionOption.CreateOrJoin)]
Task<uint> GetBalance();
}
grain的实现需要使用ITransactionalState
Facet(参考Facet系统),通过ACID事务,来管理grain的状态。
public interface ITransactionalState<TState>
where TState : class, new()
{
Task PerformRead(Func readFunction);
Task PerformUpdate(Func updateFunction);
}
必须通过传递给事务性状态facet的同步函数,来执行对持久化状态的所有读或写访问。这允许事务系统以事务方式执行或取消这些操作。要在grain中使用事务性状态,只需要定义要持久化的可序列化的状态类,并在grain的构造函数中,使用TransactionalState
属性声明事务性状态。后者声明状态名称和(可选地)要使用哪个事务性状态存储(请参阅设置)。
[AttributeUsage(AttributeTargets.Parameter)]
public class TransactionalStateAttribute : Attribute
{
public TransactionalStateAttribute(string stateName, string storageName = null)
{
…
}
}
例:
public class AccountGrain : Grain, IAccountGrain
{
private readonly ITransactionalState balance;
public AccountGrain(
[TransactionalState("balance", "TransactionStore")]
ITransactionalState balance)
{
this.balance = balance ?? throw new ArgumentNullException(nameof(balance));
}
Task IAccountGrain.Deposit(uint amount)
{
return this.balance.PerformUpdate(x => x.Value += amount);
}
Task IAccountGrain.Withdrawal(uint amount)
{
return this.balance.PerformUpdate(x => x.Value -= amount);
}
Task<uint> IAccountGrain.GetBalance()
{
return this.balance.PerformRead(x => x.Value);
}
}
在上面的示例中,属性TransactionalState
用于声明'balance'构造函数参数应与名为“balance”的事务性状态相关联。通过此声明,Orleans将注入一个ITransactionalState
实例,该实例具有从名为“TransactionStore”的事务性状态存储加载的状态(请参阅设置)。状态可以通过PerformUpdate
修改,也可以通过PerformRead
读取。事务基础设施将确保作为事务的一部分执行的任何此类更改,即使在分布在Orleans集群上的多个grain中,在完成创建事务的grain(上述示例中的IATMGrain.Transfer
)调用完成后,将会要么全部提交,要么全部撤消。
grain接口上的事务性方法的调用,与任何其他的grain调用一样。
IATMGrain atm = client.GetGrain(0);
Guid from = Guid.NewGuid();
Guid to = Guid.NewGuid();
await atm.Transfer(from, to, 100);
uint fromBalance = await client.GetGrain(from).GetBalance();
uint toBalance = await client.GetGrain(to).GetBalance();
在上述调用中,ATM grain用于将100个单位的货币从一个帐户转账到另一个帐户。转账完成后,查询两个账户的当前余额。货币转账以及两个帐户的查询都作为ACID事务执行。
如上例所示,事务可以像其他grain调用一样,返回Task中的值,但是在调用失败时,它们不会抛出应用程序异常,而是抛出一个OrleansTransactionException
或TimeoutException
。如果应用程序在事务期间抛出异常,并且该异常导致事务失败(而不是由于其他系统故障导致失败),则应用程序异常将是OrleansTransactionException
的内部异常。如果抛出类型为OrleansTransactionAbortedException的
事务异常,则该事务失败,但可以重试。抛出的任何其他异常,都表示事务以未知状态终止。由于事务是分布式操作,因此处于未知状态的事务可能已成功、失败或仍在进行中。因此,建议在验证状态或重试操作之前,允许度过一个调用的超时周期(SiloMessagingOptions.ResponseTimeout
),以避免级联中止。