Orleans是一个用于构建健壮的、可扩展的分布式应用的跨平台框架。
Orleans将.NET开发者生产力带入了分布式应用的世界,如云服务。Orleans可以将单一的本地部署的服务,扩展为部署在云端的全球分布的、高可用的应用。
Orleans使用了objects、interfaces、async/await以及try/catch等常用的概念,并将它们扩展到多服务器环境上。如此以来,Orleans能帮助那些只有单服务器应用开发经验的开发者,过渡到构建弹性的、可伸缩的云服务和其他分布式应用上。因此,Orleans通常被称为“分布式的.NET”。
该框架由Microsoft Research创建,引进了Virtual Actor Model这一用于构建云时代的新一代分布式系统的新颖方法。Orleans的核心贡献在于它的编程模型,该模型驯服了高度并行的分布式系统固有的复杂性,且不会限制应用的能力,不会对开发者施加繁重的约束(对开发者的要求也没那么高)。

颗粒(Grains)

image.png
所有Orleans应用的基本构建模块被称为颗粒(Grain)。Grain即由用户自定义标识(user-defined identity)、行为(behaviour)和状态(state)组成的实体。Grain标识是用户定义的key,使Grain始终处于可调用的状态。Grain可以被其他Grain或外部客户端(如web前端)通过强类型的接口(协议)来调用。每个Grain都是实现了一个或多个这些接口的类的实例。
Grain可以在任何存储系统中,存储其易变性(volatile)和/或持久性(persistent)的状态。如此,Grain隐式地对应用的状态进行了分区,从而实现了自动伸缩,并简化了从故障中恢复的过程。当Grain处于active状态时,其状态会保存在内存中,从而降低获取的延时,并减少数据存储负载。
image.png
Orleans运行时(Orleans Runtime)会根据需要自动实例化Grain。一段时间没有被使用的Grain会被自动从内存中移除以释放资源。得益于无论当前Grain是否被加载到了内存里,Orleans都允许通过Grain标识去调用Grain。这个机制也支持故障自动恢复,因为无论何时,调用方不必知道Grain有没有实例化,在哪个服务器上做的实例化。Grain的生命周期由Orleans运行时托管,Orleans运行时负责activating/deactivating这些Grain,并根据需要放置(placing)/定位(locating)这些Grain。允许开发者在写代码时就认为所有Grain一直都在内存里。
总而言之,Grain的以下特性保证了Orleans框架开发的应用具有可伸缩性,可靠性以及较高的性能,同时也不需要开发者为了系统支持分布式而编写复杂的代码:

  • 稳定的标识;
  • 有状态;
  • 生命周期被托管。

示例:物联网云后端

考虑实现一个物联网系统的云后端。该程序需要处理从设备传过来的数据,进行过滤、聚合、汇总等,还需要支持向设备发送命令。在Orleans框架下,可以很自然地用Grain对每个物联网设备进行建模,Grain与它对应的物联网设备就像数字孪生兄弟(digital twin)一样。Grain会将相应设备的最新数据存放在内存中,便于快速查询和处理这些数据,而无需直接与物联网设备进行通信。通过观察和分析设备的时序数据流,Grain可以通过检测设备状态或条件的变化(如测量值超过某个阈值)来触发某个特定的动作。
这就对恒温器建立一个简单的模型:

  1. public interface IThermostat : IGrainWithStringKey
  2. {
  3. Task<List<Command>> OnUpdate(ThermostatStatus update);
  4. }

可通过调用OnUpdate方法,将来自Web前端的恒温器触发的事件发送给相应的Grain,Grain可以生成一组命令,并可以将命令反馈给相应的物理设备:

  1. var thermostat = client.GetGrain<IThermostat>(id);
  2. return await thermostat.OnUpdate(update);

相同的恒温器Grain可以实现另外一个单独的接口,用于和控制系统进行交互:

  1. public interface IThermostatControl : IGrainWithStringKey
  2. {
  3. Task<ThermostatStatus> GetStatus();
  4. Task UpdateConfiguration(ThermostatConfiguration config);
  5. }

