Split Brain Resolver

当使用Akka cluster时,必须要考虑如何处理集群分区(split brain)和 机器宕机 (Jvm和硬件故障)。如果在使用Cluster Singleton或Cluster Sharding,尤其是与Akka Persistence一起使用时,解决这个问题是至关重要的。

问题

分布式系统的基本的问题就是,系统的观察者不知道到底是网络分区还是机器故障。例如:一个节点故障了,但是另外一个节点不会知道他是宕机永远不可达,还是网络问题一会就会恢复。由于必须在有限的时间内做出判断,所以不能区分到底是短暂的还是永久的故障。并且往往短暂的故障持续时间也会超过系统的决策时间。

第三种类型的问题是,如果进程无响应,例如:由于过载,CPU匮乏或长时间垃圾收集暂停。这也是无法从网络分区和宕机中区分出来的。 我们唯一的决定信号是“在给定时间内没有回复心跳”,这意味着导致延误或心跳丢失的现象彼此不可区分,并且必须以相同的方式处理。

发生宕机时,我们希望立即从集群中删除受影响的节点。 1、当存在网络分区或无响应的进程时,我们希望等待一段时间,希望这是一个暂时的问题,系统会再次恢复过来,但在某些时候,我们必须放弃其中的一部分。 2、此外,某些功能在分区过程中并不完全可用,因此如果分区时间过长,分区可能不是很重要。这两个目标相互冲突,我们可以在 快速移除崩溃节点 和 快速应对网络分区 之间进行权衡。(注:宕机节点需要快速删掉,网络分区需要等一会,所以在选择上就要进行权衡)

这是一个难以解决的问题,由于网络分区时不同侧的节点不能相互通信。关于分区的哪一侧该继续运行,哪一侧该结束,两个分区必须要做出相同的决策。

Akka集群有一个失败检测器,它会注意到网络分区和机器崩溃(但它不能区分这两者)。它使用定期心跳消息来检查节点是否可用。 这些由故障检测器发现的节点被称为不可达的节点,如果不可达节点再次发通心跳,那么就会变成可达节点。

故障检测器不能够在所有场景下都做出正确决定。原始的方法就是在超过一定的时间后删除掉不可达节点。这对宕机和短暂的网络分区是有效的,但是不能解决长时间网络分区的问题。网络分区的两端都会将另一端视为无法访问,并在一段时间后将其从群集成员中删除。 由于这发生在双方,结果是创建了两个独立的群集。This approach is provided by the opt-in (off by default) auto-down feature in the OSS version of Akka Cluster.

如果将基于超时的auto-down功能与Cluster Singleton或Cluster Sharding结合使用,这意味着将运行两个具有相同标识符的Cluster Singleton或Cluster Sharding。 例如,当与Akka Persistence一起使用时,可能导致具有相同persistenceId的持久化actor的两个实例正在同时运行并写入同一个持续事件流,这将产生致命的后果。

Akka集群中的默认设置不会自动删除无法访问的节点, 建议由操作人员或外部监控系统决定应采取的措施。 这是一个有效的解决方案,但如果您因为其他原因没有该员工或外部系统,则会很不方便。

如果无法访问的节点完全没有关闭,它们仍然是集群成员的一部分。 这意味着群集Singleton和Cluster Shardin不会将故障转移到另一个节点。虽然有无法访问的节点,但加入群集的新节点不会将状态置为Up(也就是分区状态时,新节点加入不进来)。同样,在所有不可达节点都解决之前,不会删除离开的成员(同样这时有节点离开集群,也不会影响集群的信息)。 换句话说,让无法访问的成员无限期地工作是不可取的。

随着问题的引入,是时候看下 网络分区、无响应节点和宕机节点 的解决策略了。

Using the Split Brain Resolver
要使用Split Brain Resolver功能,必须添加对akka-split-brain-resolver组件依赖。
sbt : "com.lightbend.akka" %% "akka-split-brain-resolver" % "1.1.0"
Gradle:
dependencies {
compile group: 'com.typesafe.akka', name: 'akka-split-brain-resolver_2.11', version: '1.1.0'
}
maven:

com.lightbend.akka
akka-split-brain-resolver_2.11
1.1.0

