最近,我一直试图把我的注意力集中在Akka和基于演员的系统的概念上。虽然到目前为止,我对Akka基本原理有了很好的理解,但在集群和远程参与者方面,我仍然在为一些问题而奋斗。
我将尝试用WebSocket chat example that comes with Play Framework 2.0来说明这个问题:有一个包含WebSockets的参与者,它保存了当前连接的用户列表。演员在技术上和逻辑上基本上代表了聊天室。只要在一台服务器上运行一个聊天室,它就能很好地工作。
现在,当我们讨论许多动态聊天室(在任何时候都可以打开/关闭新的聊天室)运行在服务器集群上(根据当前的需求添加或删除单个节点)时,我试图理解如何扩展这个示例。在这种情况下,用户A可以连接到服务器1,而用户B可以连接到服务器2,两者可能在同一个聊天室上交谈。在每个服务器上仍然有一个参与者(对于每个聊天室?)它保存WebSocket实例以接收事件(消息)并将其发布给正确的用户。但从逻辑上讲,服务器1或服务器2上只应该有一个聊天室参与者,其中包含当前连接的用户列表(或类似的任务)。
您将如何实现这一目标,最好是在“纯akka”中,而不添加像ZeroMQ或RabbitMQ这样的附加消息传递系统?
这是我到目前为止想出的,请告诉我这是否有意义:
如果服务器2出现故障,聊天室演员将不得不以某种方式重新创建/移动到服务器2,尽管这不是我现在最关心的问题。我最想知道的是,演员的这种动态发现是如何通过Akka的工具集传播的,这些机器基本上是独立的。
我看Akka的文档已经有一段时间了,所以我可能忽略了这里的显而易见的东西。如果是的话,请告诉我:)
发布于 2012-06-02 14:37:22
我正在从事一个私人项目,这基本上是聊天室示例的一个非常扩展的版本,我还遇到了akka和整个“分散”思维的启动问题。所以我可以告诉你我是如何“解决”我的扩展聊天室的:
我想要一台无需额外配置就可以轻松部署多次的服务器。我使用redis作为所有开放用户会话(他们的ActorRefs的简单序列化)和所有聊天室的存储。
服务器有以下行为体:
WebsocketSession:--它保存到一个用户的连接,处理来自用户的请求,并转发来自system.ChatroomManager:的消息--这是中央广播机构,它部署在服务器的每个实例上。如果用户想要向聊天室发送消息,WebSocketSession-Actor将所有信息发送给ChatroomManager,后者将消息广播给聊天室的所有成员。下面是我的程序:
actorFor-method一起使用的绝对路径)检索聊天室的用户列表,然后将消息发送给每个会话参与者。这些会话参与者然后写信给他们的websockets.。
在每个ChatroomManager中,我都会执行一些ActorRef缓存,从而提高了速度。我认为这与您的方法不同,特别是这些ChatroomManagers处理所有聊天室的请求。但是只有一个演员在一个聊天室是一个单一的失败点,我想避免。此外,这是否会引起更多的信息(如:
如果用户A想与用户B交谈,他们都必须通过服务器1上的聊天室-参与者进行通信。
此外,我还使用akka的功能,如(循环)-routers,在每个系统上创建多个ChatroomManager参与者实例,以处理许多请求。
我花了几天时间来建立整个akka远程基础设施,结合序列化和redis。但是现在我能够创建服务器应用程序的任意数量的实例,这些实例使用redis来共享ActorRef(以绝对路径的形式与ip+port序列化)。
这可能会帮助你更进一步,我愿意接受新的问题(请不要问我的英语;)
发布于 2012-06-20 08:44:16
跨多台机器扩展的关键是尽可能保持可变状态的隔离。尽管您可以使用分布式缓存来协调所有节点之间的状态,但这将使您在扩展到大量节点时出现同步和瓶颈问题。理想情况下,应该只有一个演员知道消息和聊天室的参与者。
问题的核心是,如果聊天室是由运行在一台机器上的单个参与者表示的,或者确实存在这样的一个房间。诀窍是使用标识符(如聊天室的名称)路由与给定聊天室相关的请求。计算名称的散列,并根据数字从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/
发布于 2013-07-02 21:47:26
我会使用Zookeeper+Norbert来了解哪些主机正在上升和下降:
http://www.ibm.com/developerworks/library/j-zookeeper/
现在,我的聊天室服务器场中的每个节点都可以知道逻辑集群中的所有主机。当一个节点离线(或联机)时,它们将得到一个回调。任何节点现在都可以保留当前集群成员的排序列表,散列聊天室ID,并根据列表大小来获得列表中的索引,该列表是应该承载任何给定聊天室的节点。我们可以添加1和重新散列来选择第二个索引(需要一个循环,直到得到一个新的索引)来计算第二个主机,以保存聊天室的第二个副本以进行冗余。在两个聊天室主机中的每一个上都有一个聊天室参与者,它只是将所有聊天消息转发给每个Websocket参与者,后者是聊天室成员。
现在,我们可以通过自定义Akka路由器的两个主动聊天室参与者发送聊天消息。客户端只发送一次消息,路由器将执行散列模式并发送到两个远程聊天室参与者。我会使用twitter雪花算法为发送的消息生成唯一的64位ids。在下面的链接中查看代码的nextId()方法中的算法。可以使用norbert属性设置datacenterId和workerId,以确保在不同服务器上不生成冲突ID:
现在,每个消息的两个副本将通过两个活动聊天室参与者中的每一个发送到每个客户端端点。对于每个Websocket客户端参与者,我都会取消对雪花in的屏蔽,以学习发送消息的datacenterId+workerId号,并跟踪集群中每个主机的最高聊天消息号。然后,我将忽略不高于给定发送方主机在给定客户端上已经看到的任何消息。这将使通过两个活跃的聊天室演员传入的一对消息脱模。
到目前为止,我们已经有了弹性消息,因为如果任何节点死亡,我们都不会丢失聊天室中幸存的副本。消息将自动通过第二个聊天室不受干扰地流动。
接下来,我们需要处理退出集群或被添加回集群的节点。我们将在每个节点中得到一个norbert回调,以通知我们集群成员的更改。在这个回调中,我们可以通过自定义路由器发送一条akka消息,说明新的成员列表和当前主机名。当前主机上的自定义路由器将看到该消息并更新其状态以了解新的集群成员资格,以计算新的一对节点以发送任何给定的聊天室流量。路由器将向所有节点发送对新群集成员资格的确认,以便每个服务器能够跟踪所有服务器何时已赶上成员资格更改,并且现在正在正确发送消息。
会员变更后,幸存的聊天室可能仍处于活动状态。在这种情况下,所有节点上的所有路由器都将继续正常地发送给它,但也会向新的第二个聊天室主机发送一条消息。第二个聊天室可能还没有启动,但这并不是一个问题,因为信息将通过幸存者流动。如果幸存的聊天室在成员资格改变后不再处于活动状态,则所有主机上的所有路由器将首先发送给三个主机;幸存者和两个新节点。可以使用akka死亡监视机制,这样所有节点最终都可以看到幸存的聊天室关闭,从而返回到通过两台主机路由聊天通信。
接下来,我们需要根据具体情况将聊天室从幸存的服务器迁移到一两个新主机上。聊天室演员在某一时刻会收到一条消息,告诉它新的集群成员身份。它将从向新节点发送聊天室成员的副本开始。此消息将在新节点上创建具有正确成员资格的聊天室参与者的新副本。如果幸存者不再是应该容纳聊天室的两个节点之一,那么它将进入退役模式。在退役模式下,它只会将任何消息转发给新的主节点和辅助节点,而不会转发给任何聊天室成员。阿克卡消息转发是完美的这一点。
退役聊天室将侦听来自每个节点的norbert集群成员资格确认消息。最后,它将看到集群中的所有节点都承认了新的集群成员资格。这样,它就知道它不会再收到任何要转发的消息了。然后它就可以自杀了。Akka热交换是实现退役行为的完美方法。
到目前为止,我们已经有了一个弹性消息传递设置,它不会在节点崩溃时丢失消息。在集群成员关系发生变化的时刻,我们将获得内部传输量的尖峰,以便将聊天室复制到新节点。我们还有一个剩余的内部转发消息到节点的风暴,直到所有服务器都赶上了哪个聊天室移动了两个服务器。如果我们想扩大系统的规模,我们可以等到用户流量下降,然后打开一个新的节点。聊天室将自动在新节点上重新分配。
上述描述的基础是阅读下列论文并将其翻译成akka概念:
https://stackoverflow.com/questions/10073446
复制相似问题