发现集群中的Akka演员

时间:2012-04-09 12:49:57

标签: playframework playframework-2.0 akka akka-cluster

我最近一直试图围绕Akka和基于演员的系统的概念。虽然我对Akka的基本原理有了很好的理解,但现在我仍然在集群和远程演员方面遇到一些问题。

我将尝试使用WebSocket chat example that comes with Play Framework 2.0来说明问题:有一个持有WebSockets并保留当前连接用户列表的actor。演员基本上在技术上和逻辑上代表聊天室。只要在一台服务器上运行一个聊天室,这就完美无缺。

现在,我正在尝试理解当我们讨论在服务器群集上运行的许多动态聊天室(可以随时打开/关闭的新房间)时,如何扩展此示例(单节点为根据当前需求添加或删除)。在这种情况下,用户A可以连接到服务器1,而用户B连接到服务器2.两者可能在同一个聊天室中进行通话。在每个服务器上仍然会有一个actor(对于每个聊天室?),它保存WebSocket实例以接收和发布事件(消息)给正确的用户。但从逻辑上讲,服务器1或服务器2上只应有一个聊天室角色,其中包含当前连接用户(或类似任务)的列表。

您将如何实现这一目标,最好是在“纯粹的akka​​”中,而不添加ZeroMQ或RabbitMQ等额外的消息传递系统?

这是我到目前为止所提出的,请告诉我这是否有意义:

  1. 用户A连接到服务器1,并且分配了一个保存其WebSocket的actor。
  2. 演员检查(使用Router?EventBus?还有其他什么?)是否在任何连接的集群节点上存在活动聊天室的“聊天室演员”。因为它不会以某种方式请求创建一个新的聊天室演员,并将发送和接收此演员的未来聊天消息。
  3. 用户B在服务器2上连接,并且还为他的WebSocket分配了一个actor。
  4. 它还检查所请求的聊天室的actor是否存在于某处并在服务器1上找到它。
  5. 服务器1上的聊天室演员现在充当给定聊天室的中心,向所有“已连接”的聊天成员演员发送消息并分发传入的聊天室。
  6. 如果服务器2出现故障,聊天室演员将不得不以某种方式重新创建/移动到服务器2,尽管这不是我现在主要关注的问题。我最想知道演员如何通过使用Akka的工具集来传播关于各种基本上独立的机器的动态发现。

    我一直在看Akka的文档很长一段时间了,所以也许我错过了这里显而易见的事情。如果是的话,请赐教: - )

3 个答案:

答案 0 :(得分:12)

我正在开发一个私人项目,它基本上是聊天室示例的一个非常扩展的版本,我也有akka的启动问题和整个“分散”思维。 所以我可以告诉你我如何“解决”我的扩展聊天室:

我想要一台可以轻松部署多次而无需额外配置的服务器。 我使用redis作为所有开放用户会话(其ActorRefs的简单序列化)和所有聊天室的存储。

服务器有以下角色:

  • WebsocketSession:保存与一个用户的连接,处理来自用户的请求,并转发来自系统的消息。
  • ChatroomManager:这是中央广播公司,它部署在每个实例上 的服务器。如果用户想要向聊天室发送消息,则WebSocketSession-Actor会将所有信息发送到ChatroomManager-Actor,然后ChatroomManager-Actor将消息广播给聊天室的所有成员。