启用Split Brain Resolver:,您需要将ActorSystem (* application.conf *)中的akka.cluster.downing-provider-class配置为
akka.cluster.downing-provider-class = "com.lightbend.akka.sbr.SplitBrainResolverProvider"

具体的策略如下所介绍:
没有一个方案可以解决上述所有问题。你必须选一种最适合你系统的策略。每个策略都有失败的场景。

通过配置akka.cluster.split-brain-resolver.active-strategy.来让策略生效。
注:同时也必须删除akka.cluster.auto-down-unreachable-after配置。

在集群达到稳定之前这个策略还是没有生效的。在网络分区时新增成员也不会影响集群状态。

// To enable the split brain resolver you first need to enable the provider in your application.conf:
// akka.cluster.downing-provider-class = "com.lightbend.akka.sbr.SplitBrainResolverProvider"

akka.cluster.split-brain-resolver {
// Select one of the available strategies (see descriptions below):
// static-quorum, keep-majority, keep-oldest, keep-referee
// if left "off" when the downing provider is enabled cluster startup will fail.
active-strategy = off

// Time margin after which shards or singletons that belonged to a downed/removed
// partition are created in surviving partition. The purpose of this margin is that
// in case of a network partition the persistent actors in the non-surviving partitions
// must be stopped before corresponding persistent actors are started somewhere else.
// This is useful if you implement downing strategies that handle network partitions,
// e.g. by keeping the larger side of the partition and shutting down the smaller side.
// Decision is taken by the strategy when there has been no membership or
// reachability changes for this duration, i.e. the cluster state is stable.
stable-after = 20s

注:
stable-after:一个时间段,用来将持久化的actor从将要删除的分区中移到 其他分区。
active-strategy = off :active-strategy配置为off那么集群分区策略不会生效
}

stable-after 时间不要设置的太短,更不要小于整个系统的通信时间,建议设置如下:
cluster size stable-after
5 7 s
10 10 s
20 13 s
50 17 s
其余不同的策略还有其他配置,下面将会介绍:

分区中将要关闭的一部分,会使用cluster down命令从集群成员中删除。

当从集群中删除节点时,最好终止ActorSystem并退出JVM。
在Akka 2.5.x中 使用Coordinated Shutdown终止ActorSystem,但是退出Jvm建议使用如下配置:
akka.coordinated-shutdown.exit-jvm = on

在Akka 2.4.x中 , ActorSystem不会自动关闭。 要实现这个,你必须关闭ActorSystem ,然后像这样退出JVM:
/*
import java.util.concurrent.TimeUnit;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import akka.cluster.Cluster;
*/
Cluster.get(system).registerOnMemberRemoved(() -> {
// exit JVM when ActorSystem has been terminated
system.registerOnTermination(() -> System.exit(0));

// shut down ActorSystem
system.terminate();

// In case ActorSystem shutdown takes longer than 10 seconds,
// exit the JVM forcefully anyway.
// We must spawn a separate thread to not block current thread,
// since that would have blocked the shutdown of the ActorSystem.
new Thread() {
@Override public void run(){
try {
Await.ready(system.whenTerminated(), Duration.create(10, TimeUnit.SECONDS));
} catch (Exception e) {
System.exit(-1);
}

}

}.start();
});
Note:
Some legacy containers may block calls to System.exit(..) and you may have to find an alternate way to shut the app down. For example, when running Akka on top of a Spring / Tomcat setup, you could replace the call to System.exit(..) with a call to Spring’s ApplicationContext .close() method (or with a HTTP call to Tomcat Manager’s API to un-deploy the app).


Static Quorum(静态法定人数)

该策略会关闭分区中节点数量小于quorum-size的部分。换句话说, quorum-size定义了集群必须运行的最小节点数量。
如果你的群集节点数量固定时,或者集群中某个节点具有确定角色,此策略是一个不错的选择。
例如,在一个9节点群集中,您将配置quorum-size为5.如果存在4个和5个节点的网络分割,则包含5个节点的边将生存下来,而其他4个节点将停止。 之后,在5节点群集中,不能再处理更多的故障,因为剩余的群集大小将小于5.在5节点群集中发生另一次故障的情况下,所有节点都将被关闭。
因此,在删除旧节点时加入新节点非常重要。

