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

com.twitter.finagle.loadbalancer.p2c.P2CPick.scala Maven / Gradle / Ivy

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

import com.twitter.finagle.Status
import com.twitter.finagle.util.Rng
import com.twitter.finagle.loadbalancer.{DistributorT, NodeT}

/**
 * A mix-in for a [[DistributorT]] that uses the ideas behind the
 * "power of 2 choices" [1] to select two nodes from the underlying
 * vector.
 *
 * [1] Michael Mitzenmacher. 2001. The Power of Two Choices in
 * Randomized Load Balancing. IEEE Trans. Parallel Distrib. Syst. 12,
 * 10 (October 2001), 1094-1104.
 */
private[loadbalancer] trait P2CPick[Node <: NodeT[_, _]] { self: DistributorT[Node] =>

  /**
   * The random number generator used by `pick` to select two nodes
   * for comparison.
   */
  protected def rng: Rng

  /**
   * The Node returned from `pick` when `vector` is empty.
   */
  protected def emptyNode: Node

  /**
   * The upper bound (exclusive) over which `pick` selects
   * nodes for comparison. Note, this should be <= vector.size.
   */
  protected def bound: Int

  /**
   * Allows implementations to pre-process the `vector` which
   * `pick` will select from.
   */
  protected val vec: Vector[Node]

  /**
   * Picks two nodes randomly, and uniformly, from `vector` within `bound` and
   * selects between the two first by `status` and then by `load`. Effectively,
   * we want to select the most healthy, least loaded of the two.
   */
  def pick(): Node = {
    val range = bound
    if (vec.isEmpty) emptyNode
    else if (range == 1 || vec.size == 1) vec.head
    else {
      // We want to pick two distinct nodes. We do this without replacement by
      // restricting the bounds of the second selection, `b`, to be one less than the
      // the first, `a`. Effectively, we are creating a "hole" in the second selection.
      // We avoid the "hole" by incrementing the index by one when we hit it. However,
      // special care must be taken to not bias towards "hole" + 1, so we treat the
      // entire range greater than "hole" uniformly (hence, the >= in the collision
      // comparison).
      val a = rng.nextInt(range)
      var b = rng.nextInt(range - 1)
      if (b >= a) { b = b + 1 }

      val nodeA = vec(a)
      val nodeB = vec(b)

      // If both nodes are in the same health status, we pick the least loaded
      // one. Otherwise we pick the one that's healthier.
      val aStatus = nodeA.status
      val bStatus = nodeB.status
      if (aStatus == bStatus) {
        if (nodeA.load <= nodeB.load) nodeA else nodeB
      } else {
        if (Status.best(aStatus, bStatus) == aStatus) nodeA else nodeB
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy