-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2243 Second pass at topology API with topology-aware routers
* Includes cleanup of API for data center failover (as well as instance but may remove this)
- Loading branch information
Helena Edelson
committed
Sep 7, 2013
1 parent
beba5d9
commit 45956d1
Showing
8 changed files
with
409 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
akka-cluster/src/main/scala/akka/cluster/ClusterNetworkTopology.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com> | ||
*/ | ||
|
||
package akka.cluster | ||
|
||
import java.lang.System.{ currentTimeMillis ⇒ newTimestamp } | ||
|
||
import language.postfixOps | ||
import scala.util.Try | ||
import scala.collection.immutable | ||
import scala.collection.JavaConverters._ | ||
import akka.actor._ | ||
import com.typesafe.config._ | ||
|
||
/** | ||
* INTERNAL API. | ||
*/ | ||
private[cluster] class ClusterNetworkTopology(publisher: ActorRef) extends Actor with ActorLogging { | ||
|
||
import akka.cluster.ClusterNetworkTopology._ | ||
import ClusterEvent._ | ||
|
||
val cluster = Cluster(context.system) | ||
|
||
import cluster.InfoLogger._ | ||
|
||
private val config = cluster.settings.TopologyAvailabilityZones | ||
|
||
val topology = Topology(config) // var when we can detect changes, i.e. responding to network changes: add gossip | ||
|
||
override def preStart(): Unit = { | ||
cluster.subscribe(self, classOf[MemberEvent]) | ||
logInfo("NetworkTopology has started successfully with {} regions and {} valid availability dataCenters", topology.regions.size, topology.dataCenters.size) | ||
} | ||
|
||
override def postStop(): Unit = cluster unsubscribe self | ||
|
||
def receive = { | ||
case state: CurrentClusterState ⇒ receiveState(state) | ||
case _: MemberEvent ⇒ | ||
} | ||
|
||
def receiveState(state: CurrentClusterState): Unit = { | ||
// TODO | ||
} | ||
} | ||
|
||
/** | ||
* Description intentionally leaves out the notion of 'Region' in the network hierarchy. | ||
*/ | ||
private[cluster] object ClusterNetworkTopology { | ||
|
||
@SerialVersionUID(1L) | ||
trait Graph | ||
case class Nearest(edge: Int) extends Graph | ||
case class Instance(host: String) extends Graph | ||
|
||
/** | ||
* Defines a data center within a Region. | ||
* | ||
* @param name the data center name | ||
* | ||
* @param instances the instances within the data center - TODO this would vary over time | ||
* | ||
* @param edges the nearest neighbors | ||
* | ||
* @param timestamp TODO UTC | ||
* | ||
*/ | ||
case class DataCenter(id: Int, name: String, edges: immutable.Set[Nearest], instances: immutable.Set[Instance], timestamp: Long) extends Graph | ||
|
||
object DataCenter { | ||
def apply(id: Int, name: String, edges: immutable.Set[Nearest], instances: immutable.Set[Instance]): DataCenter = | ||
DataCenter(id, name, edges, instances, newTimestamp) | ||
} | ||
|
||
/** | ||
* Light weight partition in the topology describing clusters of data centers. | ||
*/ | ||
case class Region(name: String, dataCenters: immutable.Set[DataCenter] = Set.empty) | ||
|
||
case class Topology(regions: immutable.Set[Region]) extends Graph { | ||
|
||
def dataCenters: Set[DataCenter] = regions flatMap (_.dataCenters) | ||
} | ||
|
||
object Topology { | ||
|
||
def apply(config: Config): Topology = { | ||
val regions = config.root.asScala.flatMap { | ||
case (key, value: ConfigObject) ⇒ parseConfig(key, value.toConfig) | ||
case _ ⇒ None | ||
}.toSet | ||
|
||
Topology(regions) | ||
} | ||
|
||
/** | ||
* Partitions proximal dataCenters per region. | ||
*/ | ||
def parseConfig(region: String, config: Config): Option[Region] = { | ||
val dataCenters = config.root.asScala.flatMap { | ||
case (dataCenterName, deployed: ConfigObject) ⇒ Try { | ||
val dc = deployed.toConfig | ||
val id = dc.getInt("zone-id") | ||
val edges = dc.getIntList("proximal-to").asScala.map(Nearest(_)).toSet | ||
val instances = dc.getStringList("instances").asScala.collect { case host if host.nonEmpty ⇒ Instance(host) }.toSet | ||
DataCenter(id, dataCenterName, edges, instances) | ||
}.toOption filter (_.instances.nonEmpty) | ||
case _ ⇒ None | ||
} | ||
if (dataCenters.nonEmpty) Some(Region(region, dataCenters.toSet)) else None | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
akka-cluster/src/main/scala/akka/cluster/routing/TopologyAwareRouter.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com> | ||
*/ | ||
package akka.cluster.routing | ||
|
||
import scala.collection.immutable | ||
import scala.concurrent.forkjoin.ThreadLocalRandom | ||
import akka.actor._ | ||
import akka.cluster.Cluster | ||
import akka.event.Logging | ||
import akka.routing._ | ||
import akka.routing.{ Broadcast, Destination } | ||
import akka.cluster.ClusterEvent.{ ClusterTopologyChanged, CurrentClusterState } | ||
import akka.dispatch.Dispatchers | ||
import akka.cluster.ClusterNetworkTopology._ | ||
|
||
object TopologyAwareRouter { | ||
private val escalateStrategy: SupervisorStrategy = OneForOneStrategy() { | ||
case _ ⇒ SupervisorStrategy.Escalate | ||
} | ||
} | ||
|
||
@SerialVersionUID(1L) | ||
case class TopologyAwareRouter( | ||
selector: ProximalSelector = NearestNeighborSelector, | ||
nrOfInstances: Int = 0, routees: immutable.Iterable[String] = Nil, | ||
override val resizer: Option[Resizer] = None, | ||
val routerDispatcher: String = Dispatchers.DefaultDispatcherId, | ||
val supervisorStrategy: SupervisorStrategy = TopologyAwareRouter.escalateStrategy) | ||
extends RouterConfig with TopologyAwareRouterLike with OverrideUnsetConfig[TopologyAwareRouter] { | ||
|
||
override def withFallback(other: RouterConfig): RouterConfig = other match { | ||
case _: FromConfig | _: NoRouter | _: TopologyAwareRouter ⇒ this.overrideUnsetConfig(other) | ||
case _ ⇒ throw new IllegalArgumentException(s"Expected TopologyAwareRouter but found ${other}") | ||
} | ||
|
||
/** | ||
* Java API for setting the supervisor strategy to be used for the “head” | ||
* Router actor. | ||
*/ | ||
def withSupervisorStrategy(strategy: SupervisorStrategy): TopologyAwareRouter = | ||
copy(supervisorStrategy = strategy) | ||
/** | ||
* Java API for setting the resizer to be used. | ||
*/ | ||
def withResizer(resizer: Resizer): TopologyAwareRouter = copy(resizer = Some(resizer)) | ||
|
||
} | ||
|
||
trait TopologyAwareRouterLike { this: RouterConfig ⇒ | ||
|
||
/** | ||
* Proximity is calculated by the selector via the `topologyListener`. | ||
*/ | ||
def selector: ProximalSelector | ||
|
||
def nrOfInstances: Int | ||
|
||
def routees: immutable.Iterable[String] | ||
|
||
def routerDispatcher: String | ||
|
||
override def createRoute(routeeProvider: RouteeProvider): Route = { | ||
if (resizer.isEmpty) { | ||
if (routees.isEmpty) routeeProvider.createRoutees(nrOfInstances) | ||
else routeeProvider.registerRouteesFor(routees) | ||
} | ||
|
||
val log = Logging(routeeProvider.context.system, routeeProvider.context.self) | ||
|
||
/** | ||
* The current proximal routees, if any. | ||
*/ | ||
@volatile var proximalRoutees: Option[ProximalRoutees] = None | ||
|
||
/** | ||
* While proximity is only updated by the topologyListener, access occurs from the threads of the senders. | ||
*/ | ||
val topologyListener = routeeProvider.context.actorOf(Props(new Actor { | ||
|
||
val cluster = Cluster(context.system) | ||
|
||
override def preStart(): Unit = cluster.subscribe(self, classOf[ClusterTopologyChanged]) | ||
|
||
override def postStop(): Unit = cluster.unsubscribe(self) | ||
|
||
def receive = { | ||
case ClusterTopologyChanged(topology) ⇒ receiveTopology(topology) | ||
case _: CurrentClusterState ⇒ // ignore | ||
} | ||
|
||
def receiveTopology(topology: Topology): Unit = | ||
proximalRoutees = Some(new ProximalRoutees(cluster.selfAddress, routeeProvider.routees, selector.proximity(topology))) | ||
|
||
}).withDispatcher(routerDispatcher).withDeploy(Deploy.local), "topologyListener") | ||
|
||
def getNearest: ActorRef = proximalRoutees match { | ||
case Some(nearest) if nearest.isEmpty ⇒ | ||
if (nearest.isEmpty) routeeProvider.context.system.deadLetters | ||
else nearest(ThreadLocalRandom.current.nextInt(nearest.total) + 1) | ||
case _ ⇒ | ||
val currentRoutees = routeeProvider.routees | ||
if (currentRoutees.isEmpty) routeeProvider.context.system.deadLetters | ||
else currentRoutees(ThreadLocalRandom.current.nextInt(currentRoutees.size)) | ||
} | ||
|
||
{ | ||
case (sender, message) ⇒ | ||
message match { | ||
case Broadcast(msg) ⇒ toAll(sender, routeeProvider.routees) | ||
case msg ⇒ List(Destination(sender, getNearest)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
trait ProximalSelector extends Serializable { | ||
|
||
/** | ||
* The proximity per address by zone, based on the network topology. | ||
*/ | ||
def proximity(topology: Topology): Map[Address, Int] | ||
} | ||
|
||
@SerialVersionUID(1L) | ||
case object NearestNeighborSelector extends ProximalSelector { | ||
|
||
def proximity(topology: Topology): Map[Address, Int] = { | ||
// TODO update nearest | ||
Map.empty | ||
} | ||
} | ||
|
||
/** | ||
* INTERNAL API | ||
* | ||
* Select a routee based on its proximity (nearest neighbors) in the network topology. Lower proximity, lower latency. | ||
*/ | ||
private[cluster] class ProximalRoutees(selfAddress: Address, refs: immutable.IndexedSeq[ActorRef], proximities: Map[Address, Int]) { | ||
|
||
def isEmpty: Boolean = proximities.isEmpty | ||
|
||
def total: Int = 1 // TODO | ||
|
||
/** | ||
* Pick the appropriate routee. | ||
*/ | ||
def apply(value: Int): ActorRef = refs.head // TODO | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
45956d1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See 53d467e as well: minor change
45956d1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.