这样做的另一个结果是,如果在启动群集时存在无法访问的节点,则在达到此限制之前,群集可能会立即关闭。 如果您几乎同时启动所有节点,或者在the leader将“joining”更改为“up”之前使用akka.cluster.min-nr-of-members定义所需的成员数量,则这不是问题。您可以调整超时后,使用stable-after设置进行下达决策。

注意不要是系统的节点数量超过quorum-size * 2 - 1,这样就可能形成两个单独的集群。

1.如果群集分成3个(或更多)部分,那么每个小于配置的quorum-size将自行关闭,并可能关闭整个群集。

2.如果超过了quorum-size数量的分区被关闭了,同时其他运行节点会自行关闭,因为他们认为它们不是大多数,从而导致整个集群终止。(注:一个较大的分区被关闭,会导致其他较小的分区关闭,即时数量多于quorum-size)

决策可以基于具有配置role的节点而不是集群中的所有节点。 当某些类型的节点比其他节点更有价值时,这会很有用。 例如,您可能有一些节点负责持久性数据和一些具有无状态工作服务的节点。 那么尽可能多地保留持久数据节点可能更重要,即使这意味着要关闭更多的工作节点。

这个role还有另一个用途。 通过为集群中的少数(例如7个)稳定节点定义role并在static-quorum配置中使用该角色,您将能够动态地添加和删除没有此角色的其他节点,并且能够更好的决策什么节点需要keep running 、什么节点需要shut down。这种方法相对于keep-majority (下文将描述)的优势在于,您不必冒险将群集分成两个独立的群集。同样还是不能启动太多具有role的节点。因为这样的话就还会面对所有节点被关闭的风险。

Configuration:

akka.cluster.split-brain-resolver.active-strategy=static-quorum
akka.cluster.split-brain-resolver.static-quorum {
// minimum number of nodes that the cluster must have
quorum-size = undefined

// if the 'role' is defined the decision is based only on members with that 'role'
role = ""
}


Keep Majority

基于集群最后得知的成员信息,该策略会关闭数量较少的一部分。 如果两部分分区数量相同,那么将保持地址较低的节点。
如果集群的节点是动态变化的,那么该策略会是一个不错的选择。因此也不能使用static-quorum策略.
但是这个策略也有个小问题,如果两个分区基于不同的信息,那么就会导致两个分区决策不一致。也就是说当网络分区的同时发生了成员资格的变更,就会出现这种情况。例如:有两个节点在分区的一侧,状态变为“UP”,但是另一侧并不知道,这样会导致两个分区都认为自己是最多数,从而形成两个集群。同样在网络分区之后分区决策之前有一些节点宕机,也会导致这种情况出现。
在这方面,使用static-quorum会更安全,但这种策略的动态性优势可能比风险更重要。

1、如果出现不止两个分区,且其中没有一个分区是最多的(应该是多余总数的一半,因为该分区不知道另外的分区是多个还是一个),则整个集群都会被终止。
2、如果超过一半以上的节点同时宕机,那么其他正在运行的节点将会自动关闭,因为他们认为自己不是大多数,从而导致整个集群终止。

这个策略可以基于配置了role的节点,而不是所有节点,当某些类型的节点比其他节点更有价值时,这会很有用。例如,您可能有一些负责持久数据的节点和一些具有无状态工作服务的节点。 那么尽可能多地保留持久数据节点可能更重要,即使这意味着要关闭更多的工作节点。
Configuration:
akka.cluster.split-brain-resolver.active-strategy=keep-majority

akka.cluster.split-brain-resolver.keep-majority {
// if the 'role' is defined the decision is based only on members with that 'role'
role = ""
}


Keep Oldest

名为“ keep-oldest ”的策略将停止不包含最旧成员的部分。 有趣的是Cluster Singleton instance运行在最老的成员上。

有一个例外:如果配置的down-if-alone 为on ,然后最好的员与其他节点网络分区,那么这个最老的节点会被终止,其他所有节点保持运行。但是如果集群只有这一节点,改策略不会生效。

请注意,down-if-alone 为on时 , 如果最老的节点崩溃,其他节点将会将其从群集中删除,否则 会关闭整个群集以及最早的节点。

当你使用Cluster Singleton并且不想让运行该singleton instance的节点关掉,这是个不错的策略。如果最老的节点宕机,那么一个新的singleton instance将会在下一个最老的节点上被启动。缺点是在集群分区是,该策略有可能只保存较少部分节点。 例如,如果一个最旧的部分由2个节点组成,另一个部分由98个节点组成,那么它将保留2个节点并关闭98个节点。

