dubbo.apache.org/dubbo-go/v3@v3.1.1/filter/hystrix/filter.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 hystrix provides hystrix filter. 19 package hystrix 20 21 import ( 22 "context" 23 "fmt" 24 "reflect" 25 "regexp" 26 "sync" 27 ) 28 29 import ( 30 "github.com/afex/hystrix-go/hystrix" 31 32 "github.com/dubbogo/gost/log/logger" 33 34 perrors "github.com/pkg/errors" 35 36 "gopkg.in/yaml.v2" 37 ) 38 39 import ( 40 "dubbo.apache.org/dubbo-go/v3/common/constant" 41 "dubbo.apache.org/dubbo-go/v3/common/extension" 42 "dubbo.apache.org/dubbo-go/v3/config" 43 "dubbo.apache.org/dubbo-go/v3/filter" 44 "dubbo.apache.org/dubbo-go/v3/protocol" 45 ) 46 47 const ( 48 // nolint 49 HYSTRIX = "hystrix" 50 ) 51 52 var ( 53 confConsumer = &FilterConfig{} 54 confProvider = &FilterConfig{} 55 configLoadMutex = sync.RWMutex{} 56 consumerConfigOnce sync.Once 57 providerConfigOnce sync.Once 58 ) 59 60 func init() { 61 extension.SetFilter(constant.HystrixConsumerFilterKey, newFilterConsumer) 62 extension.SetFilter(constant.HystrixProviderFilterKey, newFilterProvider) 63 } 64 65 // FilterError implements error interface 66 type FilterError struct { 67 err error 68 failByHystrix bool 69 } 70 71 func (hfError *FilterError) Error() string { 72 return hfError.err.Error() 73 } 74 75 // FailByHystrix returns whether the fails causing by Hystrix 76 func (hfError *FilterError) FailByHystrix() bool { 77 return hfError.failByHystrix 78 } 79 80 // NewHystrixFilterError return a FilterError instance 81 func NewHystrixFilterError(err error, failByHystrix bool) error { 82 return &FilterError{ 83 err: err, 84 failByHystrix: failByHystrix, 85 } 86 } 87 88 // Filter for Hystrix 89 /** 90 * You should add hystrix related configuration in provider or consumer config or both, according to which side you are to apply Filter. 91 * For example: 92 * filter_conf: 93 * hystrix: 94 * configs: 95 * # =========== Define config here ============ 96 * "Default": 97 * timeout : 1000 98 * max_concurrent_requests : 25 99 * sleep_window : 5000 100 * error_percent_threshold : 50 101 * request_volume_threshold: 20 102 * "userp": 103 * timeout: 2000 104 * max_concurrent_requests: 512 105 * sleep_window: 4000 106 * error_percent_threshold: 35 107 * request_volume_threshold: 6 108 * "userp_m": 109 * timeout : 1200 110 * max_concurrent_requests : 512 111 * sleep_window : 6000 112 * error_percent_threshold : 60 113 * request_volume_threshold: 16 114 * # =========== Define error whitelist which will be ignored by Hystrix counter ============ 115 * error_whitelist: [".*exception.*"] 116 * 117 * # =========== Apply default config here =========== 118 * default: "Default" 119 * 120 * services: 121 * "com.ikurento.user.UserProvider": 122 * # =========== Apply service level config =========== 123 * service_config: "userp" 124 * # =========== Apply method level config =========== 125 * methods: 126 * "GetUser": "userp_m" 127 * "GetUser1": "userp_m" 128 */ 129 type Filter struct { 130 COrP bool // true for consumer 131 res map[string][]*regexp.Regexp 132 ifNewMap sync.Map 133 } 134 135 // Invoke is an implementation of filter, provides Hystrix pattern latency and fault tolerance 136 func (f *Filter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { 137 cmdName := fmt.Sprintf("%s&method=%s", invoker.GetURL().Key(), invocation.MethodName()) 138 139 // Do the configuration if the circuit breaker is created for the first time 140 if _, load := f.ifNewMap.LoadOrStore(cmdName, true); !load { 141 configLoadMutex.Lock() 142 filterConf := getConfig(invoker.GetURL().Service(), invocation.MethodName(), f.COrP) 143 for _, ptn := range filterConf.Error { 144 reg, err := regexp.Compile(ptn) 145 if err != nil { 146 logger.Warnf("[Hystrix Filter]Errors occurred parsing error omit regexp: %s, %v", ptn, err) 147 } else { 148 if f.res == nil { 149 f.res = make(map[string][]*regexp.Regexp) 150 } 151 f.res[invocation.MethodName()] = append(f.res[invocation.MethodName()], reg) 152 } 153 } 154 hystrix.ConfigureCommand(cmdName, hystrix.CommandConfig{ 155 Timeout: filterConf.Timeout, 156 MaxConcurrentRequests: filterConf.MaxConcurrentRequests, 157 SleepWindow: filterConf.SleepWindow, 158 ErrorPercentThreshold: filterConf.ErrorPercentThreshold, 159 RequestVolumeThreshold: filterConf.RequestVolumeThreshold, 160 }) 161 configLoadMutex.Unlock() 162 } 163 configLoadMutex.RLock() 164 _, _, err := hystrix.GetCircuit(cmdName) 165 configLoadMutex.RUnlock() 166 if err != nil { 167 logger.Errorf("[Hystrix Filter]Errors occurred getting circuit for %s , will invoke without hystrix, error is: %+v", cmdName, err) 168 return invoker.Invoke(ctx, invocation) 169 } 170 logger.Infof("[Hystrix Filter]Using hystrix filter: %s", cmdName) 171 var result protocol.Result 172 _ = hystrix.Do(cmdName, func() error { 173 result = invoker.Invoke(ctx, invocation) 174 err := result.Error() 175 if err != nil { 176 result.SetError(NewHystrixFilterError(err, false)) 177 for _, reg := range f.res[invocation.MethodName()] { 178 if reg.MatchString(err.Error()) { 179 logger.Debugf("[Hystrix Filter]Error in invocation but omitted in circuit breaker: %v; %s", err, cmdName) 180 return nil 181 } 182 } 183 } 184 return err 185 }, func(err error) error { 186 // Return error and if it is caused by hystrix logic, so that it can be handled by previous filters. 187 _, ok := err.(hystrix.CircuitError) 188 logger.Debugf("[Hystrix Filter]Hystrix health check counted, error is: %v, failed by hystrix: %v; %s", err, ok, cmdName) 189 result = &protocol.RPCResult{} 190 result.SetResult(nil) 191 result.SetError(NewHystrixFilterError(err, ok)) 192 return err 193 }) 194 return result 195 } 196 197 // OnResponse dummy process, returns the result directly 198 func (f *Filter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { 199 return result 200 } 201 202 // newFilterConsumer returns Filter instance for consumer 203 func newFilterConsumer() filter.Filter { 204 // When first called, load the config in 205 consumerConfigOnce.Do(func() { 206 if err := initConfigConsumer(); err != nil { 207 logger.Warnf("[Hystrix Filter]ShutdownConfig load failed for consumer, error is: %v , will use default", err) 208 } 209 }) 210 return &Filter{COrP: true} 211 } 212 213 // newFilterProvider returns Filter instance for provider 214 func newFilterProvider() filter.Filter { 215 providerConfigOnce.Do(func() { 216 if err := initConfigProvider(); err != nil { 217 logger.Warnf("[Hystrix Filter]ShutdownConfig load failed for provider, error is: %v , will use default", err) 218 } 219 }) 220 return &Filter{COrP: false} 221 } 222 223 func getConfig(service string, method string, cOrP bool) CommandConfigWithError { 224 // Find method level config 225 var conf *FilterConfig 226 if cOrP { 227 conf = confConsumer 228 } else { 229 conf = confProvider 230 } 231 getConf := conf.Configs[conf.Services[service].Methods[method]] 232 if getConf != nil { 233 logger.Infof("[Hystrix Filter]Found method-level config for %s - %s", service, method) 234 return *getConf 235 } 236 // Find service level config 237 getConf = conf.Configs[conf.Services[service].ServiceConfig] 238 if getConf != nil { 239 logger.Infof("[Hystrix Filter]Found service-level config for %s - %s", service, method) 240 return *getConf 241 } 242 // Find default config 243 getConf = conf.Configs[conf.Default] 244 if getConf != nil { 245 logger.Infof("[Hystrix Filter]Found global default config for %s - %s", service, method) 246 return *getConf 247 } 248 getConf = &CommandConfigWithError{} 249 logger.Infof("[Hystrix Filter]No config found for %s - %s, using default", service, method) 250 return *getConf 251 } 252 253 func initConfigConsumer() error { 254 if config.GetConsumerConfig().FilterConf == nil { 255 return perrors.Errorf("no config for hystrix_consumer") 256 } 257 filterConf := config.GetConsumerConfig().FilterConf 258 var filterConfig interface{} 259 switch reflect.ValueOf(filterConf).Interface().(type) { 260 case map[interface{}]interface{}: 261 filterConfig = config.GetConsumerConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] 262 case map[string]interface{}: 263 filterConfig = config.GetConsumerConfig().FilterConf.(map[string]interface{})[HYSTRIX] 264 } 265 if filterConfig == nil { 266 return perrors.Errorf("no config for hystrix_consumer") 267 } 268 hystrixConfByte, err := yaml.Marshal(filterConfig) 269 if err != nil { 270 return err 271 } 272 err = yaml.Unmarshal(hystrixConfByte, confConsumer) 273 if err != nil { 274 return err 275 } 276 return nil 277 } 278 279 func initConfigProvider() error { 280 if config.GetProviderConfig().FilterConf == nil { 281 return perrors.Errorf("no config for hystrix_provider") 282 } 283 filterConfig := config.GetProviderConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] 284 if filterConfig == nil { 285 return perrors.Errorf("no config for hystrix_provider") 286 } 287 hystrixConfByte, err := yaml.Marshal(filterConfig) 288 if err != nil { 289 return err 290 } 291 err = yaml.Unmarshal(hystrixConfByte, confProvider) 292 if err != nil { 293 return err 294 } 295 return nil 296 } 297 298 //For sake of dynamic config 299 //func RefreshHystrix() error { 300 // conf = &FilterConfig{} 301 // hystrix.Flush() 302 // return initHystrixConfig() 303 //} 304 305 // nolint 306 type CommandConfigWithError struct { 307 Timeout int `yaml:"timeout"` 308 MaxConcurrentRequests int `yaml:"max_concurrent_requests"` 309 RequestVolumeThreshold int `yaml:"request_volume_threshold"` 310 SleepWindow int `yaml:"sleep_window"` 311 ErrorPercentThreshold int `yaml:"error_percent_threshold"` 312 Error []string `yaml:"error_whitelist"` 313 } 314 315 //ShutdownConfig: 316 //- Timeout: how long to wait for command to complete, in milliseconds 317 //- MaxConcurrentRequests: how many commands of the same type can run at the same time 318 //- RequestVolumeThreshold: the minimum number of requests needed before a circuit can be tripped due to health 319 //- SleepWindow: how long, in milliseconds, to wait after a circuit opens before testing for recovery 320 //- ErrorPercentThreshold: it causes circuits to open once the rolling measure of errors exceeds this percent of requests 321 //See hystrix doc 322 323 // nolint 324 type FilterConfig struct { 325 Configs map[string]*CommandConfigWithError 326 Default string 327 Services map[string]ServiceHystrixConfig 328 } 329 330 // nolint 331 type ServiceHystrixConfig struct { 332 ServiceConfig string `yaml:"service_config"` 333 Methods map[string]string 334 }