pekko/akka-cluster/src/main/scala/akka/cluster/ClusterEvent.scala
Patrik Nordwall 4e8a5d333b
More structured logger names, #27922 (#27934)
* some cluster logging improvements
* most logger names are actually good, when using ActorLogging since
  config can be setup on the package (prefix)
* override logSource when StageLogging is used
* replace system.log with more specific logger
2019-10-14 17:55:12 +02:00

682 lines
24 KiB
Scala

/*
* Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.cluster
import language.postfixOps
import scala.collection.immutable
import scala.collection.immutable.{ SortedSet, VectorBuilder }
import akka.actor.{ Actor, ActorRef, Address }
import akka.cluster.ClusterSettings.DataCenter
import akka.cluster.ClusterEvent._
import akka.cluster.MemberStatus._
import akka.event.EventStream
import akka.dispatch.{ RequiresMessageQueue, UnboundedMessageQueueSemantics }
import akka.actor.DeadLetterSuppression
import akka.annotation.{ DoNotInherit, InternalApi }
import akka.util.ccompat._
import scala.runtime.AbstractFunction5
import com.github.ghik.silencer.silent
/**
* Domain events published to the event bus.
* Subscribe with:
* {{{
* Cluster(system).subscribe(actorRef, classOf[ClusterDomainEvent])
* }}}
*/
object ClusterEvent {
sealed abstract class SubscriptionInitialStateMode
/**
* When using this subscription mode a snapshot of
* [[akka.cluster.ClusterEvent.CurrentClusterState]] will be sent to the
* subscriber as the first message.
*/
case object InitialStateAsSnapshot extends SubscriptionInitialStateMode
/**
* When using this subscription mode the events corresponding
* to the current state will be sent to the subscriber to mimic what you would
* have seen if you were listening to the events when they occurred in the past.
*/
case object InitialStateAsEvents extends SubscriptionInitialStateMode
/**
* Java API
*/
def initialStateAsSnapshot = InitialStateAsSnapshot
/**
* Java API
*/
def initialStateAsEvents = InitialStateAsEvents
/**
* Marker interface for cluster domain events.
*
* Not intended for user extension.
*/
@DoNotInherit
trait ClusterDomainEvent extends DeadLetterSuppression
// for binary compatibility (used to be a case class)
object CurrentClusterState
extends AbstractFunction5[
immutable.SortedSet[Member],
Set[Member],
Set[Address],
Option[Address],
Map[String, Option[Address]],
CurrentClusterState] {
def apply(
members: immutable.SortedSet[Member] = immutable.SortedSet.empty,
unreachable: Set[Member] = Set.empty,
seenBy: Set[Address] = Set.empty,
leader: Option[Address] = None,
roleLeaderMap: Map[String, Option[Address]] = Map.empty): CurrentClusterState =
new CurrentClusterState(members, unreachable, seenBy, leader, roleLeaderMap)
def unapply(cs: CurrentClusterState): Option[
(immutable.SortedSet[Member], Set[Member], Set[Address], Option[Address], Map[String, Option[Address]])] =
Some((cs.members, cs.unreachable, cs.seenBy, cs.leader, cs.roleLeaderMap))
}
/**
* Current snapshot state of the cluster. Sent to new subscriber.
*
* @param leader leader of the data center of this node
*/
@SerialVersionUID(2)
final class CurrentClusterState(
val members: immutable.SortedSet[Member],
val unreachable: Set[Member],
val seenBy: Set[Address],
val leader: Option[Address],
val roleLeaderMap: Map[String, Option[Address]],
val unreachableDataCenters: Set[DataCenter])
extends Product5[
immutable.SortedSet[Member],
Set[Member],
Set[Address],
Option[Address],
Map[String, Option[Address]]]
with Serializable {
// for binary compatibility
def this(
members: immutable.SortedSet[Member] = immutable.SortedSet.empty,
unreachable: Set[Member] = Set.empty,
seenBy: Set[Address] = Set.empty,
leader: Option[Address] = None,
roleLeaderMap: Map[String, Option[Address]] = Map.empty) =
this(members, unreachable, seenBy, leader, roleLeaderMap, Set.empty)
/**
* Java API: get current member list.
*/
def getMembers: java.lang.Iterable[Member] = {
import akka.util.ccompat.JavaConverters._
members.asJava
}
/**
* Java API: get current unreachable set.
*/
@silent("deprecated")
def getUnreachable: java.util.Set[Member] =
scala.collection.JavaConverters.setAsJavaSetConverter(unreachable).asJava
/**
* Java API: All data centers in the cluster
*/
@silent("deprecated")
def getUnreachableDataCenters: java.util.Set[String] =
scala.collection.JavaConverters.setAsJavaSetConverter(unreachableDataCenters).asJava
/**
* Java API: get current “seen-by” set.
*/
@silent("deprecated")
def getSeenBy: java.util.Set[Address] =
scala.collection.JavaConverters.setAsJavaSetConverter(seenBy).asJava
/**
* Java API: get address of current data center leader, or null if none
*/
def getLeader: Address = leader orNull
/**
* get address of current leader, if any, within the data center that has the given role
*/
def roleLeader(role: String): Option[Address] = roleLeaderMap.getOrElse(role, None)
/**
* Java API: get address of current leader, if any, within the data center that has the given role
* or null if no such node exists
*/
def getRoleLeader(role: String): Address = roleLeaderMap.get(role).flatten.orNull
/**
* All node roles in the cluster
*/
def allRoles: Set[String] = roleLeaderMap.keySet
/**
* Java API: All node roles in the cluster
*/
@silent("deprecated")
def getAllRoles: java.util.Set[String] =
scala.collection.JavaConverters.setAsJavaSetConverter(allRoles).asJava
/**
* All data centers in the cluster
*/
def allDataCenters: Set[String] = members.iterator.map(_.dataCenter).to(immutable.Set)
/**
* Java API: All data centers in the cluster
*/
@silent("deprecated")
def getAllDataCenters: java.util.Set[String] =
scala.collection.JavaConverters.setAsJavaSetConverter(allDataCenters).asJava
/**
* Replace the set of unreachable datacenters with the given set
*/
def withUnreachableDataCenters(unreachableDataCenters: Set[DataCenter]): CurrentClusterState =
new CurrentClusterState(members, unreachable, seenBy, leader, roleLeaderMap, unreachableDataCenters)
/**
* INTERNAL API
* Returns true if the address is a cluster member and that member is `MemberStatus.Up`.
*/
@InternalApi
private[akka] def isMemberUp(address: Address): Boolean =
members.exists(m => m.address == address && m.status == MemberStatus.Up)
// for binary compatibility (used to be a case class)
def copy(
members: immutable.SortedSet[Member] = this.members,
unreachable: Set[Member] = this.unreachable,
seenBy: Set[Address] = this.seenBy,
leader: Option[Address] = this.leader,
roleLeaderMap: Map[String, Option[Address]] = this.roleLeaderMap) =
new CurrentClusterState(members, unreachable, seenBy, leader, roleLeaderMap, unreachableDataCenters)
override def equals(other: Any): Boolean = other match {
case that: CurrentClusterState =>
(this eq that) || (members == that.members &&
unreachable == that.unreachable &&
seenBy == that.seenBy &&
leader == that.leader &&
roleLeaderMap == that.roleLeaderMap)
case _ => false
}
override def hashCode(): Int = {
val state = Seq(members, unreachable, seenBy, leader, roleLeaderMap)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
}
// Product5
override def productPrefix = "CurrentClusterState"
def _1: SortedSet[Member] = members
def _2: Set[Member] = unreachable
def _3: Set[Address] = seenBy
def _4: Option[Address] = leader
def _5: Map[String, Option[Address]] = roleLeaderMap
def canEqual(that: Any): Boolean = that.isInstanceOf[CurrentClusterState]
override def toString = s"CurrentClusterState($members, $unreachable, $seenBy, $leader, $roleLeaderMap)"
}
/**
* Marker interface for membership events.
* Published when the state change is first seen on a node.
* The state change was performed by the leader when there was
* convergence on the leader node, i.e. all members had seen previous
* state.
*/
sealed trait MemberEvent extends ClusterDomainEvent {
def member: Member
}
/**
* Member status changed to Joining.
*/
final case class MemberJoined(member: Member) extends MemberEvent {
if (member.status != Joining) throw new IllegalArgumentException("Expected Joining status, got: " + member)
}
/**
* Member status changed to WeaklyUp.
* A joining member can be moved to `WeaklyUp` if convergence
* cannot be reached, i.e. there are unreachable nodes.
* It will be moved to `Up` when convergence is reached.
*/
final case class MemberWeaklyUp(member: Member) extends MemberEvent {
if (member.status != WeaklyUp) throw new IllegalArgumentException("Expected WeaklyUp status, got: " + member)
}
/**
* Member status changed to Up.
*/
final case class MemberUp(member: Member) extends MemberEvent {
if (member.status != Up) throw new IllegalArgumentException("Expected Up status, got: " + member)
}
/**
* Member status changed to Leaving.
*/
final case class MemberLeft(member: Member) extends MemberEvent {
if (member.status != Leaving) throw new IllegalArgumentException("Expected Leaving status, got: " + member)
}
/**
* Member status changed to `MemberStatus.Exiting` and will be removed
* when all members have seen the `Exiting` status.
*/
final case class MemberExited(member: Member) extends MemberEvent {
if (member.status != Exiting) throw new IllegalArgumentException("Expected Exiting status, got: " + member)
}
/**
* Member status changed to `MemberStatus.Down` and will be removed
* when all members have seen the `Down` status.
*/
final case class MemberDowned(member: Member) extends MemberEvent {
if (member.status != Down) throw new IllegalArgumentException("Expected Down status, got: " + member)
}
/**
* Member completely removed from the cluster.
* When `previousStatus` is `MemberStatus.Down` the node was removed
* after being detected as unreachable and downed.
* When `previousStatus` is `MemberStatus.Exiting` the node was removed
* after graceful leaving and exiting.
*/
final case class MemberRemoved(member: Member, previousStatus: MemberStatus) extends MemberEvent {
if (member.status != Removed) throw new IllegalArgumentException("Expected Removed status, got: " + member)
}
/**
* Leader of the cluster data center of this node changed. Published when the state change
* is first seen on a node.
*/
final case class LeaderChanged(leader: Option[Address]) extends ClusterDomainEvent {
/**
* Java API
* @return address of current leader, or null if none
*/
def getLeader: Address = leader orNull
}
/**
* First member (leader) of the members within a role set (in the same data center as this node,
* if data centers are used) changed.
* Published when the state change is first seen on a node.
*/
final case class RoleLeaderChanged(role: String, leader: Option[Address]) extends ClusterDomainEvent {
/**
* Java API
* @return address of current leader, or null if none
*/
def getLeader: Address = leader orNull
}
/**
* This event is published when the cluster node is shutting down,
* before the final [[MemberRemoved]] events are published.
*/
final case object ClusterShuttingDown extends ClusterDomainEvent
/**
* Java API: get the singleton instance of `ClusterShuttingDown` event
*/
def getClusterShuttingDownInstance = ClusterShuttingDown
/**
* Marker interface to facilitate subscription of
* both [[UnreachableMember]] and [[ReachableMember]].
*/
sealed trait ReachabilityEvent extends ClusterDomainEvent {
def member: Member
}
/**
* A member is considered as unreachable by the failure detector.
*/
final case class UnreachableMember(member: Member) extends ReachabilityEvent
/**
* A member is considered as reachable by the failure detector
* after having been unreachable.
* @see [[UnreachableMember]]
*/
final case class ReachableMember(member: Member) extends ReachabilityEvent
/**
* Marker interface to facilitate subscription of
* both [[UnreachableDataCenter]] and [[ReachableDataCenter]].
*/
sealed trait DataCenterReachabilityEvent extends ClusterDomainEvent
/**
* A data center is considered as unreachable when any members from the data center are unreachable
*/
final case class UnreachableDataCenter(dataCenter: DataCenter) extends DataCenterReachabilityEvent
/**
* A data center is considered reachable when all members from the data center are reachable
*/
final case class ReachableDataCenter(dataCenter: DataCenter) extends DataCenterReachabilityEvent
/**
* INTERNAL API
* The nodes that have seen current version of the Gossip.
*/
@ccompatUsedUntil213
private[cluster] final case class SeenChanged(convergence: Boolean, seenBy: Set[Address]) extends ClusterDomainEvent
/**
* INTERNAL API
*/
private[cluster] final case class ReachabilityChanged(reachability: Reachability) extends ClusterDomainEvent
/**
* INTERNAL API
*/
private[cluster] final case class CurrentInternalStats(gossipStats: GossipStats, vclockStats: VectorClockStats)
extends ClusterDomainEvent
/**
* INTERNAL API
*/
private[cluster] def diffUnreachable(
oldState: MembershipState,
newState: MembershipState): immutable.Seq[UnreachableMember] =
if (newState eq oldState) Nil
else {
val newGossip = newState.latestGossip
val oldUnreachableNodes = oldState.dcReachabilityNoOutsideNodes.allUnreachableOrTerminated
newState.dcReachabilityNoOutsideNodes.allUnreachableOrTerminated.iterator
.collect {
case node if !oldUnreachableNodes.contains(node) && node != newState.selfUniqueAddress =>
UnreachableMember(newGossip.member(node))
}
.to(immutable.IndexedSeq)
}
/**
* INTERNAL API
*/
private[cluster] def diffReachable(
oldState: MembershipState,
newState: MembershipState): immutable.Seq[ReachableMember] =
if (newState eq oldState) Nil
else {
val newGossip = newState.latestGossip
oldState.dcReachabilityNoOutsideNodes.allUnreachable.iterator
.collect {
case node
if newGossip.hasMember(node) && newState.dcReachabilityNoOutsideNodes.isReachable(node) && node != newState.selfUniqueAddress =>
ReachableMember(newGossip.member(node))
}
.to(immutable.IndexedSeq)
}
/**
* Internal API
*/
private[cluster] def isDataCenterReachable(state: MembershipState)(otherDc: DataCenter): Boolean = {
val unrelatedDcNodes = state.latestGossip.members.collect {
case m if m.dataCenter != otherDc && m.dataCenter != state.selfDc => m.uniqueAddress
}
val reachabilityForOtherDc = state.dcReachabilityWithoutObservationsWithin.remove(unrelatedDcNodes)
reachabilityForOtherDc.allUnreachable.isEmpty
}
/**
* INTERNAL API
*/
private[cluster] def diffUnreachableDataCenter(
oldState: MembershipState,
newState: MembershipState): immutable.Seq[UnreachableDataCenter] = {
if (newState eq oldState) Nil
else {
val otherDcs = (oldState.latestGossip.allDataCenters
.union(newState.latestGossip.allDataCenters)) - newState.selfDc
val oldUnreachableDcs = otherDcs.filterNot(isDataCenterReachable(oldState))
val currentUnreachableDcs = otherDcs.filterNot(isDataCenterReachable(newState))
currentUnreachableDcs.diff(oldUnreachableDcs).iterator.map(UnreachableDataCenter).to(immutable.IndexedSeq)
}
}
/**
* INTERNAL API
*/
private[cluster] def diffReachableDataCenter(
oldState: MembershipState,
newState: MembershipState): immutable.Seq[ReachableDataCenter] = {
if (newState eq oldState) Nil
else {
val otherDcs = (oldState.latestGossip.allDataCenters
.union(newState.latestGossip.allDataCenters)) - newState.selfDc
val oldUnreachableDcs = otherDcs.filterNot(isDataCenterReachable(oldState))
val currentUnreachableDcs = otherDcs.filterNot(isDataCenterReachable(newState))
oldUnreachableDcs.diff(currentUnreachableDcs).iterator.map(ReachableDataCenter).to(immutable.IndexedSeq)
}
}
/**
* INTERNAL API.
*/
private[cluster] def diffMemberEvents(
oldState: MembershipState,
newState: MembershipState): immutable.Seq[MemberEvent] =
if (newState eq oldState) Nil
else {
val oldGossip = oldState.latestGossip
val newGossip = newState.latestGossip
val newMembers = newGossip.members.diff(oldGossip.members)
val membersGroupedByAddress = List(newGossip.members, oldGossip.members).flatten.groupBy(_.uniqueAddress)
val changedMembers = membersGroupedByAddress.collect {
case (_, newMember :: oldMember :: Nil)
if newMember.status != oldMember.status || newMember.upNumber != oldMember.upNumber =>
newMember
}
val memberEvents = (newMembers ++ changedMembers).unsorted.collect {
case m if m.status == Joining => MemberJoined(m)
case m if m.status == WeaklyUp => MemberWeaklyUp(m)
case m if m.status == Up => MemberUp(m)
case m if m.status == Leaving => MemberLeft(m)
case m if m.status == Exiting => MemberExited(m)
case m if m.status == Down => MemberDowned(m)
// no events for other transitions
}
val removedMembers = oldGossip.members.diff(newGossip.members)
val removedEvents = removedMembers.unsorted.map(m => MemberRemoved(m.copy(status = Removed), m.status))
(new VectorBuilder[MemberEvent]() ++= removedEvents ++= memberEvents).result()
}
/**
* INTERNAL API
*/
@InternalApi
private[cluster] def diffLeader(
oldState: MembershipState,
newState: MembershipState): immutable.Seq[LeaderChanged] = {
val newLeader = newState.leader
if (newLeader != oldState.leader) List(LeaderChanged(newLeader.map(_.address)))
else Nil
}
/**
* INTERNAL API
*/
@InternalApi
private[cluster] def diffRolesLeader(oldState: MembershipState, newState: MembershipState): Set[RoleLeaderChanged] = {
for {
role <- oldState.latestGossip.allRoles.union(newState.latestGossip.allRoles)
newLeader = newState.roleLeader(role)
if newLeader != oldState.roleLeader(role)
} yield RoleLeaderChanged(role, newLeader.map(_.address))
}
/**
* INTERNAL API
*/
@InternalApi
private[cluster] def diffSeen(oldState: MembershipState, newState: MembershipState): immutable.Seq[SeenChanged] =
if (oldState eq newState) Nil
else {
val newConvergence = newState.convergence(Set.empty)
val newSeenBy = newState.latestGossip.seenBy
if (newConvergence != oldState.convergence(Set.empty) || newSeenBy != oldState.latestGossip.seenBy)
List(SeenChanged(newConvergence, newSeenBy.map(_.address)))
else Nil
}
/**
* INTERNAL API
*/
@InternalApi
private[cluster] def diffReachability(
oldState: MembershipState,
newState: MembershipState): immutable.Seq[ReachabilityChanged] =
if (newState.overview.reachability eq oldState.overview.reachability) Nil
else List(ReachabilityChanged(newState.overview.reachability))
}
/**
* INTERNAL API.
* Responsible for domain event subscriptions and publishing of
* domain events to event bus.
*/
private[cluster] final class ClusterDomainEventPublisher
extends Actor
with RequiresMessageQueue[UnboundedMessageQueueSemantics] {
import InternalClusterAction._
val cluster = Cluster(context.system)
val selfUniqueAddress = cluster.selfUniqueAddress
val emptyMembershipState = MembershipState(
Gossip.empty,
cluster.selfUniqueAddress,
cluster.settings.SelfDataCenter,
cluster.settings.MultiDataCenter.CrossDcConnections)
var membershipState: MembershipState = emptyMembershipState
def selfDc = cluster.settings.SelfDataCenter
override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
// don't postStop when restarted, no children to stop
}
override def postStop(): Unit = {
// publish the final removed state before shutting down
publish(ClusterShuttingDown)
publishChanges(emptyMembershipState)
}
def receive = {
case PublishChanges(newState) => publishChanges(newState)
case currentStats: CurrentInternalStats => publishInternalStats(currentStats)
case SendCurrentClusterState(receiver) => sendCurrentClusterState(receiver)
case Subscribe(subscriber, initMode, to) => subscribe(subscriber, initMode, to)
case Unsubscribe(subscriber, to) => unsubscribe(subscriber, to)
case PublishEvent(event) => publish(event)
}
def eventStream: EventStream = context.system.eventStream
/**
* The current snapshot state corresponding to latest gossip
* to mimic what you would have seen if you were listening to the events.
*/
def sendCurrentClusterState(receiver: ActorRef): Unit = {
val unreachable: Set[Member] =
membershipState.dcReachabilityNoOutsideNodes.allUnreachableOrTerminated.collect {
case node if node != selfUniqueAddress => membershipState.latestGossip.member(node)
}
val unreachableDataCenters: Set[DataCenter] =
if (!membershipState.latestGossip.isMultiDc) Set.empty
else membershipState.latestGossip.allDataCenters.filterNot(isDataCenterReachable(membershipState))
val state = new CurrentClusterState(
members = membershipState.latestGossip.members,
unreachable = unreachable,
seenBy = membershipState.latestGossip.seenBy.map(_.address),
leader = membershipState.leader.map(_.address),
roleLeaderMap = membershipState.latestGossip.allRoles.iterator
.map(r => r -> membershipState.roleLeader(r).map(_.address))
.toMap,
unreachableDataCenters)
receiver ! state
}
def subscribe(subscriber: ActorRef, initMode: SubscriptionInitialStateMode, to: Set[Class[_]]): Unit = {
initMode match {
case InitialStateAsEvents =>
def pub(event: AnyRef): Unit = {
if (to.exists(_.isAssignableFrom(event.getClass)))
subscriber ! event
}
publishDiff(emptyMembershipState, membershipState, pub)
case InitialStateAsSnapshot =>
sendCurrentClusterState(subscriber)
}
to.foreach { eventStream.subscribe(subscriber, _) }
}
def unsubscribe(subscriber: ActorRef, to: Option[Class[_]]): Unit = to match {
case None => eventStream.unsubscribe(subscriber)
case Some(c) => eventStream.unsubscribe(subscriber, c)
}
def publishChanges(newState: MembershipState): Unit = {
val oldState = membershipState
// keep the latest state to be sent to new subscribers
membershipState = newState
publishDiff(oldState, newState, publish)
}
def publishDiff(oldState: MembershipState, newState: MembershipState, pub: AnyRef => Unit): Unit = {
diffMemberEvents(oldState, newState).foreach(pub)
diffUnreachable(oldState, newState).foreach(pub)
diffReachable(oldState, newState).foreach(pub)
diffUnreachableDataCenter(oldState, newState).foreach(pub)
diffReachableDataCenter(oldState, newState).foreach(pub)
diffLeader(oldState, newState).foreach(pub)
diffRolesLeader(oldState, newState).foreach(pub)
// publish internal SeenState for testing purposes
diffSeen(oldState, newState).foreach(pub)
diffReachability(oldState, newState).foreach(pub)
}
def publishInternalStats(currentStats: CurrentInternalStats): Unit = publish(currentStats)
def publish(event: AnyRef): Unit = eventStream.publish(event)
def clearState(): Unit = {
membershipState = emptyMembershipState
}
}