这个策略有一个风险。如果分区的两边关于那个节点最老有不同的意见,可能两边都关闭,或者两边都运行。
两边都关闭的场合:集群分区后但是在决策之前,最老的节点宕机。
两边都运行的场景:集群分区之前,最老的节点宕机,但是信息只传播到一半的时候发生了集群分区。

同样这个策略可以基于配置了role的节点,而不是所有节点。
Configuration:

akka.cluster.split-brain-resolver.active-strategy=keep-oldest
akka.cluster.split-brain-resolver.keep-oldest {
// Enable downing of the oldest node when it is partitioned from all other nodes
down-if-alone = on

// if the 'role' is defined the decision is based only on members with that 'role',
// i.e. using the oldest member (singleton) within the nodes with that role
role = ""
}


Keep Referee

名为keep-referee的策略会放弃不包含给定裁判节点的部分。
如果剩余节点数量少于配置 down-all-if-less-than-nodes,那么剩余节点也会被关闭。如果裁判节点被删除,那么所有节点也会被关掉。
如果您有一个承载一些关键资源的节点,并且没有他系统无法运行,那么此策略很有用。有一个缺点就是这个裁判节点只有一个。也不会造成两个独立的集群。
Configuration:
akka.cluster.split-brain-resolver.active-strategy=keep-referee
akka.cluster.split-brain-resolver.keep-referee {
// referee address on the form of "akka.tcp://system@hostname:port"
address = ""
down-all-if-less-than-nodes = 1
}


Multiple data centers
Akka Cluster支持多数据中心,每个数据中心相互独立管理其分区的成员信息。The Split Brain Resolver包含了该策略,也就是说其他分区的成员变更不会对该分区产生影响。

当数据中心之间进行网络分区时,典型的解决方案就是:等(do nothing)。其他决定应由外部监督工具或人员操作执行。


Cluster Singleton and Cluster Sharding
Cluster Singleton和Cluster Sharding的目的是在任何时间点至多运行一个给定actor的一个实例。当这种实例关闭时,应该在集群中的其他地方启动一个新实例。在旧实例停止之前,新实例未启动很重要。 当单例或分片实例是持久化实例的时候,这是特别重要的,因为一个持久化的actor实例的日志事件只能被一个actor写。

由于分区的两侧不能通信,所以分区两侧做决策的时间也会稍有不同,因此必须有基于时间的余量,以确保新实例在旧的停止前未启动。
此持续时间有如下配置:
// Time margin after which shards or singletons that belonged to a downed/removed
// partition are created in surviving partition. The purpose of this margin is that
// in case of a network partition the persistent actors in the non-surviving partitions
// must be stopped before corresponding persistent actors are started somewhere else.
// This is useful if you implement downing strategies that handle network partitions,
// e.g. by keeping the larger side of the partition and shutting down the smaller side.
// Decision is taken by the strategy when there has been no membership or
// reachability changes for this duration, i.e. the cluster state is stable.
stable-after = 20s

您希望将此配置设置为短时间以实现快速故障切换,但这会增加多个单例/分片实例同时运行的风险,并且可能需要不同的时间来处理该决策
建议将其配置为与stable-after属性相同的值。 不同簇大小的建议最小持续时间:
cluster size down-removal-margin
5 7 s
10 10 s
20 13 s
50 17 s

Expected Failover Time
如您所见,有几个配置的超时会增加总故障切换延迟。 使用默认配置的是:
failure detection 5 seconds(故障检测5秒)
stable-after 20 seconds
down-removal-margin 20 seconds
总而言之,通过默认配置,您可以预计单例或分片实例的故障转移时间大约为45秒。默认配置的大小为100个节点的群集。 如果您有大约10个节点,则可以将stable-after 和down-removal-margin设置到10秒左右,从而可以实现预计约25秒的故障切换时间。

商用产品需要创建凭证,详细信息参考:
https://www.lightbend.com/product/lightbend-reactive-platform/credentials
翻译自:
https://developer.lightbend.com/docs/akka-commercial-addons/current/split-brain-resolver.html

你可能感兴趣的:(Split Brain Resolver)