用一个类来实现IThermostatIThermostatControl这两个接口:

  1. public class ThermostatGrain : Grain, IThermostat, IThermostatControl
  2. {
  3. private ThermostatStatus _status;
  4. private List<Command> _commands;
  5. public Task<List<Command>> OnUpdate(ThermostatStatus status)
  6. {
  7. _status = status;
  8. var result = _commands;
  9. _commands = new List<Command>();
  10. return Task.FromResult(result);
  11. }
  12. public Task<ThermostatStatus> GetStatus() => Task.FromResult(_status);
  13. public Task UpdateConfiguration(ThermostatConfiguration config)
  14. {
  15. _commands.Add(new ConfigUpdateCommand(config));
  16. return Task.CompletedTask;
  17. }
  18. }

在这个例子中,Grain类没有保存状态。关于更详细的状态持久化的例子,可以看这篇文档

Orleans运行时(Orleans Runtime)

Orleans运行时为应用开发实现了Orleas程序的编程模型。运行时的主要组件为筒仓(Silo),是托管Grain的仓储。通常,一组筒仓以集群的方式运行,以实现可伸缩性和容错机制。作为集群运行时,筒仓之间彼此协调分配工作,检测并从故障中恢复。Orleans运行时能使在集群中托管的Grain能够像在同一个单一进程中一样彼此相互通信。
除了核心编程模型之外,筒仓还为Grain提供一组运行时服务,如计时器、提醒器(持久化的计时器)、持久化、事务、流等。详情见下面的功能小节
Web前端及其他外部客户端通过客户端类库调用集群中的Grain,客户端类库自动管理网络通信。更简单的方式是,客户端和筒仓被托管到同一个进程中去。
Orleans兼容.NET Standard 2.0及以上版本。能够在装有.NET Framework(仅支持Windows)或.NET Core的Windows、Linux和macOS上运行。

功能

持久化(Persistence)

Orleans提供了一个简单的持久化模型。该模型可以确保在处理请求之前,Grain是可用的状态,并能维护不同请求处理过程中使用的相同标识的Grain一致性。Grain可以拥有多个已命名的持久化数据对象,例如,一个保存了用户档案的对象被命名为“profile”,另一个保存了库存清单的对象名为“inventory”。状态可以存储在任何存储系统中,即用户档案和库存清单可以被存储在不同的数据库中。当Grain在运行状态中(应该是说activating时,原文写的是running)时,状态是保持在内存中的,所以处理读取请求时无需访问存储。当Grain的状态需要更新时,调用state.WriteStateAsync()就可以确保其背后的存储可以得到更新,以此实现持久化和一致性。详情见Grain持久化文档

分布式ACID事务(Distributed ACID Transactions)

除上述简单的持久化模型外,Grain还支持事务性状态(transactional state)。多个Grain,无论它们的状态实际上存储在哪里,都可以参与到同一个ACID事务中。Orleans的事务是分布式的,去中心化的(即不存在中心化的事务管理器或事务协调器),且支持串行化隔离(serializable isolation)#Isolation_levels)*。关于Orleans事务的详情,见这个文档这篇论文

*:数据库一共定义了四种隔离级别,其中串行化隔离是隔离级别最高的,可避免脏读、不可重复读、虚读等情况。

流(Stream)

流可帮助开发者近乎实时地去处理连续的数据。在Orleans中,流是被托管的:Grain或客户端在发布或订阅流之前,无需创建或注册流。这就使得流的生产者、消费者以及代码的基础设施层三者之间耦合度很低。流处理是可靠的:Grain可以存储检查点(checkpoint)(游标),可以在激活期间或之后的任何时间被重置到某个存储过的检查点。流支持向消费者传输批量消息,可以提高传输效率和恢复能力。流处理机制由Azure Event Hubs、Amazon Kinesis或其他类型的队列服务(queueing service)作为制成。任意多个流可以多路复用到数量较少的队列上,并在整个集群中均衡地处理这些数据。

定时器 & 提醒器(Timers & Reminders)

提醒器是Grain的一种持久调度机制(durable scheduling mechanism)。通过使用提醒器,我们可以保证未来能够完成一些操作,哪怕此时此刻Grain还没有被激活。定时器是不被持久化的提醒器,可被用于处理对可靠性要求不高的、频繁出现的事件。详情见调度机制文档

灵活的Grain放置(Flexible Grain Placement)

