Improve publish of domain events, see #2202

* Gossip is not exposed in user api
* Better and more events
* Snapshot event sent to new subscriber
* Updated tests
* Periodic publish only for internal stats
This commit is contained in:
Patrik Nordwall 2012-08-15 16:47:34 +02:00
parent bc4d8fc7c5
commit 06f81f4373
21 changed files with 294 additions and 197 deletions

View file

@ -14,6 +14,7 @@ import akka.util.Timeout
import akka.pattern.{ AskTimeoutException, ask, pipe }
import akka.cluster.MemberStatus._
import akka.cluster.ClusterEvent._
import language.existentials
/**
* Base trait for all cluster messages. All ClusterMessage's are serializable.
@ -82,7 +83,7 @@ private[cluster] object InternalClusterAction {
case object LeaderActionsTick
case object PublishStateTick
case object PublishStatsTick
case class SendClusterMessage(to: Address, msg: ClusterMessage)
@ -90,6 +91,9 @@ private[cluster] object InternalClusterAction {
case object GetClusterCoreRef
case class Subscribe(subscriber: ActorRef, to: Class[_])
case class Unsubscribe(subscriber: ActorRef)
case class Ping(timestamp: Long = System.currentTimeMillis) extends ClusterMessage
case class Pong(ping: Ping, timestamp: Long = System.currentTimeMillis) extends ClusterMessage
@ -205,9 +209,9 @@ private[cluster] final class ClusterCoreDaemon(environment: ClusterEnvironment)
// start periodic publish of current state
private val publishStateTask: Option[Cancellable] =
if (PublishStateInterval == Duration.Zero) None
else Some(FixedRateTask(clusterScheduler, PeriodicTasksInitialDelay.max(PublishStateInterval), PublishStateInterval) {
self ! PublishStateTick
if (PublishStatsInterval == Duration.Zero) None
else Some(FixedRateTask(clusterScheduler, PeriodicTasksInitialDelay.max(PublishStatsInterval), PublishStatsInterval) {
self ! PublishStatsTick
})
override def preStart(): Unit = {
@ -229,7 +233,7 @@ private[cluster] final class ClusterCoreDaemon(environment: ClusterEnvironment)
case HeartbeatTick heartbeat()
case ReapUnreachableTick reapUnreachableMembers()
case LeaderActionsTick leaderActions()
case PublishStateTick publishState()
case PublishStatsTick publishInternalStats()
case JoinSeedNode joinSeedNode()
case InitJoin initJoin()
case InitJoinAck(address) join(address)
@ -241,6 +245,8 @@ private[cluster] final class ClusterCoreDaemon(environment: ClusterEnvironment)
case Exit(address) exiting(address)
case Remove(address) removing(address)
case SendGossipTo(address) gossipTo(address)
case Subscribe(subscriber, to) subscribe(subscriber, to)
case Unsubscribe(subscriber) unsubscribe(subscriber)
case p: Ping ping(p)
}
@ -802,23 +808,63 @@ private[cluster] final class ClusterCoreDaemon(environment: ClusterEnvironment)
def gossipTo(address: Address, gossipMsg: GossipEnvelope): Unit = if (address != selfAddress)
coreSender ! SendClusterMessage(address, gossipMsg)
def subscribe(subscriber: ActorRef, to: Class[_]): Unit = {
subscriber ! CurrentClusterState(
members = latestGossip.members,
unreachable = latestGossip.overview.unreachable,
convergence = latestGossip.convergence,
seenBy = latestGossip.seenBy,
leader = latestGossip.leader)
eventStream.subscribe(subscriber, to)
}
def unsubscribe(subscriber: ActorRef): Unit =
eventStream.unsubscribe(subscriber)
def publish(oldGossip: Gossip): Unit = {
if (PublishStateInterval == Duration.Zero) publishState()
publishMembers(oldGossip.members)
publishMembers(oldGossip)
publishUnreachableMembers(oldGossip)
publishLeader(oldGossip)
publishSeen(oldGossip)
if (PublishStatsInterval == Duration.Zero) publishInternalStats()
}
def publishState(): Unit = {
eventStream publish MembershipGossipChanged(latestGossip)
eventStream publish InternalStatsChanged(stats)
}
def publishMembers(oldMembers: SortedSet[Member]): Unit = {
val oldMembersStatus = oldMembers.map(m (m.address, m.status))
val newMembersStatus = latestGossip.members.map(m (m.address, m.status))
if (newMembersStatus != oldMembersStatus)
def publishMembers(oldGossip: Gossip): Unit = {
if (!isSame(oldGossip.members, latestGossip.members))
eventStream publish MembersChanged(latestGossip.members)
}
def publishUnreachableMembers(oldGossip: Gossip): Unit = {
if (!isSame(oldGossip.overview.unreachable, latestGossip.overview.unreachable))
eventStream publish UnreachableMembersChanged(latestGossip.overview.unreachable)
}
def isSame(oldMembers: Set[Member], newMembers: Set[Member]): Boolean = {
def oldMembersStatus = oldMembers.map(m (m.address, m.status))
def newMembersStatus = newMembers.map(m (m.address, m.status))
(newMembers eq oldMembers) || ((newMembers.size == oldMembers.size) && (newMembersStatus == oldMembersStatus))
}
def publishLeader(oldGossip: Gossip): Unit = {
if (latestGossip.leader != oldGossip.leader)
eventStream publish LeaderChanged(latestGossip.leader)
}
def publishSeen(oldGossip: Gossip): Unit = {
val oldConvergence = oldGossip.convergence
val newConvergence = latestGossip.convergence
val oldSeenBy = oldGossip.seenBy
val newSeenBy = latestGossip.seenBy
if (newConvergence != oldConvergence || newSeenBy != oldSeenBy) {
eventStream publish SeenChanged(newConvergence, newSeenBy)
}
}
def publishInternalStats(): Unit = {
eventStream publish CurrentInternalStats(stats)
}
def eventStream: EventStream = context.system.eventStream
def ping(p: Ping): Unit = sender ! Pong(p)
@ -843,6 +889,49 @@ private[cluster] final class ClusterCoreSender(selfAddress: Address) extends Act
}
}
/**
* Domain events published to the event bus.
*/
object ClusterEvent {
/**
* Marker interface for cluster domain events.
*/
trait ClusterDomainEvent
/**
* Current snapshot state of the cluster. Sent to new subscriber.
*/
case class CurrentClusterState(
members: SortedSet[Member] = SortedSet.empty,
unreachable: Set[Member] = Set.empty,
convergence: Boolean = false,
seenBy: Set[Address] = Set.empty,
leader: Option[Address] = None) extends ClusterDomainEvent
/**
* Set of cluster members or their status have changed.
*/
case class MembersChanged(members: SortedSet[Member]) extends ClusterDomainEvent
/**
* Set of unreachable cluster members or their status have changed.
*/
case class UnreachableMembersChanged(unreachable: Set[Member]) extends ClusterDomainEvent
/**
* The nodes that have seen current version of the Gossip.
*/
case class SeenChanged(convergence: Boolean, seenBy: Set[Address]) extends ClusterDomainEvent
case class LeaderChanged(leader: Option[Address]) extends ClusterDomainEvent
/**
* INTERNAL API
*/
private[cluster] case class CurrentInternalStats(stats: ClusterStats) extends ClusterDomainEvent
}
/**
* INTERNAL API
*/