@@ -5,6 +5,8 @@ package router
55
66import (
77 "context"
8+ "fmt"
9+ "net"
810 "slices"
911 "strings"
1012 "sync"
@@ -41,6 +43,8 @@ type ScoreBasedRouter struct {
4143 backends map [string ]* backendWrapper
4244 // TODO: sort the groups to leverage binary search.
4345 groups []* Group
46+ // portConflictDetector dispatches listener ports to cluster-scoped backend groups.
47+ portConflictDetector * portConflictDetector
4448 // The routing rule for categorizing backends to groups.
4549 matchType MatchType
4650 observeError error
@@ -74,6 +78,8 @@ func (r *ScoreBasedRouter) Init(ctx context.Context, ob observer.BackendObserver
7478 r .matchType = MatchClientCIDR
7579 case config .MatchProxyCIDRStr :
7680 r .matchType = MatchProxyCIDR
81+ case config .MatchPortStr :
82+ r .matchType = MatchPort
7783 case "" :
7884 default :
7985 r .logger .Error ("unsupported routing rule, use the default rule" , zap .String ("rule" , cfg .Balance .RoutingRule ))
@@ -110,7 +116,10 @@ func (router *ScoreBasedRouter) GetBackendSelector(clientInfo ClientInfo) Backen
110116 return
111117 }
112118 // The group may change from round to round because the backends are updated.
113- group = router .routeToGroup (clientInfo )
119+ group , err = router .routeToGroup (clientInfo )
120+ if err != nil {
121+ return
122+ }
114123 if group == nil {
115124 err = ErrNoBackend
116125 return
@@ -146,14 +155,22 @@ func (router *ScoreBasedRouter) HealthyBackendCount() int {
146155}
147156
148157// called in the lock
149- func (router * ScoreBasedRouter ) routeToGroup (clientInfo ClientInfo ) * Group {
158+ func (router * ScoreBasedRouter ) routeToGroup (clientInfo ClientInfo ) (* Group , error ) {
159+ if router .matchType == MatchPort {
160+ _ , port , err := net .SplitHostPort (clientInfo .ListenerAddr )
161+ if err != nil {
162+ router .logger .Error ("checking port failed" , zap .String ("listener_addr" , clientInfo .ListenerAddr ), zap .Error (err ))
163+ return nil , nil
164+ }
165+ return router .portConflictDetector .groupFor (port )
166+ }
150167 // TODO: binary search
151168 for _ , group := range router .groups {
152169 if group .Match (clientInfo ) {
153- return group
170+ return group , nil
154171 }
155172 }
156- return nil
173+ return nil , nil
157174}
158175
159176// RefreshBackend implements Router.GetBackendSelector interface.
@@ -233,7 +250,32 @@ func (router *ScoreBasedRouter) updateBackendHealth(healthResults observer.Healt
233250 }
234251}
235252
236- // Update the groups after the backend list is updated.
253+ func matchPortValue (clusterName , port string ) string {
254+ if clusterName == "" {
255+ return port
256+ }
257+ return fmt .Sprintf ("%s:%s" , clusterName , port )
258+ }
259+
260+ func (router * ScoreBasedRouter ) rebuildPortConflictDetector () {
261+ if router .matchType != MatchPort {
262+ router .portConflictDetector = nil
263+ return
264+ }
265+ detector := newPortConflictDetector ()
266+ for _ , group := range router .groups {
267+ for _ , value := range group .values {
268+ clusterName , port , ok := strings .Cut (value , ":" )
269+ if ! ok {
270+ port = value
271+ clusterName = ""
272+ }
273+ detector .bind (port , clusterName , group )
274+ }
275+ }
276+ router .portConflictDetector = detector
277+ }
278+
237279// called in the lock.
238280func (router * ScoreBasedRouter ) updateGroups () {
239281 for _ , backend := range router .backends {
@@ -243,22 +285,39 @@ func (router *ScoreBasedRouter) updateGroups() {
243285 delete (router .backends , backend .id )
244286 if backend .group != nil {
245287 backend .group .RemoveBackend (backend .id )
246- // remove empty groups
247288 if backend .group .Empty () {
248289 router .groups = slices .DeleteFunc (router .groups , func (g * Group ) bool {
249290 return g == backend .group
250291 })
251292 }
293+ backend .group = nil
252294 }
253295 continue
254296 }
255- // If the labels were correctly set, we won't update its group even if the labels change.
256297 if backend .group != nil {
298+ switch router .matchType {
299+ case MatchClientCIDR , MatchProxyCIDR , MatchPort :
300+ var values []string
301+ switch router .matchType {
302+ case MatchClientCIDR , MatchProxyCIDR :
303+ values = backend .Cidr ()
304+ case MatchPort :
305+ port := backend .TiProxyPort ()
306+ if port != "" {
307+ values = []string {matchPortValue (backend .ClusterName (), port )}
308+ }
309+ }
310+ if ! backend .group .EqualValues (values ) {
311+ router .logger .Warn ("backend routing values changed, keep the existing group until it is removed" ,
312+ zap .String ("backend_id" , backend .id ),
313+ zap .String ("addr" , backend .Addr ()),
314+ zap .Strings ("current_values" , values ),
315+ zap .Strings ("group_values" , backend .group .values ))
316+ }
317+ }
257318 continue
258319 }
259320
260- // If the backend is not in any group, add it to a new group if its label is set.
261- // In operator deployment, the labels are set dynamically.
262321 var group * Group
263322 switch router .matchType {
264323 case MatchAll :
@@ -267,33 +326,43 @@ func (router *ScoreBasedRouter) updateGroups() {
267326 router .groups = append (router .groups , group )
268327 }
269328 group = router .groups [0 ]
270- case MatchClientCIDR , MatchProxyCIDR :
271- cidrs := backend .Cidr ()
272- if len (cidrs ) == 0 {
329+ case MatchClientCIDR , MatchProxyCIDR , MatchPort :
330+ var values []string
331+ switch router .matchType {
332+ case MatchClientCIDR , MatchProxyCIDR :
333+ values = backend .Cidr ()
334+ case MatchPort :
335+ port := backend .TiProxyPort ()
336+ if port != "" {
337+ values = []string {matchPortValue (backend .ClusterName (), port )}
338+ }
339+ }
340+ if len (values ) == 0 {
273341 break
274342 }
275343 for _ , g := range router .groups {
276- if g .Intersect (cidrs ) {
344+ if g .Intersect (values ) {
277345 group = g
278346 break
279347 }
280348 }
281349 if group == nil {
282- g , err := NewGroup (cidrs , router .bpCreator , router .matchType , router .logger )
350+ g , err := NewGroup (values , router .bpCreator , router .matchType , router .logger )
283351 if err == nil {
284352 group = g
285353 router .groups = append (router .groups , group )
286354 }
287- // maybe too many logs, ignore the error now
288355 }
289356 }
290- if group ! = nil {
291- group . AddBackend ( backend . id , backend )
357+ if group = = nil {
358+ continue
292359 }
360+ group .AddBackend (backend .id , backend )
293361 }
294362 for _ , group := range router .groups {
295363 group .RefreshCidr ()
296364 }
365+ router .rebuildPortConflictDetector ()
297366}
298367
299368func (router * ScoreBasedRouter ) rebalanceLoop (ctx context.Context ) {
0 commit comments