集群单例

来源:互联网 发布:导购源码 编辑:程序博客网 时间:2024/06/11 00:01

集群单例

对于某些使用场景,有时候需要强制确保集群中运行的某种类型的actor只有一个。

一些例子:

· 负责某些集群范围的一致性决定或者跨集群系统的动作协作的单点

· 外部系统的单个进入点

· 单个master,多个worker

· 集中化的命名服务,或者路由逻辑

使用单例不应该是首选的设计。它有一些缺点,例如单点瓶颈。单点失败也需要适当的考虑,但是对于一些场景,这个特性关心的是确保另一个单例最终会被启动。

集群单例模式由akka.cluster.singleton.ClusterSingletonManager实现。它管理所有集群节点或者特定角色节点上的单例actorClusterSingletonManager是在集群所有节点上或者特定角色节点上都应该启动的一个actor。实际的单例actor是由最老的节点上的ClusterSingletonManager启动的,从提供的Props创建一个子actorClusterSingletonManager确保某个时间点上任何点至多有一个单例运行。

单例actor总是运行在特定角色的最老的成员上。最老的成员由akka.cluster.Member#isOlderThan确定。当把这个成员从集群中移除时,最老的成员就会发生变化。我们要意识到在短暂的交接处理时间内没有活动的单例。

集群故障检测器会通知,当最老的节点由于某些事情不可达,比如JVM崩溃、硬关闭、网络故障灯。然后新的最老节点就会接管,新的单例actor被创建。对于这些失败场景,不存在优雅的交接,但是以合理的方式阻止超过一个的活动单例。一些极端情况最终用配置超时解决。

你可以使用提供的akka.cluster.singleton.ClusterSingletonProxy 访问单例actor,这个代理将会把所有的消息路由到当前的单例。这个代理会保持跟踪最老的节点,通过明确向单例的ActorSelection发送一个akka.actor.Identify消息来解析单例的ActorRef。如果单例在配置的特定时间内没有应答,那么句周期执行这个消息发送。考虑到实现,可能有一段时间ActorRef不可用,例如节点离开集群。在这些场景中,代理会将发送给单例的消息缓存起来,当单例最终可用时,再将消息传递给它们。当新的消息同代理发送时,如果缓存满了,那么ClusterSingletonProxy会丢弃老的消息。缓存的大小是可配置的,缓存大小设置为0可禁止缓存。

值得注意的是由于这些actor的分布式性质,消息总是可能会丢失的。一如既往的,在单例中需要实现额外的逻辑,在客户端actor要确保递送at-least-once 消息。

单例不会运行在WeaklyUp状态的成员上,如果这个特性是使能的话。

要意识到潜在的问题

这个模式可能一开始看起来很诱人,但是它有些缺点。下面列出一些:

· 集群单例会很快成为性能瓶颈

· 你不能依赖集群单例一直可用例如,当单例运行的节点死了,它需要花费一些时间进行通知,单例会迁移到另一个节点上

· 在使用自动Downing导致的网络分离场景中,可能会发生这样的事情:分离的集群决定场景自己的单例,这意味着在系统中可能运行着多个单例。而集群无法找到它们(因为网络分离)

尤其只最后一个点是你应该关心的通常,当使用集群单例模式时,你应该自己关闭downing节点,不要依赖于基于时间的auto-down特性。

警告:不要同时使用集群单例和自动Downing 由于它允许集群分离成两个单独的集群,这就反过来导致多个单例被启动,每一个分离的集群中都有一个。

例子

假设我们需要一个外部系统的单个进入点。有一个actor接收来自JMX队列的消息,严格要求只有一个JMS消费者必须存在,确保消息按顺序处理。这可能不是你想要设计的东西,但是在通常的真实世界的场景中需要与外部系统交互。

在集群中的每一个节点上,你都需要启动一个ClusterSingletonManager,并提供单例actorProps,在这个场景中就是JMS队列消费者consumer

final ClusterSingletonManagerSettings settings =

  ClusterSingletonManagerSettings.create(system).withRole("worker");

system.actorOf(ClusterSingletonManager.props(

  Props.create(Consumer.class, queue, testActor),

  new End(), settings), "consumer");

这里,我们限制单例节点的角色为"worker",但是所有的节点,角色独立的,可以不使用withRole指定。

这里在实际停止单例actor之前,我们使用应用特定的终止消息关闭资源。注意如果你只需要关闭actorPoisonPill是极好的终止消息。

用上面给定的名字,访问单例可以从任意的集群节点用配置的代理获取:

ClusterSingletonProxySettings proxySettings =

    ClusterSingletonProxySettings.create(system).withRole("worker");

system.actorOf(ClusterSingletonProxy.props("/user/consumer", proxySettings),

    "consumerProxy");

更加复杂的例子可以在Lightbend Activator教程Akka和Java实现的分布式worker找到。

依赖

要使用集群单例,你必须在你的工程中添加如下的依赖:

sbt:

"com.typesafe.akka" %% "akka-cluster-tools" % "2.4.16"

maven:

<dependency>

  <groupId>com.typesafe.akka</groupId>

  <artifactId>akka-cluster-tools_2.11</artifactId>

  <version>2.4.16</version>

</dependency>

配置

下面的配置属性由ClusterSingletonManagerSettings读取,当使用ActorSystem参数创建时。可以修改ClusterSingletonManagerSettings或者从相同层次的另一个配置区创建它。ClusterSingletonManagerSettingsClusterSingletonManager.props工厂方法的一个参数,即每一个单例可以用不同的设置按需配置。

akka.cluster.singleton {

  # The actor name of the child singleton actor.

  singleton-name = "singleton"

 

  # Singleton among the nodes tagged with specified role.

  # If the role is not specified it's a singleton among all nodes in the cluster.

  role = ""

 

  # When a node is becoming oldest it sends hand-over request to previous oldest,

  # that might be leaving the cluster. This is retried with this interval until

  # the previous oldest confirms that the hand over has started or the previous

  # oldest member is removed from the cluster (+ akka.cluster.down-removal-margin).

  hand-over-retry-interval = 1s

 

  # The number of retries are derived from hand-over-retry-interval and

  # akka.cluster.down-removal-margin (or ClusterSingletonManagerSettings.removalMargin),

  # but it will never be less than this property.

  min-number-of-hand-over-retries = 10

}

下面的配置属性由ClusterSingletonProxySettings读取,当用ActorSystem参数创建它时。可以修改ClusterSingletonProxySettings或者从相同层次的另一个配置区创建它。ClusterSingletonProxySettingsClusterSingletonProxy.props工厂方法的一个参数,即每一个单例代理可以用你不同的设置按需配置。

akka.cluster.singleton-proxy {

  # The actor name of the singleton actor that is started by the ClusterSingletonManager

  singleton-name = ${akka.cluster.singleton.singleton-name}

 

  # The role of the cluster nodes where the singleton can be deployed.

  # If the role is not specified then any node will do.

  role = ""

 

  # Interval at which the proxy will try to resolve the singleton instance.

  singleton-identification-interval = 1s

 

  # If the location of the singleton is unknown the proxy will buffer this

  # number of messages and deliver them when the singleton is identified.

  # When the buffer is full old messages will be dropped when new messages are

  # sent via the proxy.

  # Use 0 to disable buffering, i.e. messages will be dropped immediately if

  # the location of the singleton is unknown.

  # Maximum allowed buffer size is 10000.

  buffer-size = 1000

}