dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/balancer/priority/balancer.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * 20 * Copyright 2021 gRPC authors. 21 * 22 */ 23 24 // Package priority implements the priority balancer. 25 // 26 // This balancer will be kept in internal until we use it in the xds balancers, 27 // and are confident its functionalities are stable. It will then be exported 28 // for more users. 29 package priority 30 31 import ( 32 "encoding/json" 33 "fmt" 34 "sync" 35 "time" 36 ) 37 38 import ( 39 dubbogoLogger "github.com/dubbogo/gost/log/logger" 40 41 "google.golang.org/grpc/balancer" 42 43 "google.golang.org/grpc/resolver" 44 45 "google.golang.org/grpc/serviceconfig" 46 ) 47 48 import ( 49 "dubbo.apache.org/dubbo-go/v3/xds/utils/balancergroup" 50 "dubbo.apache.org/dubbo-go/v3/xds/utils/buffer" 51 "dubbo.apache.org/dubbo-go/v3/xds/utils/grpcsync" 52 "dubbo.apache.org/dubbo-go/v3/xds/utils/hierarchy" 53 "dubbo.apache.org/dubbo-go/v3/xds/utils/pretty" 54 ) 55 56 // Name is the name of the priority balancer. 57 const Name = "priority_experimental" 58 59 func init() { 60 balancer.Register(bb{}) 61 } 62 63 type bb struct{} 64 65 func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer { 66 b := &priorityBalancer{ 67 cc: cc, 68 done: grpcsync.NewEvent(), 69 childToPriority: make(map[string]int), 70 children: make(map[string]*childBalancer), 71 childBalancerStateUpdate: buffer.NewUnbounded(), 72 } 73 74 b.logger = dubbogoLogger.GetLogger() 75 b.bg = balancergroup.New(cc, bOpts, b, b.logger) 76 b.bg.Start() 77 go b.run() 78 b.logger.Infof("Created") 79 return b 80 } 81 82 func (b bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 83 return parseConfig(s) 84 } 85 86 func (bb) Name() string { 87 return Name 88 } 89 90 // timerWrapper wraps a timer with a boolean. So that when a race happens 91 // between AfterFunc and Stop, the func is guaranteed to not execute. 92 type timerWrapper struct { 93 stopped bool 94 timer *time.Timer 95 } 96 97 type priorityBalancer struct { 98 logger dubbogoLogger.Logger 99 cc balancer.ClientConn 100 bg *balancergroup.BalancerGroup 101 done *grpcsync.Event 102 childBalancerStateUpdate *buffer.Unbounded 103 104 mu sync.Mutex 105 childInUse string 106 // priority of the child that's current in use. Int starting from 0, and 0 107 // is the higher priority. 108 priorityInUse int 109 // priorities is a list of child names from higher to lower priority. 110 priorities []string 111 // childToPriority is a map from the child name to it's priority. Priority 112 // is an int start from 0, and 0 is the higher priority. 113 childToPriority map[string]int 114 // children is a map from child name to sub-balancers. 115 children map[string]*childBalancer 116 // The timer to give a priority some time to connect. And if the priority 117 // doesn't go into Ready/Failure, the next priority will be started. 118 // 119 // One timer is enough because there can be at most one priority in init 120 // state. 121 priorityInitTimer *timerWrapper 122 } 123 124 func (b *priorityBalancer) UpdateClientConnState(s balancer.ClientConnState) error { 125 b.logger.Infof("Received update from resolver, balancer config: %+v", pretty.ToJSON(s.BalancerConfig)) 126 newConfig, ok := s.BalancerConfig.(*LBConfig) 127 if !ok { 128 return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig) 129 } 130 addressesSplit := hierarchy.Group(s.ResolverState.Addresses) 131 132 b.mu.Lock() 133 defer b.mu.Unlock() 134 // Create and remove children, since we know all children from the config 135 // are used by some priority. 136 for name, newSubConfig := range newConfig.Children { 137 bb := balancer.Get(newSubConfig.Config.Name) 138 if bb == nil { 139 b.logger.Errorf("balancer name %v from config is not registered", newSubConfig.Config.Name) 140 continue 141 } 142 143 currentChild, ok := b.children[name] 144 if !ok { 145 // This is a new child, add it to the children list. But note that 146 // the balancer isn't built, because this child can be a low 147 // priority. If necessary, it will be built when syncing priorities. 148 cb := newChildBalancer(name, b, bb) 149 cb.updateConfig(newSubConfig, resolver.State{ 150 Addresses: addressesSplit[name], 151 ServiceConfig: s.ResolverState.ServiceConfig, 152 Attributes: s.ResolverState.Attributes, 153 }) 154 b.children[name] = cb 155 continue 156 } 157 158 // This is not a new child. But the config/addresses could change. 159 160 // The balancing policy name is changed, close the old child. But don't 161 // rebuild, rebuild will happen when syncing priorities. 162 if currentChild.bb.Name() != bb.Name() { 163 currentChild.stop() 164 currentChild.updateBuilder(bb) 165 } 166 167 // Update config and address, but note that this doesn't send the 168 // updates to child balancer (the child balancer might not be built, if 169 // it's a low priority). 170 currentChild.updateConfig(newSubConfig, resolver.State{ 171 Addresses: addressesSplit[name], 172 ServiceConfig: s.ResolverState.ServiceConfig, 173 Attributes: s.ResolverState.Attributes, 174 }) 175 } 176 177 // Remove child from children if it's not in new config. 178 for name, oldChild := range b.children { 179 if _, ok := newConfig.Children[name]; !ok { 180 oldChild.stop() 181 } 182 } 183 184 // Update priorities and handle priority changes. 185 b.priorities = newConfig.Priorities 186 b.childToPriority = make(map[string]int, len(newConfig.Priorities)) 187 for pi, pName := range newConfig.Priorities { 188 b.childToPriority[pName] = pi 189 } 190 // Sync the states of all children to the new updated priorities. This 191 // include starting/stopping child balancers when necessary. 192 b.syncPriority() 193 194 return nil 195 } 196 197 func (b *priorityBalancer) ResolverError(err error) { 198 b.bg.ResolverError(err) 199 } 200 201 func (b *priorityBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 202 b.bg.UpdateSubConnState(sc, state) 203 } 204 205 func (b *priorityBalancer) Close() { 206 b.bg.Close() 207 208 b.mu.Lock() 209 defer b.mu.Unlock() 210 b.done.Fire() 211 // Clear states of the current child in use, so if there's a race in picker 212 // update, it will be dropped. 213 b.childInUse = "" 214 b.stopPriorityInitTimer() 215 } 216 217 func (b *priorityBalancer) ExitIdle() { 218 b.bg.ExitIdle() 219 } 220 221 // stopPriorityInitTimer stops the priorityInitTimer if it's not nil, and set it 222 // to nil. 223 // 224 // Caller must hold b.mu. 225 func (b *priorityBalancer) stopPriorityInitTimer() { 226 timerW := b.priorityInitTimer 227 if timerW == nil { 228 return 229 } 230 b.priorityInitTimer = nil 231 timerW.stopped = true 232 timerW.timer.Stop() 233 } 234 235 // UpdateState implements balancergroup.BalancerStateAggregator interface. The 236 // balancer group sends new connectivity state and picker here. 237 func (b *priorityBalancer) UpdateState(childName string, state balancer.State) { 238 b.childBalancerStateUpdate.Put(&childBalancerState{ 239 name: childName, 240 s: state, 241 }) 242 } 243 244 type childBalancerState struct { 245 name string 246 s balancer.State 247 } 248 249 // run handles child update in a separate goroutine, so if the child sends 250 // updates inline (when called by parent), it won't cause deadlocks (by trying 251 // to hold the same mutex). 252 func (b *priorityBalancer) run() { 253 for { 254 select { 255 case u := <-b.childBalancerStateUpdate.Get(): 256 b.childBalancerStateUpdate.Load() 257 s := u.(*childBalancerState) 258 // Needs to handle state update in a goroutine, because each state 259 // update needs to start/close child policy, could result in 260 // deadlock. 261 b.handleChildStateUpdate(s.name, s.s) 262 case <-b.done.Done(): 263 return 264 } 265 } 266 }