当Orleans激活一个Grain时,运行时会决定在哪个服务器(筒仓)上进行激活。这个过程叫做Grain放置。Orleans的放置机制是完全可配置的:开发者可以直接从一组开箱即用的放置策略中选择一种策略,如随机、本地优先、基于负载,也可以配置自定义逻辑。这就使得Grain的放置非常具有灵活性。例如,Grain可以被放置在距离它所操作的资源较近的服务器上,或者它需要进行通讯的其他Grain的服务器上。详情见Grain放置文档

Grain版本控制 & 异构集群(Grain Versioning & Heterogeneous Cluster)

应用的代码终究会随着时间和业务升级而更新变化,如何将这些变化安全又快捷地升级到生产系统,是一项很大的挑战,特别是在有状态的系统中。在Orleans中,Grain接口支持版本控制。集群会维护一张映射表,记录了哪些Grain的实现对于哪些筒仓是可用的,以及这些实现的版本信息。Orleans运行时会根据这个版本信息与Grain放置策略,结合二者,在路由调用Grain时,做出合适的放置决策。除了支持Grain版本控制,Orleans还支持异构集群。不同的筒仓可以用不同的Grain实现集。详情见Grain版本控制文档

弹性伸缩 & 容错(Elastic Scalability & Fault Tolerance)

Orleans在设计时就已充分考虑可伸缩性。当有一个新的筒仓加入集群时,Orleans会根据需要去激活它。当某个筒仓由于某个原因(系统规模缩减或节点故障)而需要从集群中移除时,当前筒仓上的Grain会在集群的其他筒仓上被按需重新激活。一个Orleans集群的规模可以被缩小到只有一个单一的筒仓。Orleans支持可伸缩性的同时,也支持容错性:Orleans集群会自动检测各个筒仓是否发生了故障,并让它们快速从故障中恢复。

跨平台运行(Run Anywhere)

Orleans可以运行在任何支持.NET Core或.NET Framework的系统上,包括Windows、Linux、macOS,并支持部署到K8s、虚拟或物理主机,本地或云端,以及PaaS服务如Azure Cloud服务。

无状态Worker(Stateless Workers)

无状态Worker是一种被特殊标记的Grain,这些Grain没有任何关联状态,可以同时被多个筒仓激活。这样就可以提高无状态函数(stateless function)的并行性。详情见无状态Worker颗粒文档

Grain调用过滤器(Grain Call Filters)

Grain调用过滤器是指多个Grain共同拥有的逻辑。Orleans支持传入和传出调用过滤器。常用的过滤器例子有:授权,日志,遥测,错误处理,等等。

(实际上就是AOP。)

请求上下文(Request Context)

元数据和其他信息可以使用请求上下文在一系列的请求中传递。请求上下文可被用来托管分布式跟踪信息或任何用户定义的值。

开始吧

入门教程开始。

构建项目

Windows上,运行build.cmd脚本在本地编译NuGet包,然后从/Artifacts/Release/*引用NuGet包。可以运行Test.cmd来执行所有BVT(版本验证测试)测试用例,运行TestAll.cmd来执行所有功能性测试。
Linux和macOS上,运行build.sh脚本或dotnet build ./OrleansCrossPlatform.sln来构建Orleans。

官方编译

最近的稳定的、生产环境适用的版本在这里
Nightly版本发布在https://dotnet.myget.org/gallery/orleans-ci。这些版本可以通过所有功能性测试,但是没有像稳定版或在NuGet上预发行的版本那么经过充分测试。

在项目中使用nightly构建包

如果要使用nightly构建包,用任意一种方法添加MyGet源:

  1. 在.csproj文件中添加这一段:

    1. <RestoreSources>
    2. $(RestoreSources);
    3. https://dotnet.myget.org/F/orleans-ci/api/v3/index.json;
    4. </RestoreSources>
  2. 在解决方案目录下新建一个名为NuGet.config的文件,内容如下:

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <configuration>
    3. <packageSources>
    4. <clear />
    5. <add key="orleans-ci" value="https://dotnet.myget.org/F/orleans-ci/api/v3/index.json" />
    6. <add key="nuget" value="https://api.nuget.org/v3/index.json" />
    7. </packageSources>
    8. </configuration>

社区

相关链接