Member management in Akka Cluster centers around the Member class which represents cluster nodes and their current state. Members progress through well-defined lifecycle stages and can be organized by roles and data centers.
class Member private[cluster] (
val uniqueAddress: UniqueAddress,
private[cluster] val upNumber: Int,
val status: MemberStatus,
val roles: Set[String],
val appVersion: Version
) extends Serializable {
def address: Address
def dataCenter: DataCenter
def hasRole(role: String): Boolean
def getRoles: java.util.Set[String]
def isOlderThan(other: Member): Boolean
def copy(status: MemberStatus): Member
def copyUp(upNumber: Int): Member
// Standard methods
override def hashCode: Int
override def equals(other: Any): Boolean
override def toString: String
}class UniqueAddress(val address: Address, val longUid: Long)
extends Product with Serializable with Ordered[UniqueAddress] {
def address: Address
def longUid: Long
@deprecated("Use longUid instead", since = "2.4.11")
def uid: Int = longUid.toInt
def compare(that: UniqueAddress): Int
override def productArity: Int = 2
override def productElement(n: Int): Any
override def canEqual(that: Any): Boolean
override def hashCode: Int
override def equals(other: Any): Boolean
override def toString: String
}val member: Member = cluster.selfMember
// Core identification
val address: Address = member.address // Network address
val uniqueAddress: UniqueAddress = member.uniqueAddress // Address + unique ID
val uid: Long = member.uniqueAddress.uid // Unique identifier
// Member metadata
val status: MemberStatus = member.status // Current lifecycle status
val roles: Set[String] = member.roles // Assigned roles
val dataCenter: String = member.dataCenter // Data center assignment
val appVersion: Version = member.appVersion // Application version
println(s"Member: ${address}, Status: ${status}, DC: ${dataCenter}")sealed abstract class MemberStatus
case object Joining extends MemberStatus
case object WeaklyUp extends MemberStatus
case object Up extends MemberStatus
case object Leaving extends MemberStatus
case object Exiting extends MemberStatus
case object Down extends MemberStatus
case object Removed extends MemberStatus
case object PreparingForShutdown extends MemberStatus
case object ReadyForShutdown extends MemberStatus
object MemberStatus {
// Java API accessors
def joining(): MemberStatus = Joining
def weaklyUp(): MemberStatus = WeaklyUp
def up(): MemberStatus = Up
def leaving(): MemberStatus = Leaving
def exiting(): MemberStatus = Exiting
def down(): MemberStatus = Down
def removed(): MemberStatus = Removed
def shuttingDown(): MemberStatus = PreparingForShutdown // Java name mapping
def shutDown(): MemberStatus = ReadyForShutdown // Java name mapping
}Normal lifecycle progression:
Joining → WeaklyUp → Up → Leaving → Exiting → RemovedFailure scenarios:
Any Status → Down → RemovedCoordinated shutdown:
Up → PreparingForShutdown → ReadyForShutdown → Exiting → RemovedComplete allowed transitions:
From PreparingForShutdown: ReadyForShutdown, Removed, Leaving, Down
From ReadyForShutdown: Removed, Leaving, DownRoles are configured during system startup and cannot be changed at runtime:
akka.cluster.roles = ["frontend", "backend", "database"]val member: Member = cluster.selfMember
// Check for specific role
if (member.hasRole("backend")) {
// Backend-specific logic
startBackendServices()
}
// Check multiple roles
val isFrontend = member.hasRole("frontend")
val isDatabase = member.hasRole("database")
// Get all roles
val allRoles: Set[String] = member.roles
println(s"Node roles: ${allRoles.mkString(", ")}")
// Java API
val rolesJava: java.util.Set[String] = member.getRolesval currentState = cluster.state
// Find all backend members
val backendMembers = currentState.members.filter(_.hasRole("backend"))
// Find available (Up or WeaklyUp) frontend members
val availableFrontends = currentState.members
.filter(m => m.hasRole("frontend") &&
(m.status == MemberStatus.Up || m.status == MemberStatus.WeaklyUp))
// Count members by role
val membersByRole = currentState.members.groupBy(_.roles).view.mapValues(_.size)
println(s"Members by role: $membersByRole")// Compare member age (same data center only)
def isOlderThan(other: Member): BooleanUsage:
val member1: Member = // ...
val member2: Member = // ...
try {
if (member1.isOlderThan(member2)) {
println(s"${member1.address} is older than ${member2.address}")
}
} catch {
case _: IllegalArgumentException =>
println("Cannot compare members from different data centers")
}object Member {
val ordering: Ordering[Member]
val ageOrdering: Ordering[Member]
val addressOrdering: Ordering[Address]
val none: Set[Member]
// Utility methods
def highestPriorityOf(m1: Member, m2: Member): Member
}Usage:
val members: Set[Member] = cluster.state.members.toSet
// Default ordering (by address)
val sortedMembers = members.toSeq.sorted(Member.ordering)
// Age-based ordering (oldest first)
val membersByAge = members.toSeq.sorted(Member.ageOrdering)
// Address-based ordering
val addresses = members.map(_.address).toSeq.sorted(Member.addressOrdering)Data centers are assigned via special roles with the dc- prefix:
akka.cluster {
roles = ["dc-east", "backend"]
multi-data-center.self-data-center = "east"
}val member: Member = cluster.selfMember
// Get data center
val dataCenter: String = member.dataCenter
println(s"Member data center: $dataCenter")
// Filter by data center
val currentState = cluster.state
val localMembers = currentState.members.filter(_.dataCenter == cluster.selfDataCenter)
val remoteMembers = currentState.members.filter(_.dataCenter != cluster.selfDataCenter)
// Group by data center
val membersByDC = currentState.members.groupBy(_.dataCenter)
membersByDC.foreach { case (dc, members) =>
println(s"Data center '$dc': ${members.size} members")
}def copy(status: MemberStatus): Member
def copyUp(upNumber: Int): MemberUsage (typically internal to Akka):
val member: Member = // existing member
val updatedMember = member.copy(MemberStatus.Up)
val upMember = member.copyUp(upNumber = 42)Only specific status transitions are allowed:
Joining to WeaklyUp or UpWeaklyUp to UpUp to Leaving or Down or PreparingForShutdownLeaving to Exiting or DownExiting to RemovedDown to RemovedPreparingForShutdown to ReadyForShutdown or DownReadyForShutdown to Exiting or DownMembers carry application version information for compatibility checking:
val member: Member = cluster.selfMember
val version: Version = member.appVersion
println(s"App version: ${version.version}")
// Version comparison (if needed)
val otherVersion = Version("1.2.0")
if (version >= otherVersion) {
// Compatible version
}import scala.concurrent.Future
import akka.util.Version
// Set version before joining
val versionFuture: Future[Version] = loadVersionFromExternalSystem()
cluster.setAppVersionLater(versionFuture)
cluster.joinSeedNodes(seedNodes)val cluster = Cluster(system)
// Current node as member
val selfMember: Member = cluster.selfMember
val selfAddress: Address = cluster.selfAddress
val selfUniqueAddress: UniqueAddress = cluster.selfUniqueAddress
val selfRoles: Set[String] = cluster.selfRoles
val selfDataCenter: String = cluster.selfDataCenter
// Java API for roles
val selfRolesJava: java.util.Set[String] = cluster.getSelfRolesval state: CurrentClusterState = cluster.state
// All members
val allMembers: immutable.SortedSet[Member] = state.members
// Filter by status
val upMembers = allMembers.filter(_.status == MemberStatus.Up)
val joiningMembers = allMembers.filter(_.status == MemberStatus.Joining)
// Find specific member
val memberOpt = allMembers.find(_.address == targetAddress)
// Unreachable members
val unreachableMembers: Set[Member] = state.unreachableimport akka.actor.{Actor, ActorLogging}
import akka.cluster.{Cluster, Member, MemberStatus}
import akka.cluster.ClusterEvent._
import scala.collection.mutable
class MemberTracker extends Actor with ActorLogging {
val cluster = Cluster(context.system)
val members = mutable.Map[Address, Member]()
override def preStart(): Unit = {
cluster.subscribe(self, classOf[MemberEvent])
}
override def postStop(): Unit = {
cluster.unsubscribe(self)
}
def receive = {
case state: CurrentClusterState =>
members.clear()
state.members.foreach { member =>
members(member.address) = member
logMemberInfo(member, "Initial")
}
case MemberJoined(member) =>
members(member.address) = member
logMemberInfo(member, "Joined")
case MemberUp(member) =>
members(member.address) = member
logMemberInfo(member, "Up")
case MemberLeft(member) =>
members(member.address) = member
logMemberInfo(member, "Left")
case MemberRemoved(member, previousStatus) =>
members.remove(member.address)
log.info("Member removed: {} (was: {})", member.address, previousStatus)
case event: MemberEvent =>
members(event.member.address) = event.member
logMemberInfo(event.member, event.getClass.getSimpleName)
}
def logMemberInfo(member: Member, event: String): Unit = {
log.info("{}: {} - Status: {}, Roles: {}, DC: {}, Version: {}",
event, member.address, member.status,
member.roles.mkString(","), member.dataCenter, member.appVersion)
}
}