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 }