# Notify.me 体系结构-同步性

> 原文： [http://highscalability.com/blog/2008/10/27/notifyme-architecture-synchronicity-kills.html](http://highscalability.com/blog/2008/10/27/notifyme-architecture-synchronicity-kills.html)

开始一个新项目的好处是，您终于有机会正确地做它。 当然，您最终会以自己的方式弄乱一切，但是在那一瞬间，世界有了一个完美的秩序，一种让人感到满足和美好的正义。 全新的实时通知交付服务 notify.me 的 CTO Arne Claassen 现在正处于这个蜜月期。

Arne 足够亲切地与我们分享他如何构建通知服务的理念。 我想您会发现它很有趣，因为 Arne 对其系统如何工作进行了许多有用的详细介绍。

他的主要设计理念是最小化围绕同步访问形成的瓶颈，即，当*请求了一些资源并且请求者占用更多资源，等待响应时。 如果无法及时交付所请求的资源，则会堆积越来越多的请求，直到服务器无法接受任何新请求为止。 没有人得到他们想要的东西，而您断电了。 通过将请求和响应分为单独的消息传递操作将同步操作分解为异步操作，可以停止资源过载。 它可以使系统尽可能快地处理积压的请求，而不会导致系统因并行请求过多而崩溃。 在大多数情况下，请求/响应周期是如此之快，以至于它们看起来像线性的事件序列。*

Notify.me 正在采用一种创新且冒险的策略，即使用基于 XMPP 的系统 ejabberd 作为其内部消息传递和路由层。 Erlang 和 Mnesia（Erlang 的数据库）是否能够跟上通信量并在通信量扩展时保持低延迟？ 找出来将会很有趣。

如果您对 notify.me 感兴趣，他们已经为 HS 读者提供了 500 个 Beta 帐户： [http://notify.me/user/account/create/highscale](http://notify.me/user/account/create/highscale)

## 你是谁？

我的名字是 notify.me 的首席技术官 Arne Claassen。 在过去的十年中，我一直在致力于高度可扩展的基于 Web 的应用程序和服务。 这些站点采用了各种传统扩展技术的组合，例如服务器场，缓存，内容预生成以及使用复制和群集的高可用性数据库。 所有这些技术都是减轻许多用户争用的稀缺资源（通常是数据库）的方法。 知道了这些技术的好处和陷阱后，我就成为专注于规避稀缺资源场景的架构师系统的焦点。

## 什么是 notify.me，为什么创建它，为什么它是一件好事？

notify.me 是我们首席执行官 Jason Wieland 的创意。 这是一种近乎实时的通知服务，可提醒用户注意网络上发布的新内容。

旨在解决普通用户难以及时掌握网络上发生的时间紧迫事件的麻烦。 例如，一旦发布符合其搜索条件的新公寓，就会在 Craigslist 上搜索公寓的用户收到警报。 notify.me 所做的艰巨工作是反复检查 Craigslist 是否有新列表，并在发布新列表时提醒用户。 通知可以传递到即时通讯程序，桌面应用程序，移动设备，电子邮件和 Web 应用程序。

我们的目标是创建和发布开放的 API，使人们能够构建新的有趣的应用程序以生成和传递信息。

## 您的服务与人们可能熟悉的其他服务相比如何？ 像 Twitter，Friend Feed，Gnip 或 Yahoo Pipes？

有很多公司处于我们的竞争格局中，其中有些是直接竞争对手，例如 yotify.com 或 Alerts.com。 主要区别在于我们的方法。 Yotify 和警报的重点是作为通知门户网站供用户访问。 notify.me 是一个实用程序，重点是通过 XMPP 和 REST API 提供网站上所有可用的功能，允许用户与来自其选择应用程序的通知进行交互。 我们还允许将消息升级到目的地。 例如，如果用户未登录其 IM 或状态为离开，则可以升级通知并将其路由到其移动设备。

在消息传递领域，我们几乎与 Twitter 相反。 Twitter 基于其自己的用户生成的内容向内发布模型。 有人发了一条推文，并发布给了他们的关注者。 notify.me 正在创建一个面向外部的消息传递系统。 用户添加支持 Web feed 标准的任何网站，或将现有的通知电子邮件重定向给我们。 如果有的话，我们是一条消息传递管道，是对 titter 的补充（更多内容请参见下文）。

Friendfeed 在将您所有的社交网络合并到一个集中区域方面做得很好。 他们的主要重点是构建与捣碎的供稿进行交互的功能和工具。 此供稿非常适合作为 notify.me 的源添加，允许用户通过即时通讯程序接收所有社交网络更新。 社交瘾君子想要的功能是能够实时了解墙上是否有新帖子，以便您可以立即做出响应。

雅虎管道将被视为可能的合作伙伴，类似于他们向 Netvibes 和 Newsgator 追加销售的方式。 他们的重点是提供一个直观的编程界面，以便能够操纵提要并创建有用的混搭。

例如，[热门交易搜索](http://pipes.yahoo.com/pipes/pipe.info?_id=_E8FgV_42xGWa5pfZVUMqA)是一个漂亮的管道，可在一组网站上搜索最佳交易。 由于目标选项有限，用户可能不想使用 Yahoo 自己的通知选项。

在我们的 Beta 组中，我们看到用户添加 eBay 链接的活动类似。 ebay 有一个竞争性的通知管道来通知 notify.me，但是用户仍然在我们的服务中添加 ebay 搜索链接。 事实证明，他们希望在一个中央位置来管理各种新闻源。

Gnip 是纯粹的基础设施。 我们有类似的技术，但我们要追求完全不同的市场。

我们产品尚未公开的另一个核心功能是，我们的管道是双向的，即任何数据源也可以是目的地，反之亦然。 这样做的主要好处是能够响应消息，例如确认收到的支持通知单。 双向通信将需要与 notify.me API 集成，源可以通过该 API 来传递回复选项。

我们目前正在与 Twitter API 进行深度集成，以通过 IM 在您已收到其他通知的同一通道中为推文提供双向功能。

## 您能否解释 notify.me 的不同部分以及它们如何连接在一起？

一般而言，我们的系统由三个子系统组成，每个子系统都有许多实现。
1\. **接收**由 rss 和电子邮件接收器组成，它们会不断检查用户的电子邮件地址（ [[受电子邮件保护]](/cdn-cgi/l/email-protection) ）和用户的供稿中是否有新数据。 新数据变成通知，并传播到路由。
2\. **路由**负责将用户的通知发送到正确的交付组件。 路由是系统中与用户进行交互以进行管理的点，例如更改源和目​​的地以及查看历史记录。 通知历史记录是一个专门的传递组件，即使在通过整个管道后，也可以通过网站仔细阅读所有消息。
3\. **传递**当前由历史记录（总是获取消息），Xmpp IM，SMS 和电子邮件以及正在开发的私有 RSS，AIM 和 MSN 组成。 从更高的技术层面来看，此系统的拓扑结构由两个单独的消息总线组成：
1\. **存储转发队列**（使用 [simpleMQ](http://sourceforge.net/projects/simpleMQ) ）
2 。 **XMPP** （使用 [ejabberd](http://www.ejabberd.im/) ）

**摄取端使用存储和转发队列**来分发摄取工作，并且通常在任何地方进行 数据在用户的路由规则可见之前进行处理。 这允许扩展灵活性以及在组件故障期间进行进程隔离。

**Xmpp 总线**被称为 Avatar 总线，之所以这样命名，是因为每个数据拥有实体均由守护进程表示，该进程是该实体数据的唯一授权。 我们有四种类型的化身，即 Monitor，Agent，Source 和 User
1。 **Monitor** 化身只是负责观察实例运行状况并根据需要旋转和关闭其他计算节点的负责方。
2\. **代理**化身是将网关提供给我们用户的状态信息并将消息传递给用户的传递网关。
3\. **源**化身是摄取器，例如 RSS。 该化身从存储和转发队列中提取新消息，并将新消息通知其订户。
4\. **用户**化身可保留特定用户的所有配置和消息传递数据。 它负责接收来自摄取头像的新通知，确定路由并将消息推送到适当的传递代理，因为该代理声明了执行该传递的能力。

## 您面对哪些特殊挑战，如何克服这些挑战？ 您考虑了哪些选择，为什么决定以其他方式进行选择？

从一开始，我们的主要目标就是避免瓶颈和阻碍水平扩展的障碍。 最初，我们计划将整个系统构建为通过队列的无状态消息流，沿线的每个守护程序都负责流经它的数据，然后将其合并，复用和路由到下一个点，直到实现交付为止。 这意味着除了备份队列之外，没有任何一个部件的故障会影响整个部件。

但是，我们很早就意识到，一旦为用户指定了一条消息，我们就需要能够跟踪消息的位置并能够根据用户的配置和状态动态地重新路由它。 这导致我们通过 REST API 在守护程序之间添加了一些不适当的耦合。 由于我们仍在将处理迁移到组合队列/异步总线体系结构，因此仍然存在一些这种问题。

当我们意识到没有状态的纯消息传递将无法满足我们的动态需求时，简单的解决方案是返回到尝试过的状态保持器，即中央关系数据库。 知道我们的扩展目标后，这会引入一个失败点，我们迟早将无法缓解这一点。 我们决定以不同的方式来看待我们的状态，而不是考虑根据功能（即摄取，解析，转换，路由，交付）基于流过的数据查询状态来创建处理单元，而是想到了这些单元 在数据所有权方面，即来源和目的地（用户）。 一旦走上了轨道，几乎没有共享状态，我们可以更改存储模式，让每个数据所有者对自己的数据负责，从而允许持久层的水平扩展以及更高效的缓存。

跨所有者访问数据的其余需求是分析。 在许多系统中，分析是存在中央数据库的主要原因，因为事实和维度经常在生产模式中混杂在一起。 就我们的目的而言，此数据与生产无关，因此永远不会影响活动容量。 用法和状态更改被视为不可变的事件，它们在发生时排队进入我们的存储转发系统。 我们存储转发队列的性质使我们能够自动将所有主机中的所有这些事件收集到中央存档中，然后可以通过 ETL 流程将其处理为事实和维度数据。 这允许对使用情况进行近实时跟踪，而不会影响面向用户的系统。

## 您能否再解释一下您对 XMPP 的选择？ 它主要用作 EC2 节点上的联合 XMPP 服务器之间的消息总线吗？ 是否将 XMPP 队列用作来自所有来源的每个用户的消息的队列，然后再将其推送给用户？

我们有三个不同的 xmpp 群集，它们利用联合来进行跨阶段的操作：用户，代理和头像总线。

### 用户数

这是一台常规的 xmpp IM 服务器，我们在该服务器上为每个用户创建帐户，向他们提供可从任何具有 Xmpp 功能的客户端使用的 IM 帐户。 此帐户还用作我们的桌面应用程序登录时的用户，这将成为我们用于第三方消息提取和分发的 API 的身份验证

### 代理商

连接到该群集的守护程序充当我们的内部 Avatar 总线与外部客户端之间的通信桥。 目前，这主要是用于与聊天客户端进行通信，因为每个用户都被分配了一个他们无需我们即可通过其进行通信的代理，无论他们使用其默认帐户还是使用诸如 jabber.org，googletalk 等的第三方帐户。 通过专用代理的这些代理测试使用 Xmpp RPC 的客户端 API。 将来，我们还将为第三方集成提供完整的 XMPP 和 REST API，这些 API 将使用代理与 Avatar 总线进行通信。

我之前提到过，代理程序也是化身，但是它们有点特殊，因为它们在 Avatar 总线上没有用户，而是通过跨服务器联合与其他化身进行对话。 我们目前还在为 Oscar 和 MSN 网络建立代理，由于它们的本机传输不是联邦的，因此它们将直接位于头像总线上。 我们还计划评估其他网络，以获得将来的支持。

### 头像

头像是我们的内部消息总线，用于路由和处理所有命令和消息。 尽管我们确实利用在线状态进行监视，但我们主要在头像之间使用直接消息传递和基于 IQ 的 RPC 节。

那么什么是化身？ 它是一个守护程序（单个物理守护程序进程可以托管许多化身守护程序），是某些外部实体数据的权限。 即 在 notify.me 中注册的每个用户都有一个化身，用于监视代理的状态变化，接收照顾该用户的消息并负责将这些消息路由到适当的传递渠道。 这意味着每个头像都是关于该用户的所有数据的唯一授权者，并负责持久化数据。 如果系统的其他部分想要了解有关该用户的信息或修改其数据，它将使用 Xmpp RPC 与虚拟形象进行对话，而不是与某些中央数据库进行对话。 目前，化身持续存在于磁盘和 SimpleDB 中，同时保持 ttl 管制的高速缓存正在进行中。 由于仅化身可以写入自己的数据，因此无需检查数据库，而是可以将其内存和磁盘缓存视为权威，而 SDB 主要用于写入。 仅在节点发生故障时才需要读取以在另一节点上启动化身。

在巴士的另一端，有我们的摄入装置。 摄取器由许多守护程序组成，通常在针对外部源的轮询循环上运行，将新数据排队到我们的存储转发队列中，适当的摄取器头像会拾取新消息并将其分发给其订阅者。 在摄取器头像方案中，它是订阅和路由数据的权限。

这是一个典型的用例：用户通过 Web 界面订阅 RSS feed。 Web 界面将请求发送到用户的头像，该头像将保留订阅以供参考，然后从 rss 接收器请求订阅。 随着新的 rss 项目到达，rss 摄入器会将项目多路复用到订阅该供稿的所有用户头像。 用户化身依次确定适当的投放机制并安排投放时间。 一般而言，这意味着用户头像通过``用户代理''订阅了用户的 Xmpp 状态。 在用户处于接受消息的适当状态之前，用户化身排队 rss 项目。 一旦用户准备好接收通知，就将状态更改从代理传播到内部总线，然后用户化身将 rss 项目发送给代理，代理再将其发送给用户。

目前，所有头像均始终在线（即使大部分都是空闲的），这对于我们当前的用户群来说还不错。 我们的计划是修改 ejabberd 的脱机存储模块，以便我们可以盲目发射节并使队列中的消息向监视器发出信号以启动用于目标 XmppId 的适当化身。 一旦建立了该系统，我们将能够按需启动化身，并在闲置时将其关闭。

## 您希望在什么流量负载下破坏当前的体系结构，您的计划是什么？

由于我们的系统是分布式的，并且是设计异步的，因此应避免在负载下发生系统范围的故障。 但是，尽管避免了所有常见的瓶颈，但事实是我们的消息总线使这一切成为可能，这可能成为我们的限制因素，要么是因为它无法处理化身的数量（总线上的节点），要么是因为延迟 巴士变得无法接受。 我们只是开始使用头像系统作为我们的骨干网，所以它仍然有点脆弱，我们仍在对 ejabberd 进行负载测试以确定在什么时候遇到限制因素。

虽然我们已经在集群 ejabberd，但 mnesia 数据库复制和跨节点震颤的负载意味着连接数或延迟都将最终导致集群失败或仅消耗太多内存来进行管理。

由于我们的消息传递主要是点对点的，因此我们希望可以将用户群划分为头像孤岛，每个孤岛都托管在专用的头像子域群集中，从而减少了消息和连接负载。 只要我们的筒仓经过适当设计，以将跨子域的中断保持在最低水平，我们就应该能够拥有 n 个筒仓来保持负载最大。

要避免这种体系结构失败，我们面临的最大挑战就是永远保持警惕，防止引入会造成消息传递瓶颈的功能。 我们通过单个处理器或处理器系列的大量消息流量会引入依赖关系，我们无法通过子域划分来扩展自己。

## 相关文章

*   [notify.me 技术博客-INotification](http://techblog.notify.me/)*   [Flickr-预先进行必要的工作并将其余的工作排入队列](http://highscalability.com/strategy-flickr-do-essential-work-front-and-queue-rest)

好东西！ 很高兴看到 erlang（和 ejabberd）越来越受欢迎！

尽早优化，对吧？ 所有的扩展工作，尽管疯狂有趣，并且在智力上令人兴奋，但直到成功真正需要时，它们才如此。

您可能还说这是浪费。 :)

我不认为早期优化在这里是错误的。 由于执行此操作的大多数工具几乎都是“开箱即用”的，因此通常只需要简单（但可能会很耗时）的管道即可。 这个设置似乎没有什么真正复杂的。

我想知道在以下情况下 notify.me 采取了哪些措施来克服这种即时性：

假设那里有 10,000 名程序员从 CraigList 订阅了“高级程序员”。 一旦一家公司在 CraigList 上发布了“高级程序员”的招聘广告，所有这 10,000 名程序员都会收到 IM，SMS 或电子邮件的通知。 （希望我到目前为止是对的），notify.me 如何保证通知将同时（几乎）发送给订阅者？ 如果第一个程序员收到通知的时间与最后一个程序员收到通知的时间之间的时间间隔大于 10 分钟，则这是毫无意义的通知。

希望我说得足够清楚。

关于早期优化：由于我们没有处理已建立的沟通渠道（就像大多数网络媒体资源一样），因此我们无法明确定义提升的方式。 看看我们从第一种方法中学到的知识，很明显地，我们需要一些可以扩展的基线管道，因为一旦需要，就可能会重新构造我们路由消息的方式 。

您好，我叫 Jason Wieland，我是 notify.me 的首席执行官，我只想对所有评论（公开和私人）表示感谢，并回答最后一个帖子。

飞行员负责将通知发送给用户。 这些是分布式守护程序，旨在可伸缩且不会过载。 实时消息传递（读取 IM，桌面应用程序）引擎每秒可以处理数千条消息。 电子邮件和短信（在获得简短代码之前）是另一回事。 电子邮件是通过多头 smtp 集群发送的，但是，我们只能依靠 smtp 协议流程的工作方式。 因此可能会有时间波动。

我们建议任何想要立即收到通知的人使用 Instant Messenger 或我们的桌面应用程序。

谢谢，

杰森

Jason，
谢谢您的答复。
正如您提到的，

“实时消息传递（读取 IM，桌面应用程序）引擎每秒可以处理数千条消息。”

使用 IM 的订户可以同时接收通知吗？ 是否没有循环遍历所有订户并向其发送通知？ 因为我绝对是可伸缩性的新手，所以我能想像出要解决此问题的方法是导入“ fork-join”模型，以同步所有守护程序。

祝您一切顺利
刘浩

Hello Hao Liu,

用户可以在同一批次中收到多个通知。 批处理是我们创建的 2 秒窗口，用于将同时到达的消息分组在一起。 每个用户只有一个轻量级守护程序（称为 Avatar）。 每个用户平均每天会收到 30 条通知，因此大多数时间它处于空闲状态，并且有足够的空间来处理一组消息。 虽然它们要在相同的时间实例中进行处理。 它们将在不到一秒的时间内被处理（我们的目标是每位用户每秒处理约 100 条消息）。