dubbo.apache.org/dubbo-go/v3@v3.1.1/filter/exec_limit/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 exec_limit provides a filter for limiting the number of in-progress request and it's thread-safe.
    19  /*
    20   example:
    21   "UserProvider":
    22     registry: "hangzhouzk"
    23     protocol : "dubbo"
    24     interface : "com.ikurento.user.UserProvider"
    25     ... # other configuration
    26     execute.limit: 200 # the name of MethodServiceTpsLimiterImpl. if the value < 0, invocation will be ignored.
    27     execute.limit.rejected.handle: "default" # the name of rejected handler
    28     methods:
    29      - name: "GetUser"
    30        execute.limit: 20, # in this case, this configuration in service-level will be ignored.
    31      - name: "UpdateUser"
    32        execute.limit: -1, # If the rate<0, the method will be ignored
    33      - name: "DeleteUser"
    34        execute.limit.rejected.handle: "customHandler" # Using the custom handler to do something when the request was rejected.
    35      - name: "AddUser"
    36   From the example, the configuration in service-level is 200, and the configuration of method GetUser is 20.
    37   it means that, the GetUser will be counted separately.
    38   The configuration of method UpdateUser is -1, so the invocation for it will not be counted.
    39   So the method DeleteUser and method AddUser will be limited by service-level configuration.
    40   Sometimes we want to do something, like log the request or return default value when the request is over limitation.
    41   Then you can implement the RejectedExecutionHandler interface and register it by invoking SetRejectedExecutionHandler.
    42  */
    43  package exec_limit
    44  
    45  import (
    46  	"context"
    47  	"strconv"
    48  	"sync"
    49  	"sync/atomic"
    50  )
    51  
    52  import (
    53  	"github.com/dubbogo/gost/log/logger"
    54  
    55  	"github.com/modern-go/concurrent"
    56  )
    57  
    58  import (
    59  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    60  	"dubbo.apache.org/dubbo-go/v3/common/extension"
    61  	"dubbo.apache.org/dubbo-go/v3/filter"
    62  	_ "dubbo.apache.org/dubbo-go/v3/filter/handler"
    63  	"dubbo.apache.org/dubbo-go/v3/protocol"
    64  )
    65  
    66  var (
    67  	once         sync.Once
    68  	executeLimit *executeLimitFilter
    69  )
    70  
    71  func init() {
    72  	extension.SetFilter(constant.ExecuteLimitFilterKey, newFilter)
    73  }
    74  
    75  type executeLimitFilter struct {
    76  	executeState *concurrent.Map
    77  }
    78  
    79  // ExecuteState defines the concurrent count
    80  type ExecuteState struct {
    81  	concurrentCount int64
    82  }
    83  
    84  // newFilter returns the singleton Filter instance
    85  func newFilter() filter.Filter {
    86  	if executeLimit == nil {
    87  		once.Do(func() {
    88  			executeLimit = &executeLimitFilter{
    89  				executeState: concurrent.NewMap(),
    90  			}
    91  		})
    92  	}
    93  	return executeLimit
    94  }
    95  
    96  // Invoke judges whether the current processing requests over the threshold
    97  func (f *executeLimitFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
    98  	methodConfigPrefix := "methods." + invocation.MethodName() + "."
    99  	ivkURL := invoker.GetURL()
   100  	limitTarget := ivkURL.ServiceKey()
   101  	var limitRateConfig string
   102  
   103  	methodLevelConfig := ivkURL.GetParam(methodConfigPrefix+constant.ExecuteLimitKey, "")
   104  	if len(methodLevelConfig) > 0 {
   105  		// we have the method-level configuration
   106  		limitTarget = limitTarget + "#" + invocation.MethodName()
   107  		limitRateConfig = methodLevelConfig
   108  	} else {
   109  		limitRateConfig = ivkURL.GetParam(constant.ExecuteLimitKey, constant.DefaultExecuteLimit)
   110  	}
   111  
   112  	limitRate, err := strconv.ParseInt(limitRateConfig, 0, 0)
   113  	if err != nil {
   114  		logger.Errorf("The configuration of execute.limit is invalid: %s", limitRateConfig)
   115  		return &protocol.RPCResult{}
   116  	}
   117  
   118  	if limitRate < 0 {
   119  		return invoker.Invoke(ctx, invocation)
   120  	}
   121  
   122  	state, _ := f.executeState.LoadOrStore(limitTarget, &ExecuteState{
   123  		concurrentCount: 0,
   124  	})
   125  
   126  	concurrentCount := state.(*ExecuteState).increase()
   127  	defer state.(*ExecuteState).decrease()
   128  	if concurrentCount > limitRate {
   129  		logger.Errorf("The invocation was rejected due to over the execute limitation, url: %s ", ivkURL.String())
   130  		rejectedHandlerConfig := ivkURL.GetParam(methodConfigPrefix+constant.ExecuteRejectedExecutionHandlerKey,
   131  			ivkURL.GetParam(constant.ExecuteRejectedExecutionHandlerKey, constant.DefaultKey))
   132  		rejectedExecutionHandler, err := extension.GetRejectedExecutionHandler(rejectedHandlerConfig)
   133  		if err != nil {
   134  			logger.Warn(err)
   135  		} else {
   136  			return rejectedExecutionHandler.RejectedExecution(ivkURL, invocation)
   137  		}
   138  	}
   139  
   140  	return invoker.Invoke(ctx, invocation)
   141  }
   142  
   143  // OnResponse dummy process, returns the result directly
   144  func (f *executeLimitFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result {
   145  	return result
   146  }
   147  
   148  func (state *ExecuteState) increase() int64 {
   149  	return atomic.AddInt64(&state.concurrentCount, 1)
   150  }
   151  
   152  func (state *ExecuteState) decrease() {
   153  	atomic.AddInt64(&state.concurrentCount, -1)
   154  }