dubbo.apache.org/dubbo-go/v3@v3.1.1/cluster/router/tag/match.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  package tag
    19  
    20  import (
    21  	"strconv"
    22  )
    23  
    24  import (
    25  	"github.com/dubbogo/gost/log/logger"
    26  )
    27  
    28  import (
    29  	"dubbo.apache.org/dubbo-go/v3/common"
    30  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    31  	"dubbo.apache.org/dubbo-go/v3/config"
    32  	"dubbo.apache.org/dubbo-go/v3/protocol"
    33  )
    34  
    35  type predicate func(invoker protocol.Invoker, tag interface{}) bool
    36  
    37  // static tag matching. no used configuration center to create tag router configuration
    38  func staticTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
    39  	var (
    40  		tag    string
    41  		ok     bool
    42  		result []protocol.Invoker
    43  	)
    44  	if tag, ok = invocation.GetAttachment(constant.Tagkey); !ok {
    45  		tag = url.GetParam(constant.Tagkey, "")
    46  	}
    47  	if tag != "" {
    48  		// match dynamic tag
    49  		result = filterInvokers(invokers, tag, func(invoker protocol.Invoker, tag interface{}) bool {
    50  			return invoker.GetURL().GetParam(constant.Tagkey, "") != tag
    51  		})
    52  	}
    53  
    54  	// match empty tag
    55  	if (len(result) == 0 && !requestIsForce(url, invocation)) || tag == "" {
    56  		result = filterInvokers(invokers, tag, func(invoker protocol.Invoker, tag interface{}) bool {
    57  			return invoker.GetURL().GetParam(constant.Tagkey, "") != ""
    58  		})
    59  	}
    60  	logger.Debugf("[tag router] filter static tag, invokers=%+v", result)
    61  	return result
    62  }
    63  
    64  // dynamic tag matching. used configuration center to create tag router configuration
    65  func dynamicTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation, cfg config.RouterConfig) []protocol.Invoker {
    66  	tag := invocation.GetAttachmentWithDefaultValue(constant.Tagkey, url.GetParam(constant.Tagkey, ""))
    67  	if tag == "" {
    68  		return requestEmptyTag(invokers, cfg)
    69  	}
    70  	return requestTag(invokers, url, invocation, cfg, tag)
    71  }
    72  
    73  // if request.tag is not set, only providers with empty tags will be matched.
    74  // even if a service is available in the cluster, it cannot be invoked if the tag does not match,
    75  // and requests without tags or other tags will never be able to access services with other tags.
    76  func requestEmptyTag(invokers []protocol.Invoker, cfg config.RouterConfig) []protocol.Invoker {
    77  	result := filterInvokers(invokers, "", func(invoker protocol.Invoker, tag interface{}) bool {
    78  		return invoker.GetURL().GetParam(constant.Tagkey, "") != ""
    79  	})
    80  	if len(result) == 0 {
    81  		return result
    82  	}
    83  	for _, tagCfg := range cfg.Tags {
    84  		if len(tagCfg.Addresses) == 0 {
    85  			continue
    86  		}
    87  		result = filterInvokers(result, tagCfg.Addresses, getAddressPredicate(true))
    88  		logger.Debugf("[tag router]filter empty tag address, invokers=%+v", result)
    89  	}
    90  	logger.Debugf("[tag router]filter empty tag, invokers=%+v", result)
    91  	return result
    92  }
    93  
    94  // when request tag =tag1, the provider with tag=tag1 is preferred.
    95  // if no service corresponding to the request tag exists in the cluster,
    96  // the provider with the empty request tag is degraded by default.
    97  // to change the default behavior that no provider matching TAG1 returns an exception, set request.tag.force=true.
    98  func requestTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation, cfg config.RouterConfig, tag string) []protocol.Invoker {
    99  	var (
   100  		addresses []string
   101  		result    []protocol.Invoker
   102  		match     []*common.ParamMatch
   103  	)
   104  	for _, tagCfg := range cfg.Tags {
   105  		if tagCfg.Name == tag {
   106  			addresses = tagCfg.Addresses
   107  			match = tagCfg.Match
   108  		}
   109  	}
   110  
   111  	// only one of 'match' and 'addresses' will take effect if both are specified.
   112  	if len(match) != 0 {
   113  		result = filterInvokers(invokers, match, func(invoker protocol.Invoker, match interface{}) bool {
   114  			matches := match.([]*common.ParamMatch)
   115  			for _, m := range matches {
   116  				if !m.IsMatch(invoker.GetURL()) {
   117  					return true
   118  				}
   119  			}
   120  			return false
   121  		})
   122  	} else {
   123  		if len(addresses) == 0 {
   124  			// filter tag does not match
   125  			result = filterInvokers(invokers, tag, func(invoker protocol.Invoker, tag interface{}) bool {
   126  				return invoker.GetURL().GetParam(constant.Tagkey, "") != tag
   127  			})
   128  			logger.Debugf("[tag router] filter dynamic tag, tag=%s, invokers=%+v", tag, result)
   129  		} else {
   130  			// filter address does not match
   131  			result = filterInvokers(invokers, addresses, getAddressPredicate(false))
   132  			logger.Debugf("[tag router] filter dynamic tag address, invokers=%+v", result)
   133  		}
   134  	}
   135  	// returns the result directly
   136  	if *cfg.Force || requestIsForce(url, invocation) {
   137  		return result
   138  	}
   139  	if len(result) != 0 {
   140  		return result
   141  	}
   142  	// failover: return all Providers without any tags
   143  	result = filterInvokers(invokers, tag, func(invoker protocol.Invoker, tag interface{}) bool {
   144  		return invoker.GetURL().GetParam(constant.Tagkey, "") != ""
   145  	})
   146  	if len(addresses) == 0 {
   147  		return result
   148  	}
   149  	result = filterInvokers(invokers, addresses, getAddressPredicate(true))
   150  	logger.Debugf("[tag router] failover match all providers without any tags, invokers=%+v", result)
   151  	return result
   152  }
   153  
   154  // filterInvokers remove invokers that match with predicate from the original input.
   155  func filterInvokers(invokers []protocol.Invoker, param interface{}, predicate predicate) []protocol.Invoker {
   156  	result := make([]protocol.Invoker, len(invokers))
   157  	copy(result, invokers)
   158  	for i := 0; i < len(result); i++ {
   159  		if predicate(result[i], param) {
   160  			result = append(result[:i], result[i+1:]...)
   161  			i--
   162  		}
   163  	}
   164  	return result
   165  }
   166  
   167  func requestIsForce(url *common.URL, invocation protocol.Invocation) bool {
   168  	force := invocation.GetAttachmentWithDefaultValue(constant.ForceUseTag, url.GetParam(constant.ForceUseTag, "false"))
   169  	ok, err := strconv.ParseBool(force)
   170  	if err != nil {
   171  		logger.Errorf("parse force param fail,force=%s,err=%v", force, err)
   172  	}
   173  	return ok
   174  }
   175  
   176  func getAddressPredicate(result bool) predicate {
   177  	return func(invoker protocol.Invoker, param interface{}) bool {
   178  		address := param.([]string)
   179  		for _, v := range address {
   180  			invokerURL := invoker.GetURL()
   181  			if v == invokerURL.Location || v == constant.AnyHostValue+constant.KeySeparator+invokerURL.Port {
   182  				return result
   183  			}
   184  		}
   185  		return !result
   186  	}
   187  }