Ella 发布/订阅中间件






4.76/5 (7投票s)
Ella 是一个完全分布式的发布/订阅中间件,用纯 C# 编写,并兼容 Mono,为您的应用程序提供可伸缩性和灵活性。
引言
构建分布式和/或模块化应用程序并非易事。通常,会使用某种框架或中间件来支持网络、插件功能和运行时重新配置。其理念是,作为开发人员,您无需关心应用程序的某个部分在网络中的具体位置,只要它存在即可。市面上有不少中间件系统,但您经常会看到一些最终需要中心节点,或者难以使用的系统。
在本文中,我们想向您介绍 Ella,这是一个用于(非集中式)分布式应用程序的中间件。
Ella 是一个完全分布式的发布/订阅中间件,用纯 C# 编写,并兼容 Mono。Ella 为您的应用程序提供了可伸缩性和灵活性。
Ella 负责处理将数据从生产者传输到消费者所需的所有通信,而无需关心它们是否在同一节点或不同节点上。它可以发现运行 Ella 实例的其他节点,因此您无需担心如何扩展应用程序。网络功能可以完全禁用。在这种情况下,Ella 非常适合构建支持开箱即用插件的模块化应用程序。因此,Ella 可用于分布式和非分布式应用程序。
Ella 基于类型发布的发布/订阅模式,因为它非常直观易用,并且不需要主题约定(在基于主题的发布/订阅中)或用于属性的专用查询机制(在基于内容的发布/订阅中)。
Ella 已作为开源项目发布,可在 http://ella.codeplex.com 上找到。
背景
发布/订阅
发布/订阅是一种事件驱动的中间件范式,定义了两种不同的角色:(i)发布者,负责生成和发布数据;(ii)订阅者,通过订阅对事件感兴趣。发布/订阅,如图所示,是一种允许应用程序内部功能元素优雅解耦的机制。一个模块可以同时是发布者和订阅者,即它处理来自另一个发布者的数据并自己发布结果。
发布/订阅管理组件负责解耦。发布者和订阅者模块不是直接连接的,而是发布者宣布其事件,订阅者可以表示对特定类型事件的兴趣。发布/订阅管理器负责匹配已发布的事件和订阅,并负责将已发布的数据交付给所有订阅者。发布/订阅的一个关键要求是,发布者和订阅者都无需相互了解。发布者无需跟踪其数据去向以及其事件有多少订阅者,订阅者也无需关心发布者位于何处以及其数据来自何处(即本地节点还是远程节点)。所有这些都由发布/订阅中间件透明地处理。下图展示了一种分布式发布/订阅的方法。在此,每个节点都运行一个本地发布/订阅管理器。该管理器跟踪其本地发布者的订阅以及网络中运行相同中间件系统的其他节点。
发布/订阅系统支持三个维度的解耦:
- 空间解耦:模块无需知道自己和其他模块在网络中的位置。这意味着发布者不持有订阅者的任何引用,反之亦然。例如,在 VSN(视觉传感器网络)中,图像发布者无需关心图像是交付给一个或多个显示器或其他模块。
- 时间解耦:发布者和订阅者无需同时参与交互。例如,发布者可能在一个没有连接订阅者时发布一个事件。稍后启动的发布者仍然可以与较早的订阅请求匹配。在 VSN 中,摄像头可能不会同时启动,但它们仍必须构成一个分布式应用程序。
- 同步解耦:准备事件不会阻塞发布者,并且即使订阅者当前正在执行另一项活动,也可以通知它们。例如,图像发布者可以在当前图像仍交付给订阅者时捕获下一张图像。
这三种形式的解耦支持异构网络中的分布式应用程序,这些应用程序是可伸缩、灵活且容错的。
Ella 中的属性
在设计 Ella 时,我们不希望强制开发人员在使用 Ella 模块时继承任何层次结构。此外,我们希望有一个系统可以轻松地将现有代码适配到其中。因此,我们决定使用 .NET 属性来定义 Ella 特定的代码部分。
属性用于将特定代码区域标记为与 Ella 相关。例如,将属性 [Subscriber]
添加到一个类,以将其声明为订阅者。有用于
- 声明类为发布者和订阅者
- 工厂方法
- 启动和停止方法
- 消息接收方法
- 事件关联
- 模板数据生成方法
Ella.Attributes
命名空间中。
通过外观使用 Ella
如果您正在开发基于 Ella 的模块或应用程序,外观是您的起点。
访问与 Ella 相关的函数旨在尽可能简单直观。为此,我们决定使用静态外观。使用此中间件的一个重要之处在于,您无需关心持有 Ella 的实例即可访问它。相反,您只需调用一些静态方法,Ella 就会处理其余的事情。
Ella 中有一系列公共静态类,如图所示。类和方法的命名方式我们认为很直观。有一些类与特定操作相关,方法与对象相关。例如,要启动发布者,您可以使用 Start.Publisher。
Start
Start 类包含一些与启动内容相关的基本功能。
在访问任何其他中间件功能之前,应调用 Start.Ella
。
Start.Network
启动 Ella 的所有网络功能。这将触发其他节点的发现和通信端口的打开。要在单个节点上仅使用 Ella 而不使用网络,只需不调用此方法。
Start.Publisher 用于启动发布者模块。它将此发布者添加到 Ella 的发布者列表中,并使订阅者能够订阅其事件。以下示例假设您已创建了一个发布者类 DateTimePublisher,它发布 System.DateTime
值。
DateTimePublisher publisher = new DateTimePublisher(1000); //lets the publisher publish a DateTime struct every second
Start.Publisher(publisher); //this lets Ella know, that this module is ready to be discovered
Ella 将检查发布者的有效性,并处理其事件。之后,发布者模块的启动方法将在单独的线程中被调用。您无需自行启动模块。Ella 会为您完成。发布者只需使用 Ella.Attributes.StartAttribute
声明一个 Start 方法。注意:订阅者无需启动。
停止
Stop 外观类与 Start 类似。它用于一次性停止单个发布者或整个中间件。Publish
此外观类由发布者使用以发布其数据。数据通过简单地调用 Publish.Event() 来发布,如下所示。
Publish.Event(DateTime.Now, this, 1);
Ella 需要拥有发布者的引用才能识别它。1 是事件编号(由发布者自身定义)。这是必需的,因为一个发布者可能发布多个事件。在此调用之后,Ella 将查找已订阅此发布者的所有订阅者(本地和远程),并将 DateTime 对象传递给它们。Publish.Event 还有一个额外的可选参数,即 SubscriptionHandles 列表。这使得发布者可以仅向一部分订阅者发布事件。
编写订阅者
订阅者的实现也非常简单。它们不与 Ella 交互,直到它们首先执行订阅。要将一个类定义为订阅者,只需将属性 [Subscriber]
添加到类中,如下所示。
[Subscriber]
public class MySubscriber
{
...
}
要实际接收来自发布者的数据,您必须表明您对某种特定类型的兴趣。假设我们的订阅者有兴趣接收具有特定自定义类型的状态消息。[Serializable]
public class Status
{
public string StatusMessage{get; set;}
public int NodeID{get; set;}
public DateTime Timestamp {get; set;}
}
注意:所有应由 Ella 传输的数据对象都必须是可序列化的。如果您仅在本地操作,则无需序列化。然后订阅者会像下面这样。
[Subscriber]
public class StatusReceiver
{
public void Init()
{
Subscribe.To<Status>(this, OnNewStatus);
}
public void OnNewStatus(Status status)
{
//Process the status message
...
}
}
在调用 Subscribe.To()
后,Ella 将立即检查合适的发布者。这将在本地以及网络中的每个已知的 Ella 节点上进行。一旦任何合适的发布者发布新的 Status,它将被传递给我们的 StatusReceiver
。订阅的可选参数 您可以为 Subscribe.To 调用添加其他参数。
- evaluateTemplateObject:在此您可以传递一个指向您的代码的委托,该代码用于评估模板对象,即,Ella 将要求每个合适的发布者提供一个模板对象,并且只有当您的函数对某个模板返回 true 时,您的订阅者才会被订阅它。
- DataModifyPolicy:在此您可以指示您将修改从发布者接收到的数据。在本地操作中,您可能会销毁其他发布者或订阅者仍然需要的数据(想象一下传递图像并更改像素值)。如果需要,Ella 将在将每个已发布的对象传递给订阅者之前复制它,以防止意外丢失数据。这种必要性是通过比较每个订阅者的 DataModifyPolicy 和发布者的 DataCopyPolicy 来确定的。
- forbidRemote:如果为 true,Ella 将不会在远程节点上搜索合适的发布者。
- subscriptionCallback:您可以将一个委托传递给一个方法,该方法将在每次订阅者订阅发布者时被调用。这将为您提供一个 SubscriptionHandle 对象,您可以使用它来区分来自不同发布者的事件。这样您就可以区分来自节点 1 和节点 2 的发布者的 StatusObject。
编写发布者
发布者的实现很简单。每个发布者都必须定义一个 [Factory]
、一个 [Start]
和一个 [Stop]
属性。要将一个类定义为发布者,您只需在类中添加一个 [Publishes]
属性。此属性包含额外信息,包括发布的类型、唯一的事件 ID 和复制策略,该策略指示在修改时是否需要复制已发布的对象。下面是一个简单的示例,其中该类被定义为类型为 Status
、事件 ID 为 1 且无需复制修改的发布者。
[Publishes(typeof(Status), 1, CopyPolicy = DataCopyPolicy.None)]
public class MyPublisher
{
...
}
每个发布者所需的 [Factory]
、[Start]
和 [Stop]
属性以类似的方式使用属性定义。[Factory]
public MyPublisher
{
...
}
[Start]
public void Run()
{
...
}
[Stop]
public void Stop()
{
...
}
发布者现在可以通过使用 Ella 静态外观的 Publish.Event()
方法来发布数据。现在我们想为上面的订阅者构建一个合适的发布者。这意味着发布者必须发布 Status。internal void PublishEvent()
{
Status status = new Status
{
StatusMessage = "Event 1",
NodeID = 23,
Timestamp = DateTime.Now
};
Publish.Event(status, this, 1);
}
要创建发布者,我们只需要做这些。网络、订阅者的发现以及事件到合适订阅者的交付都由 Ella 完成。
示例发布者/订阅者类
下面显示了一个类的一个非常简单的示例,该类既是发布者又是订阅者。[Subscriber]
[Publishes(typeof(Image), 1, CopyPolicy = DataCopyPolicy.None)]
class ChangeDetection
{
internal Image _image;
private Image _last;
[Factory]
public ChangeDetection()
{}
[Start]
public void Run()
{
Ella.Subscribe.To<Image>(this, Callback, DataModifyPolicy.NoModify);
}
[Stop]
public void Stop()
{}
internal void PublishEvent()
{
Publish.Event(_image, this, 1);
}
private void Callback(Image image, SubscriptionHandle handle)
{
if (_last == null)
_last = image;
_image = image.AbsDiff(_last);
_last = image;
PublishEvent();
GC.Collect();
}
}
上面的示例代码实现了一个简单的变化检测。我们找到两个连续图像之间的差异。这通常用于在视频流中查找运动。
该类使用属性声明为发布者和订阅者。PublishesAttribute
此外还包含有关其发布的类型、事件 ID 和数据复制策略的信息。在本例中,类型为 Image
的对象以事件 ID 1 发布,无需复制。如上所述,发布者必须提供 Factory
、Start
和 Stop
属性才能创建新实例、启动或停止它。在这里,构造函数定义了 Factory
属性。在定义 Start
属性的 Run()
方法中,该类通过简单调用 Subscribe.To()
来订阅自己到 ImageAcquisiton
组件。订阅提供了一个回调方法,该方法在到达我们订阅的事件数据时被调用。
在调用 Subscribe.To()
的时候,Ella 会查找本地发布者,并且如果订阅者指示,还会查找发布匹配类型对象的远程发布者。如果找到合适的发布者,已发布的数据将被交付给订阅者。Callback()
方法在数据从任何发布者到达时被调用。因此,Callback()
方法将我们订阅的图像作为参数,这意味着我们现在可以访问或进一步处理这些图像。现在是我们——因为我们也是一个发布者——生成要发布的数据的时候了。在这种情况下,我们只是进行一些计算以获取我们想要发布的运动图像。当数据准备好交付给其他订阅者时,我们只需通过调用 Publish.Event()
来完成。
摘要
在本文中,您了解了 Ella 发布/订阅中间件。它是一小段软件,可以大大减轻您编写模块化、分布式应用程序的工作负担。关于 Ella 及其工作原理还有很多可以说的。更多细节可以在 Ella 的网站上找到,所以去看看吧。请留下评论,让我们知道您的想法以及如何进一步改进它。