istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/loadbalancersim/loadbalancer/leastrequest.go (about)

     1  //  Copyright Istio Authors
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package loadbalancer
    16  
    17  import (
    18  	"math"
    19  	"math/rand"
    20  	"sync"
    21  	"time"
    22  
    23  	"istio.io/istio/pkg/test/loadbalancersim/network"
    24  )
    25  
    26  type LeastRequestSettings struct {
    27  	Connections       []*WeightedConnection
    28  	ActiveRequestBias float64
    29  }
    30  
    31  func NewLeastRequest(s LeastRequestSettings) network.Connection {
    32  	if len(s.Connections) == 0 {
    33  		panic("attempting to create load balancer with zero connections")
    34  	}
    35  
    36  	conn := newLBConnection("LeastRequestLB", s.Connections)
    37  
    38  	if conn.AllWeightsEqual() {
    39  		return newUnweightedLeastRequest(conn)
    40  	}
    41  
    42  	return newWeightedLeastRequest(conn, s.ActiveRequestBias)
    43  }
    44  
    45  type unweightedLeastRequest struct {
    46  	*weightedConnections
    47  	r *rand.Rand
    48  }
    49  
    50  // nolint: gosec
    51  // Test only code
    52  func newUnweightedLeastRequest(conn *weightedConnections) network.Connection {
    53  	return &unweightedLeastRequest{
    54  		weightedConnections: conn,
    55  		r:                   rand.New(rand.NewSource(time.Now().UnixNano())),
    56  	}
    57  }
    58  
    59  func (lb *unweightedLeastRequest) pick2() (*WeightedConnection, *WeightedConnection) {
    60  	numConnections := len(lb.conns)
    61  	index1 := lb.r.Intn(numConnections)
    62  	index2 := lb.r.Intn(numConnections)
    63  	if index2 == index1 {
    64  		index2 = (index2 + 1) % numConnections
    65  	}
    66  
    67  	return lb.get(index1), lb.get(index2)
    68  }
    69  
    70  func (lb *unweightedLeastRequest) Request(onDone func()) {
    71  	if len(lb.conns) == 1 {
    72  		lb.doRequest(lb.get(0), onDone)
    73  		return
    74  	}
    75  
    76  	// Pick 2 endpoints at random.
    77  	c1, c2 := lb.pick2()
    78  
    79  	// Choose the endpoint with fewer active requests.
    80  	selected := c1
    81  	if c2.ActiveRequests() < c1.ActiveRequests() {
    82  		selected = c2
    83  	}
    84  
    85  	// Apply the selected endpoint to the metrics decorator and send the request.
    86  	lb.doRequest(selected, onDone)
    87  }
    88  
    89  type weightedLeastRequest struct {
    90  	*weightedConnections
    91  	activeRequestBias float64
    92  	edf               *EDF
    93  	edfMutex          sync.Mutex
    94  }
    95  
    96  func newWeightedLeastRequest(conn *weightedConnections, activeRequestBias float64) network.Connection {
    97  	lb := &weightedLeastRequest{
    98  		weightedConnections: conn,
    99  		activeRequestBias:   activeRequestBias,
   100  		edf:                 NewEDF(),
   101  	}
   102  
   103  	// Add all endpoints to the EDF scheduler.
   104  	for _, c := range conn.conns {
   105  		lb.edf.Add(lb.calcEDFWeight(0, c), c)
   106  	}
   107  
   108  	return lb
   109  }
   110  
   111  func (lb *weightedLeastRequest) Request(onDone func()) {
   112  	// Pick the next endpoint and re-add it with the updated weight.
   113  	lb.edfMutex.Lock()
   114  	selected := lb.edf.PickAndAdd(lb.calcEDFWeight).(*WeightedConnection)
   115  	lb.edfMutex.Unlock()
   116  
   117  	// Make the request.
   118  	lb.doRequest(selected, onDone)
   119  }
   120  
   121  func (lb *weightedLeastRequest) calcEDFWeight(_ float64, value any) float64 {
   122  	conn := value.(*WeightedConnection)
   123  
   124  	weight := float64(conn.Weight)
   125  	if lb.activeRequestBias >= 1.0 {
   126  		weight /= float64(conn.ActiveRequests() + 1)
   127  	} else if lb.activeRequestBias > 0.0 {
   128  		weight /= math.Pow(float64(conn.ActiveRequests()+1), lb.activeRequestBias)
   129  	}
   130  	return weight
   131  }