All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.finagle.loadbalancer.RoundRobinBalancer.scala Maven / Gradle / Ivy

There is a newer version: 7.0.0
Show newest version
package com.twitter.finagle.loadbalancer

import com.twitter.finagle.service.FailingFactory
import com.twitter.finagle.stats.{Counter, StatsReceiver}
import com.twitter.finagle.{ClientConnection, NoBrokersAvailableException, Service, ServiceFactory, ServiceFactoryProxy, Status}
import com.twitter.util.{Activity, Future, Promise, Time}
import java.util.concurrent.atomic.AtomicLong

/**
 * A simple round robin balancer that chooses the next backend in
 * the list for each request.
 */
class RoundRobinBalancer[Req, Rep](
    val activity: Activity[Traversable[ServiceFactory[Req, Rep]]],
    val statsReceiver: StatsReceiver,
    val emptyException: NoBrokersAvailableException,
    val maxEffort: Int = 5)
  extends ServiceFactory[Req, Rep]
  with Balancer[Req, Rep]
  with Updating[Req, Rep] {

  // For the OnReady mixin
  private[this] val ready = new Promise[Unit]
  override def onReady: Future[Unit] = ready

  protected[this] val maxEffortExhausted: Counter = statsReceiver.counter("max_effort_exhausted")

  protected class Node(val factory: ServiceFactory[Req, Rep])
    extends ServiceFactoryProxy[Req,Rep](factory)
    with NodeT[Req,Rep] { self =>
    type This = Node
    // Note: These stats are never updated.
    def load: Double = 0.0
    def pending: Int = 0
    def token: Int = 0

    override def close(deadline: Time): Future[Unit] = factory.close(deadline)
    override def apply(conn: ClientConnection): Future[Service[Req,Rep]] = factory(conn)
  }

  /**
   * A simple round robin distributor.
   */
  protected class Distributor(vector: Vector[Node])
    extends DistributorT[Node](vector) {
    type This = Distributor

    /** Indicates if we've seen any down nodes during `pick` which we expected to be available */
    @volatile
    protected[this] var sawDown = false

    /**
     * `up` is the Vector of nodes that were `Status.Open` at creation time.
     * `down` is the Vector of nodes that were not `Status.Open` at creation time.
     *
     *  Since RR is not probabilistic in its selection, we're relying on partition
     *  and select only from healthy nodes.
     */
    private[this] val (up: Vector[Node], down: Vector[Node]) =
      vector.partition(_.isAvailable)

    /**
     * Utility used by [[pick()]].
     *
     * If all nodes are down, we might as well try to send requests somewhere
     * as our view of the world may be out of date.
     */
    protected[this] val selections: Vector[Node] =
      if (up.isEmpty) down else up

    private[this] val currentNode = new AtomicLong()

    // For each node that's requested, we move the currentNode index
    // around the wheel using mod arithmetic. This is the round robin
    // of our balancer.
    private def chooseNext(vecSize: Int): Int = {
      val next = currentNode.getAndIncrement()
      math.abs(next % vecSize).toInt
    }

    def pick(): Node = {
      if (vector.isEmpty)
        return failingNode(emptyException)

      val node = selections(chooseNext(selections.size))
      if (node.status != Status.Open)
        sawDown = true
      node
    }

    def rebuild(): This = new Distributor(vector)
    def rebuild(vector: Vector[Node]): This = new Distributor(vector)

    def needsRebuild: Boolean = {
      // while the `nonEmpty` check isn't necessary, it is an optimization
      // to avoid the iterator allocation in the common case where `down`
      // is empty.
      sawDown || (down.nonEmpty && down.exists(_.isAvailable))
    }
  }

  protected def initDistributor(): Distributor = new Distributor(Vector.empty)
  protected def newNode(factory: ServiceFactory[Req,Rep], statsReceiver: StatsReceiver): Node = new Node(factory)
  protected def failingNode(cause: Throwable): Node = new Node(new FailingFactory(cause))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy