65.9K
CodeProject 正在变化。 阅读更多。
Home

Microsoft Orleans - 一个实例

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2016年7月19日

CPOL

3分钟阅读

viewsIcon

37059

downloadIcon

835

一个关于如何使用 Microsoft Orleans 库开发分布式、容错基金会计系统的快速示例

引言

Microsoft Orleans 是一个框架,它声称通过抽象化在这些系统中出现的持久性和线程同步的复杂性,从而大大简化了容错、异步分布式系统的创建。它通过使用 Actor 模型(尽管将 Actor 称为“Grains”)并通过限制您可以在其上执行的操作来实现这一点,从而使它们线程安全。

必备组件

  • 本文中的代码和 Microsoft Orleans 框架需要 .NET Framework v4.5(或更高版本)才能运行
  • 使用 Orleans Tools for Visual Studio 插件创建了该项目
  • Visual Studio 2015(专业版或更高版本,如果您想使用插件)
  • Orleans 模板插件简化了操作

背景

为了演示这项技术,我整理了一个金融服务应用程序的概要,该应用程序用于跟踪资金(共同基金、对冲基金等)。当然,这是一个 hello-world 级别的应用程序,但我认为它足以展示这项技术所蕴含的潜力。

在我们的简单系统中,我们将有这些实体要建模

  • 投资者,即向基金投入资金的人
  • 基金,投资者可以投资的一种交易账户
  • 资产,基金买卖的物品
  • 经纪人,我们通过其交易资产的公司

入门

第一步是创建一个新项目,该项目定义 grain 接口(不同的 grain“实体”可以相互通信的方式)。

对于我们中的每个实体,我们需要决定如何唯一地识别它。开箱即用,选项是使用唯一的字符串、整数或全局唯一 ID (GUID)。对于这个 hello-world 级别的演示,我将使用string,但如果您的实际实体没有任何固有的唯一标识符,则可以使用 GUID 或增量整数。

接口定义了 grain(实体)的实例可能发生的事情——例如,我们可以说投资者可以订阅或赎回基金

namespace FundGrainInterfaces
{
    /// <summary>
    /// Grain interface for an investor
    /// </summary>
    /// <remarks>
    /// For this example, each investor gets an unique string identifier to identify them
    /// </remarks>
	public interface IInvestorGrain : IGrainWithStringKey
    {
        /// <summary>
        /// The investor subscribes into the fund - paying money in
        /// </summary>
        /// <param name="fundToSubscribe">
        /// The fund that the investor is subscribing to
        /// </param>
        /// <param name="amountToSubscribe">
        /// The amount of money being subscribed
        /// </param>
        Task Subscribe(IFundGrain fundToSubscribe, decimal amountToSubscribe);

        /// <summary>
        /// The investor takes money out of the fund
        /// </summary>
        /// <param name="fundToRedeem">
        /// The fund that the investor has previously subscribed to
        /// </param>
        /// <param name="redemptionAmount">
        /// The amount redeemed
        /// </param>
        Task<decimal> Redeem(IFundGrain fundToRedeem, decimal redemptionAmount);
    }
}

然后我们需要创建一个项目来实现这些接口的具体类。每个具体的 grain 类都必须继承自abstract类“Grain”并实现上述声明的接口。

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

    /// <summary>
    /// Grain implementation class for investor.
    /// </summary>
    public class InvestorGrain : Grain, IInvestorGrain
    {
        // The investors own holdings in the funds
        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()])
                {
                    // cannot redeem what you do not hold
                    redemptionAmount = _holdings[fundToRedeem.GetPrimaryKeyString()];
                }
                return fundToRedeem.Redemption(redemptionAmount);
            }
            else
            {
                // No holding therefore cannot redeem
                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);
            }
            // Indicate that all went well
            return TaskDone.Done; 
        }
    }    

相应的 Fund grain 可以在基金自己的可用流动现金中处理订阅和赎回

    public class FundGrain : Grain, IFundGrain
    {
        private decimal _liquidCash ; // Liquid cash available in the fund...

        public Task<decimal> Redemption(decimal redemptionAmount)
        {
            if (_liquidCash >= redemptionAmount)
            {
                _liquidCash -= redemptionAmount;
            }
            else
            {
                // Redeem as much cash as is available
                redemptionAmount = _liquidCash;
                _liquidCash = 0;
            }
            return Task.FromResult(redemptionAmount); 
        }

        public Task Subscription(decimal subscriptionAmount)
        {
            _liquidCash += subscriptionAmount;
            return TaskDone.Done;
        }
    }  

创建 silo

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

在此主机中,您可以实例化 grain(实体)的实例,然后可以通过其定义的接口与它们交互

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

现在,如果运行此 silo 的多个实例,它们将允许在相同的底层基金 grain 上运行,而开发人员不必实现(或关心)任何并发检查。

关注点

  • 您可以使用许多 存储提供程序来保留 grain 之间的使用,包括各种 Azure 云存储选项。

历史

  • 2016 年 7 月 19 日:初始版本
© . All rights reserved.