Skip to content

More accurate control of throttling. #306

Open
@luckyxiaoqiang

Description

@luckyxiaoqiang

Issue Description

Type: feature request

Describe what feature you want

Current feature

Now the definition of flow.Rule is as below:

type Rule struct {
	// ID represents the unique ID of the rule (optional).
	ID string `json:"id,omitempty"`
	// Resource represents the resource name.
	Resource               string                 `json:"resource"`
	TokenCalculateStrategy TokenCalculateStrategy `json:"tokenCalculateStrategy"`
	ControlBehavior        ControlBehavior        `json:"controlBehavior"`
	// Threshold means the threshold during StatIntervalInMs
	// If StatIntervalInMs is 1000(1 second), Threshold means QPS
	Threshold         float64          `json:"threshold"`
	RelationStrategy  RelationStrategy `json:"relationStrategy"`
	RefResource       string           `json:"refResource"`
	MaxQueueingTimeMs uint32           `json:"maxQueueingTimeMs"`
	WarmUpPeriodSec   uint32           `json:"warmUpPeriodSec"`
	WarmUpColdFactor  uint32           `json:"warmUpColdFactor"`
	// StatIntervalInMs indicates the statistic interval and it's the optional setting for flow Rule.
	// If user doesn't set StatIntervalInMs, that means using default metric statistic of resource.
	// If the StatIntervalInMs user specifies can not reuse the global statistic of resource,
	// 		sentinel will generate independent statistic structure for this rule.
	StatIntervalInMs uint32 `json:"statIntervalInMs"`
}

As the doc 流量控制 describes:

这里特别强调一下StatIntervalInMs和Threshold这两个字段,这两个字段决定了流量控制器的灵敏度。以 Direct + Reject 的流控策略为例,流量控制器的行为就是在StatIntervalInMs周期内,允许的最大请求数量是Threshold。比如,如果StatIntervalInMs是10000,Threshold是10000,那么流量控制器的行为就是10s内运行最多10000次访问。

But now there is scenario that the total passed requests count in StatIntervalInMs can exceeds the Threshold. And this is inconsistent with the doc description.

Example

Now let‘s see an example.
A throttling rule as below:

flow.Rule{
	Resource:               "some-test",
	TokenCalculateStrategy: flow.Direct,
	ControlBehavior:        flow.Throttling,
	Threshold:              10,
	StatIntervalInMs:       1000,
}

And we build a request sequence:
time 1.0s: req batch = 10
time 1.1s: req batch = 1
time 1.2s: req batch = 1
time 1.3s: req batch = 1
time 1.4s: req batch = 1
time 1.5s: req batch = 1
time 1.6s: req batch = 1
time 1.7s: req batch = 1
time 1.8s: req batch = 1
time 1.9s: req batch = 1

If we use the latest version of master currently(c40258e), all requests would pass. In one StatIntervalInMs, there are 19 resource accesses allowed, which exceeds the Threshold 10.

Detail example code:

package main

import (
	"fmt"
	"log"
	"time"

	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/base"
	"github.com/alibaba/sentinel-golang/core/config"
	"github.com/alibaba/sentinel-golang/core/flow"
	"github.com/alibaba/sentinel-golang/logging"
)

func main() {
	// We should initialize Sentinel first.
	conf := config.NewDefaultConfig()
	// for testing, logging output to console
	conf.Sentinel.Log.Logger = logging.NewConsoleLogger()
	err := sentinel.InitWithConfig(conf)
	if err != nil {
		log.Fatal(err)
	}

	_, err = flow.LoadRules([]*flow.Rule{
		{
			Resource:               "some-test",
			TokenCalculateStrategy: flow.Direct,
			ControlBehavior:        flow.Throttling,
			Threshold:              10,
			StatIntervalInMs:       1000,
		},
	})
	if err != nil {
		log.Fatalf("Unexpected error: %+v", err)
		return
	}

	start := time.Now()

	time.Sleep(time.Second)
	batch := uint32(10)
	_, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound), sentinel.WithBatchCount(batch))
	if b == nil {
		fmt.Printf("time: %.2f, batch: %d, result: %s\n", time.Since(start).Seconds(), batch, "pass")
	} else {
		fmt.Printf("time: %.2f, batch: %d, result: %s\n", time.Since(start).Seconds(), batch, "block")
	}

	for i := 0; i < 9; i++ {
		time.Sleep(time.Millisecond * 100)
		batch = 1
		_, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound), sentinel.WithBatchCount(batch))
		if b == nil {
			fmt.Printf("time: %.2f, batch: %d, result: %s\n", time.Since(start).Seconds(), batch, "pass")
		} else {
			fmt.Printf("time: %.2f, batch: %d, result: %s\n", time.Since(start).Seconds(), batch, "block")
		}
	}
}

Run result:

time: 1.00, batch: 10, result: pass
time: 1.10, batch: 1, result: pass
time: 1.20, batch: 1, result: pass
time: 1.30, batch: 1, result: pass
time: 1.40, batch: 1, result: pass
time: 1.50, batch: 1, result: pass
time: 1.60, batch: 1, result: pass
time: 1.70, batch: 1, result: pass
time: 1.80, batch: 1, result: pass
time: 1.90, batch: 1, result: pass

Need feature

More detail control of throttling is needed. The flow rule now only have Threshold and StateIntervalMs to control throttling behavior, which may be not enough, and may cause incorrect usage. Maybe a burst param is needed to control burst traffic.

More discuss is welcomed.

Additional context

Add any other context or screenshots about the feature request here.

Metadata

Metadata

Labels

area/flow-controlIssues or PRs related to flow controlkind/enhancementCategory issues or PRs related to enhancement

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions