@@ -332,8 +332,10 @@ public boolean isEquivalentTo(RoundRobinPicker picker) {
332332 * Implementation of Static Stride Scheduler, replaces EDFScheduler.
333333 * <p>
334334 * The Static Stride Scheduler works by iterating through the list of subchannel weights
335- * and using modular arithmetic to evenly distribute picks and skips, favoring
336- * entries with the highest weight.
335+ * and using modular arithmetic to evenly distribute picks and skips, favoring entries with the
336+ * highest weight. It generates a practically equivalent sequence of picks as the EDFScheduler.
337+ * Albeit needing more bandwidth, the Static Stride Scheduler is more performant than the
338+ * EDFScheduler, as it removes the need for a priority queue (and thus mutex locks).
337339 * <p>
338340 * go/static-stride-scheduler
339341 * <p>
@@ -346,14 +348,12 @@ public boolean isEquivalentTo(RoundRobinPicker picker) {
346348 static final class StaticStrideScheduler {
347349 private final int [] scaledWeights ;
348350 private final int sizeDivisor ;
349- private final Random random ;
350351 private final AtomicInteger sequence ;
351352 private static final int K_MAX_WEIGHT = 0xFFFF ;
352- private static final long UINT32_MAX = 0xFFFF_FFFFL ;
353353
354354 StaticStrideScheduler (float [] weights , Random random ) {
355+ checkArgument (weights .length >= 1 , "Couldn't build scheduler: requires at least one weight" );
355356 int numChannels = weights .length ;
356- checkArgument (numChannels >= 1 , "Couldn't build scheduler: requires at least one weight" );
357357 int numWeightedChannels = 0 ;
358358 double sumWeight = 0 ;
359359 float maxWeight = 0 ;
@@ -385,29 +385,39 @@ static final class StaticStrideScheduler {
385385
386386 this .scaledWeights = scaledWeights ;
387387 this .sizeDivisor = numChannels ;
388- this .random = random ;
389- this .sequence = new AtomicInteger ((int ) (this .random .nextDouble () * UINT32_MAX ));
388+ this .sequence = new AtomicInteger (random .nextInt ());
390389
391390 }
392391
393- /** Returns the next sequence number and increases sequence with wraparound. */
392+ /** Returns the next sequence number and atomically increases sequence with wraparound. */
394393 private long nextSequence () {
395394 return Integer .toUnsignedLong (sequence .getAndIncrement ());
396395 }
397396
397+ public long getSequence () {
398+ return Integer .toUnsignedLong (sequence .get ());
399+ }
398400
399- /** Selects index of next backend server. */
401+ /*
402+ * Selects index of next backend server.
403+ * <p>
404+ * A 2D array is compactly represented where the row represents the generation and the column
405+ * represents the backend index. The value of an element is a boolean value which indicates
406+ * whether or not a backend should be picked now. An atomically incremented counter keeps track
407+ * of our backend and generation through modular arithmetic within the pick() method.
408+ * An offset is also included to minimize consecutive non-picks of a backend.
409+ */
400410 int pick () {
401411 while (true ) {
402412 long sequence = this .nextSequence ();
403- long backendIndex = sequence % this .sizeDivisor ;
413+ int backendIndex = ( int ) ( sequence % this .sizeDivisor ) ;
404414 long generation = sequence / this .sizeDivisor ;
405- long weight = this .scaledWeights [( int ) backendIndex ];
406- long offset = backendIndex * K_MAX_WEIGHT / 2 ;
415+ long weight = this .scaledWeights [backendIndex ];
416+ long offset = ( long ) K_MAX_WEIGHT / 2 * backendIndex ;
407417 if ((weight * generation + offset ) % K_MAX_WEIGHT < K_MAX_WEIGHT - weight ) {
408418 continue ;
409419 }
410- return ( int ) backendIndex ;
420+ return backendIndex ;
411421 }
412422 }
413423 }
0 commit comments