分布式系统的简化构建:Orleans框架介绍

在构建分布式系统时,开发者常常面临持久性和线程同步的复杂性问题。Orleans框架通过使用Actor模型(尽管它称之为"Grains")以及限制对它们的操作,使得操作变得线程安全,从而简化了这一过程。本文将介绍Orleans框架,并展示其如何帮助开发者构建容错、异步的分布式系统。

先决条件

要运行本文中的代码以及使用Microsoft Orleans框架,需要.NET Framework v4.5(或更高版本)。此外,为了创建项目,使用了Orleans Tools for Visual Studio插件,以及Visual Studio 2015(专业版或更高版本,如果想使用插件)。Orleans Templates插件可以简化创建过程。

为了展示这项技术,构建了一个金融服务应用程序的大纲,该程序用于跟踪资金(共同基金、对冲基金等)。这是一个入门级应用程序,但认为足以展示这项技术所具有的潜力。

在简单系统中,将对以下实体进行建模:

  • 投资者:将资金投入基金的人
  • 基金:投资者可以投资的交易账户类型
  • 资产:基金买卖的东西
  • 经纪人:通过这些公司进行资产交易

开始

第一步是创建一个新项目,定义grain接口(不同grain“实体”可以相互通信的方式)。对于每个实体,需要决定如何唯一地识别它。默认情况下,选项包括通过唯一字符串、整数或全局唯一ID(GUID)。在这个入门级演示中,将使用字符串,但如果实际实体没有内在的唯一标识符,那么可以使用GUID或增量整数。

接口定义了可以对grain(实体)实例执行的操作——例如,可能会说投资者可以订阅或从基金中赎回:

namespace FundGrainInterfaces { /// <summary> /// Grain接口,用于投资者 /// </summary> /// <remarks> /// 对于这个示例,每个投资者都有一个唯一的字符串标识符来识别他们 /// </remarks> public interface IInvestorGrain : IGrainWithStringKey { /// <summary> /// 投资者订阅基金 - 支付资金 /// </summary> /// <param name="fundToSubscribe">投资者订阅的基金</param> /// <param name="amountToSubscribe">订阅的资金金额</param> Task Subscribe(IFundGrain fundToSubscribe, decimal amountToSubscribe); /// <summary> /// 投资者从基金中提取资金 /// </summary> /// <param name="fundToRedeem">投资者之前订阅的基金</param> /// <param name="redemptionAmount">赎回的金额</param> Task Redeem(IFundGrain fundToRedeem, decimal redemptionAmount); } }

然后,需要创建一个项目来在具体类中实现这些接口。每个具体的grain类必须继承自抽象类"Grain",并实现上述声明的接口。

为了说明grain之间的通信,当投资者订阅或从基金中赎回时,这些信息会传递给Fund grain:

/// <summary> /// 投资者的Grain实现类。 /// </summary> public class InvestorGrain : Grain, IInvestorGrain { // 投资者在基金中的持股 private Dictionary<string, decimal> _holdings = new Dictionary<string, decimal>(); public Task<decimal> Redeem(IFundGrain fundToRedeem, decimal redemptionAmount) { if (_holdings.ContainsKey(fundToRedeem.GetPrimaryKeyString())) { if (redemptionAmount <= _holdings[fundToRedeem.GetPrimaryKeyString()]) { // 不能赎回没有持有的 redemptionAmount = _holdings[fundToRedeem.GetPrimaryKeyString()]; } return fundToRedeem.Redemption(redemptionAmount); } else { // 没有持股,因此不能赎回 return Task.FromResult(decimal.Zero); } } public Task Subscribe(IFundGrain fundToSubscribe, decimal amountToSubscribe) { fundToSubscribe.Subscription(amountToSubscribe); if (_holdings.ContainsKey(fundToSubscribe.GetPrimaryKeyString())) { _holdings[fundToSubscribe.GetPrimaryKeyString()] += amountToSubscribe; } else { _holdings.Add(fundToSubscribe.GetPrimaryKeyString(), amountToSubscribe); } // 表示一切顺利 return TaskDone.Done; } }

相应的Fund grain可以处理来自基金自有可用现金的订阅和赎回:

public class FundGrain : Grain, IFundGrain { private decimal _liquidCash; // 基金中可用的流动现金... public Task<decimal> Redemption(decimal redemptionAmount) { if (_liquidCash >= redemptionAmount) { _liquidCash -= redemptionAmount; } else { // 赎回尽可能多的现金 redemptionAmount = _liquidCash; _liquidCash = 0; } return Task.FromResult(redemptionAmount); } public Task Subscription(decimal subscriptionAmount) { _liquidCash += subscriptionAmount; return TaskDone.Done; } }

创建silo

Grain(实体)的实例需要由silo托管,silo实际上是grain的虚拟机环境。为了测试这些,需要创建一个silo测试项目:

在这个主机中,实例化grains(实体),然后可以通过它们定义的接口与它们进行交互:

// - - - 8< - - - - - - - - - - - - - - - - - - - - - - - IFundGrain myTestFund = GrainClient.GrainFactory.GetGrain

现在,如果运行多个silo实例,它们将允许运行相同的底层fund grain,而开发者无需实现(或关心)任何并发检查。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485