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  }