所以这是我的程序:

  1. 用户A连接到服务器1,服务器1分配新的WebsocketSession。此actor将此actor的绝对路径插入redis。
  2. 用户A加入聊天室X,该聊天室X还将其绝对路径(我将其用作用户会话的唯一ID)插入到redis中(每个聊天室都设置了“连接”)
  3. 用户B连接到服务器2 - > redis的
  4. 用户B加入聊天室X - > redis的
  5. 用户B向聊天室X发送消息,如下所示:用户B通过Websocket将他的消息发送给他的session-actor,后者(在一些检查之后)向ChatroomManager发送一个actor消息。该actor实际上从redis(与akka的actorFor - 方法一起使用的绝对路径)检索聊天室的用户列表,然后将消息发送到每个session-actor。然后这些session-actors写入他们的websockets。
  6. 在每个ChatroomManager-actor中,我进行了一些ActorRef缓存,这提供了额外的速度。 我认为这与您的方法不同,特别是这些ChatroomManagers处理所有聊天室的请求。但是在一个聊天室中有一个演员是我想要避免的单点故障。此外,这会导致更多消息,例如:

    • 用户A和用户B在服务器1上。
    • 聊天室X在服务器2上。

    如果用户A想要与用户B交谈,他们都必须通过服务器1上的聊天室演员进行通信。

    此外,我使用akka的功能(如(循环) - 路由器在每个系统上创建ChatroomManager-actor的多个实例来处理许多请求。

    我花了几天时间来建立整个akka远程基础设施以及序列化和redis。但现在我能够创建任意数量的服务器应用程序实例,使用redis共享ActorRef s(序列化为带有ip +端口的绝对路径)。

    这可能对你有所帮助,我愿意接受新的问题(请不要关注我的英语;)。

答案 1 :(得分:10)

跨多台计算机扩展的关键是尽可能保持可变状态。虽然您可以使用分布式缓存来协调所有节点的状态,但这会在扩展到大量节点时为您提供同步和瓶颈问题。理想情况下,应该有一个演员知道聊天室中的消息和参与者。

问题的核心是,如果聊天室由在单台机器上运行的单个演员表示 - 或者实际上是否存在这样的房间。诀窍是使用标识符(例如聊天室的名称)来路由与给定聊天室相关的请求。计算名称的哈希值,并根据数字从n个框中选择一个。该节点将了解其当前的聊天室,并可以安全地为您找到或创建正确的聊天室演员。

您可以查看以下关于在Akka中讨论群集和扩展的博客文章:

http://blog.softmemes.com/2012/06/16/clustered-akka-building-akka-2-2-today-part-1/

http://blog.softmemes.com/2012/06/16/clustered-akka-building-akka-2-2-today-part-2/

答案 2 :(得分:7)

我会使用Zookeeper + Norbert来了解哪些主机正在上下运行:

http://www.ibm.com/developerworks/library/j-zookeeper/

现在,我的聊天室服务器场中的每个节点都可以知道逻辑集群中的所有主机。当节点脱机(或联机)时,它们将获得回调。任何节点现在都可以保留当前集群成员的排序列表,散列聊天室ID,并按列表大小调整mod以获取列表中的索引,该列表是应该托管任何给定聊天室的节点。我们可以添加1和rehash来选择第二个索引(需要一个循环,直到获得一个新的索引)来计算第二个主机以保留聊天室的第二个副本以实现冗余。在两个聊天室主机中的每一个上都是一个聊天室演员,它只是将所有聊天消息转发给每个Websocket演员,这是一个聊天室成员。

现在,我们可以通过活跃的聊天室演员和自定义的Akka路由器发送聊天消息。客户端只发送一次消息,路由器将执行散列模式并发送给两个远程聊天室演员。我会使用twitter雪花算法为正在发送的消息生成唯一的64位ID。请参阅以下链接中代码的nextId()方法中的算法。可以使用norbert属性设置datacenterId和workerId,以确保不会在不同服务器上生成冲突ID:

https://github.com/twitter/snowflake/blob/master/src/main/scala/com/twitter/service/snowflake/IdWorker.scala

现在,每个消息的两个副本将通过两个活动聊天室演员中的每一个进入每个客户端端点。在每个Websocket客户端actor上,我将取消位雪花ID的位,以了解发送消息的datacenterId + workerId号,并跟踪从群集中每个主机看到的最高聊天消息号。然后我会忽略任何不高于给定发送方主机的给定客户端已经看到的消息。这将重复删除通过两个活跃的聊天室演员传入的消息。

到目前为止一切顺利;我们会有弹性消息传递,如果任何节点死亡,我们不会丢失聊天室的一个幸存的副本。消息将自动通过第二个聊天室不间断地流动。

接下来我们需要处理从群集中退出或被添加回群集的节点。我们将在每个节点内获得一个norbert回调,以通知我们集群成员资格的变化。在此回调中,我们可以通过自定义路由器发出一条akka消息,说明新的成员资格列表和当前的主机名。当前主机上的自定义路由器将看到该消息并更新其状态以了解新的集群成员资格,以计算新的节点对以通过发送任何给定的聊天室流量。这种对新集群成员身份的确认将由路由器发送到所有节点,以便每个服务器都可以跟踪所有服务器何时赶上成员身份更改并正在正确发送消息。

成员变更后,幸存的聊天室可能仍然有效。在这种情况下,所有节点上的所有路由器将继续正常发送给它,但也会以推测方式向新的第二个聊天室主机发送消息。第二个聊天室可能尚未启动,但这不是问题,因为消息将通过幸存者流动。如果在成员资格更改后,幸存的聊天室不再处于活动状态,则所有主机上的所有路由器将首先发送给三台主机;幸存者和两个新节点。可以使用akka死亡监视机制,以便所有节点最终可以看到幸存的聊天室关闭,以通过两个主机返回路由聊天流量。

接下来,我们需要根据具体情况将聊天室中的聊天室迁移到一个或两个新主机上。冲浪的聊天室演员将在某个时候获得一条消息,告诉它新的集群成员资格。它将首先将聊天室成员资格的副本发送到新节点。此消息将在新节点上创建具有正确成员身份的聊天室actor的新副本。如果幸存者不再是应该容纳聊天室的两个节点之一,它将进入退役模式。在退役模式下,它只会将任何消息转发到新的主节点和辅助节点而不转发给任何聊天室成员。 Akka消息转发是完美的。

退役聊天室将侦听来自每个节点的norbert群集成员身份确认消息。最终,它将看到集群中的所有节点都已确认新的集群成员资格。然后它知道它将不再接收任何进一步转发的消息。它可以自杀。 Akka hotswapping非常适合实施退役行为。

到目前为止一切顺利;我们有一个弹性的消息传递设置,它不会因节点崩溃而丢失消息。在集群成员资格发生变化的时刻,我们将获得一个内部流量峰值,以便将聊天室复制到新节点。我们还有一些剩余的节点转发消息到节点,直到所有服务器都赶上了哪些聊天室移动了两个服务器。如果我们想要扩展系统,我们可以等到用户流量的低点,然后打开一个新节点。聊天室将自动在新节点上重新分配。

以上描述基于阅读以下论文并将其翻译成akka概念:

https://www.dropbox.com/s/iihpq9bjcfver07/VLDB-Paper.pdf