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 }