The author | in the rain the apache/dubbo – go project director

The author of this article is the principal of Apache/Dubbo-Go project. Sentinel-go has been built into the DubboGO project. If you want to use sentinel in Dubbo-Go alone, please refer to the article Using Sentinel in Dubbo-Go. If you have any other questions, please go to dubbogo community [Nail group 23331795] to communicate.

This paper mainly analyzes alibaba group open source flow control middleware Sentinel, its native support Java/Go/C++ and other languages, this paper only analyzes the Go language implementation. Sentinel refers to sentinel-go unless otherwise noted below.

1 Basic Concepts Resource and Rule

1.1 the Resource

	// ResourceType represents classification of the resources
	type ResourceType int32

	const (
		ResTypeCommon ResourceType = iota
		ResTypeWeb
		ResTypeRPC
	)

	// TrafficType describes the traffic type: Inbound or Outbound
	type TrafficType int32

	const (
		// Inbound represents the inbound traffic (e.g. provider)
		Inbound TrafficType = iota
		// Outbound represents the outbound traffic (e.g. consumer)
		Outbound
	)

	// ResourceWrapper represents the invocation
	type ResourceWrapper struct {
		// global unique resource name
		name string
		// resource classification
		classification ResourceType
		// Inbound or Outbound
		flowType TrafficType
	}
Copy the code

Resource(ResourceWrapper) stores the ResourceType of the application scenario and FlowType(TrafficType) of the destination flow control.

1.2 Entry

	// EntryOptions represents the options of a Sentinel resource entry.
	type EntryOptions struct {
		resourceType base.ResourceType
		entryType    base.TrafficType
		acquireCount uint32
		slotChain    *base.SlotChain
	}

	type EntryContext struct {
		entry *SentinelEntry

		// Use to calculate RT
		startTime uint64

		Resource *ResourceWrapper
		StatNode StatNode

		Input *SentinelInput
		// the result of rule slots check
		RuleCheckResult *TokenResult
	}

	type SentinelEntry struct {
		res *ResourceWrapper
		// one entry bounds with one context
		ctx *EntryContext

		sc *SlotChain
	}
Copy the code

Entry entity SentinelEntry is associated with Resource(ResourceWrapper) and its flow control rule set, SlotChain. Each Entry entity has an EntryContext, which stores flow control parameters and flow control decision results used in each Rule detection.

It’s worth noting that the sentinelEntry. sc value comes from entryOptions. slotChain, Entryoptions. slotChain stores the globalSlotChain API/slot_chain-go :globalSlotChain.

SlotChain is a collection of all flow control components provided by Sentinel. Each flow control component can be simply considered as a Slot, and detailed analysis is shown in [3.5 SlotChain].

Sentinel some variables and functions named the readability of the poor, such as EntryOptions. AcquireCount can’t let a person means, seen function core/API. Go: WithAcquireCount () comments to understand: EntryOptions acquireCount is mass action execution times. If some one RPC request calls the service side a service interface, the value 1 is EntryOptions. AcquireCount default values, 】 if the call three server-side service interface, the value of 3. Therefore, it is recommended to change the name to entryOptions. batchCount. Considering the principle of minimum change, May be kept in the core/API. Go: WithAcquireCount () at the same time increase a same function of the core/API. Go: WithBatchCount () interface. Improvements have been submitted to PR 263.

Rule 1.3

	type TokenCalculateStrategy int32
	const (
		Direct TokenCalculateStrategy = iota
		WarmUp
	)

	type ControlBehavior int32
	const (
		Reject ControlBehavior = iota
		Throttling
	)

	// Rule describes the strategy of flow control, the flow control strategy is based on QPS statistic metric
	type Rule struct {
		// Resource represents the resource name.
		Resource               string                 `json:"resource"`
		ControlBehavior        ControlBehavior        `json:"controlBehavior"`
		// Threshold means the threshold during StatIntervalInMs
		// If StatIntervalInMs is 1000(1 second), Threshold means QPS
		Threshold         float64          `json:"threshold"`
		MaxQueueingTimeMs uint32           `json:"maxQueueingTimeMs"`
		// 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"`
	}
Copy the code

Rule Records the Threshold, StatIntervalInMs, and ControlBehavior of traffic limiting for a Resource.

The core is RuleCheckSlot, the Rule interface, while StatSlot is used to count the run metrics of Sentinel itself.

1.4 the Flow

The current chapter mainly analyzes the flow limiting (core/flow) in flow control and combs the sentinel overall framework according to the flow control processing process.

1.4.1 TrafficShapingController

The TrafficShapingController, as the name implies, is the flow shaping controller, which is the concrete implementor of flow control.

	// core/flow/traffic_shaping.go

	// TrafficShapingCalculator calculates the actual traffic shaping threshold
	// based on the threshold of rule and the traffic shaping strategy.
	type TrafficShapingCalculator interface {
		CalculateAllowedTokens(acquireCount uint32, flag int32) float64
	}

	type DirectTrafficShapingCalculator struct {
		threshold float64
	}

	func (d *DirectTrafficShapingCalculator) CalculateAllowedTokens(uint32.int32) float64 {
		return d.threshold
	}
Copy the code

The TrafficShapingCalculator interface is used to calculate the upper limit of traffic limiting, and its implementation can be avoided without using the warm-up function. One of the entities DirectTrafficShapingCalculator return Rule. The Threshold [user setting the maximum current limit].

	// TrafficShapingChecker performs checking according to current metrics and the traffic
	// shaping strategy, then yield the token result.
	type TrafficShapingChecker interface {
		DoCheck(resStat base.StatNode, acquireCount uint32, threshold float64) *base.TokenResult
	}

	type RejectTrafficShapingChecker struct {
		rule  *Rule
	}

	func (d *RejectTrafficShapingChecker) DoCheck(resStat base.StatNode, acquireCount uint32, threshold float64) *base.TokenResult {
		metricReadonlyStat := d.BoundOwner().boundStat.readOnlyMetric
		if metricReadonlyStat == nil {
			return nil
		}
		curCount := float64(metricReadonlyStat.GetSum(base.MetricEventPass))
		if curCount+float64(acquireCount) > threshold {
			return base.NewTokenResultBlockedWithCause(base.BlockTypeFlow, "", d.rule, curCount)
		}
		return nil
	}
Copy the code

RejectTrafficShapingChecker according to the Rule. The Threshold to determine whether the Resource in the current time window overrun, the current limiting results TokenResultStatus can only be Pass or Blocked.

Sentinel Flow also has a constant speed ThrottlingChecker, whose purpose is to make the request be executed at a constant speed. A time window (such as 1s) is subdivided into finer micro-time Windows according to the threshold, and the request can be executed at most once in each micro-time window. The result TokenResultStatus can only be Pass, Blocked or Wait, and their relevant meanings are as follows:

  • Pass: the request is passed without exceeding the limit in the micro time window;
  • Wait: the request is delayed by a number of time Windows when the time limit is exceeded.
  • Blocked: The request was rejected when the virtual drive exceeded the limit in the micro-time window and the wait time exceeded the maximum willing wait time set by the user [rule-maxqueueingtimems].
	type TrafficShapingController struct {
		flowCalculator TrafficShapingCalculator
		flowChecker    TrafficShapingChecker

		rule *Rule
		// boundStat is the statistic of current TrafficShapingController
		boundStat standaloneStatistic
	}

	func (t *TrafficShapingController) PerformChecking(acquireCount uint32, flag int32) *base.TokenResult {
		allowedTokens := t.flowCalculator.CalculateAllowedTokens(acquireCount, flag)
		return t.flowChecker.DoCheck(resStat, acquireCount, allowedTokens)
	}
Copy the code

In Direct + Reject current-limiting scenarios, the three interfaces are not make much sense, its core function TrafficShapingController. PerformChecking () is the main process of:

  • 1 from TrafficShapingController. Gets the current value of the Resource metrics boundStat “curCount”;
  • If curCount + batchNum(acquichnum) > rule-pass, reject if curCount + batchNum(acquichnum) > rule-pass, reject

In traffic limiting scenarios, the four members of TrafficShapingController are defined as follows:

  • FlowCalculator calculates the upper limit of current limit;
  • FlowChecker performs flow limiting Check.
  • Rule Stores traffic limiting rules.
  • BoundStat stores the Check result and time window parameters for determining the next traffic limiting Check action.

1.4.2 TrafficControllerMap

When traffic limiting is performed, you need to obtain the corresponding TrafficShapingController based on the Resource name.

   // TrafficControllerMap represents the map storage for TrafficShapingController.
   type TrafficControllerMap map[string][]*TrafficShapingController
	// core/flow/rule_manager.go
	tcMap        = make(TrafficControllerMap)
Copy the code

The package level global private tcMap variable stores all the rules, with the key being the Resource name and the value being the TrafficShapingController corresponding to the Resource.

The user-level interface function core/flow/ RUle_manager.go :LoadRules() constructs its corresponding TrafficShapingController according to the user-defined Rule and stores it in tcMap. The interface function call generateStatFor (* Rule) tectonic TrafficShapingController. BoundStat.

The core code of generateStatFor(*Rule) is as follows:

	func generateStatFor(rule *Rule) (*standaloneStatistic, error) {
		resNode = stat.GetOrCreateResourceNode(rule.Resource, base.ResTypeCommon)

		// default case, use the resource's default statistic
		readStat := resNode.DefaultMetric()
		retStat.reuseResourceStat = true
		retStat.readOnlyMetric = readStat
		retStat.writeOnlyMetric = nil
		return &retStat, nil
	}
Copy the code

2 Metrics

Resource Metrics are the basis for Rule determination.

2.1 AtomicBucketWrapArray

Sentinel library is rich in functions, but its storage base is sliding time window, whether limiting current or fusing. There are a number of optimizations involved: such as locking long rounds.

There are many kinds of sliding window implementation, time wheel algorithm is one of the relatively simple implementation, on the time wheel algorithm can achieve a variety of current limiting methods. The overall block diagram of the time wheel is as follows:

1 BucketWrap

The most basic unit of a time wheel is a bucket.

	// BucketWrap represent a slot to record metrics
	// In order to reduce the usage of memory, BucketWrap don't hold length of BucketWrap
	// The length of BucketWrap could be seen in LeapArray.
	// The scope of time is [startTime, startTime+bucketLength)
	// The size of BucketWrap is 24(8+16) bytes
	type BucketWrap struct {
		// The start timestamp of this statistic bucket wrapper.
		BucketStart uint64
		// The actual data structure to record the metrics (e.g. MetricBucket).
		Value atomic.Value
	}
Copy the code

Note: Pointers are used here because bucketwrap-based AtomicBucketWrapArray can be used by multiple Sentinel flow control components, each of which has different flow control parameters. For example:

  • 1 core/circuitbreaker/circuit_breaker.go:slowRtCircuitBreakerThe use ofslowRequestLeapArrayUnderlying parameters ofslowRequestCounter
      // core/circuitbreaker/circuit_breaker.go
	type slowRequestCounter struct {
		slowCount  uint64
		totalCount uint64
	}
Copy the code
  • 2 core/circuitbreaker/circuit_breaker.go:errorRatioCircuitBreakerThe use oferrorCounterLeapArrayUnderlying parameters oferrorCounter
    // core/circuitbreaker/circuit_breaker.go
	type errorCounter struct {
		errorCount uint64
		totalCount uint64
	}
Copy the code

1.1 MetricBucket

BucketWrap can be thought of as a time bucket template. The specific bucket entity is MetricsBucket, which is defined as follows:

	// MetricBucket represents the entity to record metrics per minimum time unit (i.e. the bucket time span).
	// Note that all operations of the MetricBucket are required to be thread-safe.
	type MetricBucket struct {
		// Value of statistic
		counter [base.MetricEventTotal]int64
		minRt   int64
	}
Copy the code

MetricBucket stores five types of metrics:

	// There are five events to record
	// pass + block == Total
	const (
		// sentinel rules check pass
		MetricEventPass MetricEvent = iota
		// sentinel rules check block
		MetricEventBlock

		MetricEventComplete
		// Biz error, used for circuit breaker
		MetricEventError
		// request execute rt, unit is millisecond
		MetricEventRt
		// hack for the number of event
		MetricEventTotal
	)
Copy the code

2 AtomicBucketWrapArray

Only the start time and metric value are recorded for each bucket. The common time window length for each bucket is recorded in the AtomicBucketWrapArray, which is defined as follows:

	// atomic BucketWrap array to resolve race condition
	// AtomicBucketWrapArray can not append or delete element after initializing
	type AtomicBucketWrapArray struct {
		// The base address for real data array
		base unsafe.Pointer
		// The length of slice(array), it can not be modified.
		length int
		data   []*BucketWrap
	}
Copy the code

AtomicBucketWrapArray. The base value is AtomicBucketWrapArray. Data slice data area of the pointer to the first. Because AtomicBucketWrapArray. The data is a fixed length of slice, so AtomicBucketWrapArray. Base stored data memory area first address directly, in order to accelerate the access speed.

Second, AtomicBucketWrapArray. The data is stored in the BucketWrap pointer, rather than BucketWrap.

NewAtomicBucketWrapArrayWithTime () function will preheat, generate out all the time bucket.

2.2 time round

1 leapArray

	// Give a diagram to illustrate
	// Suppose current time is 888, bucketLengthInMs is 200ms,
	// intervalInMs is 1000ms, LeapArray will build the below windows
	// B0 B1 B2 B3 B4
	/ / |... |... |... |... |... |
	// 1000 1200 1400 1600 800 (1000)
	/ / ^
	// time=888
	type LeapArray struct {
		bucketLengthInMs uint32
		sampleCount      uint32
		intervalInMs     uint32
		array            *AtomicBucketWrapArray
		// update lock
		updateLock mutex
	}
Copy the code

LeapArray members:

  • BucketLengthInMs is the length of a leaky bucket, in milliseconds.
  • SampleCount is the number of lost buckets.
  • IntervalInMs is the length of the time window in milliseconds.

The ASCII diagrams in its annotations do a good job of explaining what each field means.

LeapArray core function is LeapArray currentBucketOfTime (), its effect is based on a certain point in time to obtain the corresponding time bucket BucketWrap, code is as follows:

	func (la *LeapArray) currentBucketOfTime(now uint64, bg BucketGenerator) (*BucketWrap, error) {
		if now <= 0 {
			return nil, errors.New("Current time is less than 0.")
		}

		idx := la.calculateTimeIdx(now)
		bucketStart := calculateStartTime(now, la.bucketLengthInMs)

		for { //spin to get the current BucketWrap
			old := la.array.get(idx)
			if old == nil {
				// because la.array.data had initiated when new la.array
				// theoretically, here is not reachable
				newWrap := &BucketWrap{
					BucketStart: bucketStart,
					Value:       atomic.Value{},
				}
				newWrap.Value.Store(bg.NewEmptyBucket())
				if la.array.compareAndSet(idx, nil, newWrap) {
					return newWrap, nil
				} else {
					runtime.Gosched()
				}
			} else if bucketStart == atomic.LoadUint64(&old.BucketStart) {
				return old, nil
			} else if bucketStart > atomic.LoadUint64(&old.BucketStart) {
				// current time has been next cycle of LeapArray and LeapArray dont't count in last cycle.
				// reset BucketWrap
				if la.updateLock.TryLock() {
					old = bg.ResetBucketTo(old, bucketStart)
					la.updateLock.Unlock()
					return old, nil
				} else {
					runtime.Gosched()
				}
			} else if bucketStart < atomic.LoadUint64(&old.BucketStart) {
				// TODO: reserve for some special case (e.g. when occupying "future" buckets).
				return nil, errors.New(fmt.Sprintf("Provided time timeMillis=%d is already behind old.BucketStart=%d.", bucketStart, old.BucketStart))
			}
		}
	}
Copy the code

Its for-loop core logic is:

  • 1 Obtain the time bucket old corresponding to the time point;
  • 2. If old is empty, create a time bucket and try to save it to the time wheel of the time window by atomic operation. If saving fails, try again.
  • 3 If old is the bucket of the current point in time, return.
  • 4 If the start time of old is smaller than the current time, an optimistic lock is used to try the start time of reset bucket. If the lock is updated successfully, the system returns.
  • 5 If the starting point of the old time is larger than the current time, the system distorts time and returns an error.

2 BucketLeapArray

LeapArray implements all the bodies of the sliding time window and its interface for external use is BucketLeapArray:

	// The implementation of sliding window based on LeapArray (as the sliding window infrastructure)
	// and MetricBucket (as the data type). The MetricBucket is used to record statistic
	// metrics per minimum time unit (i.e. the bucket time span).
	type BucketLeapArray struct {
		data     LeapArray
		dataType string
	}
Copy the code

As you can see from the comment on this struct, the entity of the BucketWrap time window is MetricBucket.

2.3 Metric Data Reading and writing

SlidingWindowMetric

	// SlidingWindowMetric represents the sliding window metric wrapper.
	// It does not store any data and is the wrapper of BucketLeapArray to adapt to different internal bucket
	// SlidingWindowMetric is used for SentinelRules and BucketLeapArray is used for monitor
	// BucketLeapArray is per resource, and SlidingWindowMetric support only read operation.
	type SlidingWindowMetric struct {
		bucketLengthInMs uint32
		sampleCount      uint32
		intervalInMs     uint32
		real             *BucketLeapArray
	}
Copy the code

SlidingWindowMetric is an encapsulation of BucketLeapArray and provides only a read-only interface.

ResourceNode

	type BaseStatNode struct {
		sampleCount uint32
		intervalMs  uint32

		goroutineNum int32

		arr    *sbase.BucketLeapArray
		metric *sbase.SlidingWindowMetric
	}

	type ResourceNode struct {
		BaseStatNode

		resourceName string
		resourceType base.ResourceType
	}

	// core/stat/node_storage.go
	type ResourceNodeMap map[string]*ResourceNode
	var (
		inboundNode = NewResourceNode(base.TotalInBoundResourceName, base.ResTypeCommon)

		resNodeMap = make(ResourceNodeMap)
		rnsMux     = new(sync.RWMutex)
	)
Copy the code

BaseStatNode provides a read/write interface. Data is written to basestatNode. arr and the read interface depends on basestatNode. metric. Basestatnode.arr is created in NewBaseStatNode(), to which the pointer slidingWindowmetric.real also points.

ResourceNode is just as its name implies, it represents a resource and its Metrics storage ResourceNode BaseStatNode.

The global variable resNodeMap stores Metrics data for all resources.

3 Flow limiting process

This section only analyzes the most basic traffic shaping function provided by Sentinel library — traffic limiting. There are various traffic limiting algorithms. You can use the built-in algorithms or expand them by yourself.

The current limiting process has three steps:

  • 1. Construct an EntryContext for a specific Resource and store Metrics, start time of limiting, etc. Sentinel is called StatPrepareSlot;
  • 2 determine whether Resource should perform traffic limiting according to its traffic limiting algorithm, and give the traffic limiting decision result, which is called RuleCheckSlot for Sentinel.
    • Addendum: The SlotChain algorithm is a series of judgment methods;
  • 3. After the decision, in addition to executing the corresponding action according to the decision result, Sentinel also needs to execute its own action according to the decision result and store the time RT and other indicators used in the whole decision process, which is called StatSlot by Sentinel.

The overall process is shown in the figure below:

3.1 Slot.

For the three steps of Check, three slots are defined as follows:

	// StatPrepareSlot is responsible for some preparation before statistic
	// For example: init structure and so on
	type StatPrepareSlot interface {
		// Prepare function do some initialization
		// Such as: init statistic structure, node and etc
		// The result of preparing would store in EntryContext
		// All StatPrepareSlots execute in sequence
		// Prepare function should not throw panic.
		Prepare(ctx *EntryContext)
	}

	// RuleCheckSlot is rule based checking strategy
	// All checking rule must implement this interface.
	type RuleCheckSlot interface {
		// Check function do some validation
		// It can break off the slot pipeline
		// Each TokenResult will return check result
		// The upper logic will control pipeline according to SlotResult.
		Check(ctx *EntryContext) *TokenResult
	}

	// StatSlot is responsible for counting all custom biz metrics.
	// StatSlot would not handle any panic, and pass up all panic to slot chain
	type StatSlot interface {
		// OnEntryPass function will be invoked when StatPrepareSlots and RuleCheckSlots execute pass
		// StatSlots will do some statistic logic, such as QPS, log, etc
		OnEntryPassed(ctx *EntryContext)
		// OnEntryBlocked function will be invoked when StatPrepareSlots and RuleCheckSlots fail to execute
		// It may be inbound flow control or outbound cir
		// StatSlots will do some statistic logic, such as QPS, log, etc
		// blockError introduce the block detail
		OnEntryBlocked(ctx *EntryContext, blockError *BlockError)
		// OnCompleted function will be invoked when chain exits.
		// The semantics of OnCompleted is the entry passed and completed
		// Note: blocked entry will not call this function
		OnCompleted(ctx *EntryContext)
	}
Copy the code

Regardless of Prepare and Stat, we can simply assume that the slot is a flow control component provided by Sentinel.

It is worth noting that the statslot.onCompleted annotation is executed only if rulecheckslot.check passes and is used to calculate Metrics such as RT used from start to end of the request.

3.2 Prepare

	// core/base/slot_chain.go
	// StatPrepareSlot is responsible for some preparation before statistic
	// For example: init structure and so on
	type StatPrepareSlot interface {
		// Prepare function do some initialization
		// Such as: init statistic structure, node and etc
		// The result of preparing would store in EntryContext
		// All StatPrepareSlots execute in sequence
		// Prepare function should not throw panic.
		Prepare(ctx *EntryContext)
	}

	// core/stat/stat_prepare_slot.go
	type ResourceNodePrepareSlot struct{}func (s *ResourceNodePrepareSlot) Prepare(ctx *base.EntryContext) {
		node := GetOrCreateResourceNode(ctx.Resource.Name(), ctx.Resource.Classification())
		// Set the resource node to the context.
		ctx.StatNode = node
	}
Copy the code

As explained earlier, Prepare primarily constructs the ResourceNode used to store Resource Metrics. All Resource statnodes are stored in package level global variables core/stat/node_storage.go:resNodeMap [type: Map [string]*ResourceNode GetOrCreateResourceNode GetOrCreateResourceNode GetOrCreateResourceNode GetOrCreateResourceNode GetOrCreateResourceNode GetOrCreateResourceNode If not, create a StatNode and store it to resNodeMap.

3.3 Check

Rulecheckslot.check ()

  • 1 Obtain all Rule sets of Resource based on the Resource name.
  • 2 Traverse the Rule set and run Check for the resources in sequence. If any of the rules determines that the Resource needs to be Blocked, the system returns the Check. Otherwise, the system releases the Check.
	type Slot struct{}func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult {
		res := ctx.Resource.Name()
		tcs := getTrafficControllerListFor(res)
		result := ctx.RuleCheckResult

		// Check rules in order
		for _, tc := range tcs {
			r := canPassCheck(tc, ctx.StatNode, ctx.Input.AcquireCount)
			if r == nil {
				// nil means pass
				continue
			}
			if r.Status() == base.ResultStatusBlocked {
				return r
			}
			if r.Status() == base.ResultStatusShouldWait {
				if waitMs := r.WaitMs(); waitMs > 0 {
					// Handle waiting action.
					time.Sleep(time.Duration(waitMs) * time.Millisecond)
				}
				continue}}return result
	}

	func canPassCheck(tc *TrafficShapingController, node base.StatNode, acquireCount uint32) *base.TokenResult {
		return canPassCheckWithFlag(tc, node, acquireCount, 0)}func canPassCheckWithFlag(tc *TrafficShapingController, node base.StatNode, acquireCount uint32, flag int32) *base.TokenResult {
		return checkInLocal(tc, node, acquireCount, flag)
	}

	func checkInLocal(tc *TrafficShapingController, resStat base.StatNode, acquireCount uint32, flag int32) *base.TokenResult {
		return tc.PerformChecking(resStat, acquireCount, flag)
	}
Copy the code

3.4 the Exit

The logical execution sequence of sentinel is as follows:

  • 1 If rulecheckslot.check () passes, execute statslot.onentrypassed (). Otherwise, rulecheckslot.check () reject executes statslot.onentryblocked ();
  • 2 If rulecheckslot.check () passes, this Action is executed.
  • 3 If rulecheckslot.check () passes, execute sentinelentry.exit () –> slotchain.ext () –> statslot.oncompleted ().

The invocation link of the third step is as follows:

StatSlot.OnCompleted()

	// core/flow/standalone_stat_slot.go
	type StandaloneStatSlot struct{}func (s StandaloneStatSlot) OnEntryPassed(ctx *base.EntryContext) {
		res := ctx.Resource.Name()
		for _, tc := range getTrafficControllerListFor(res) {
			if! tc.boundStat.reuseResourceStat {iftc.boundStat.writeOnlyMetric ! =nil {
					tc.boundStat.writeOnlyMetric.AddCount(base.MetricEventPass, int64(ctx.Input.AcquireCount))
				}
			}
		}
	}

	func (s StandaloneStatSlot) OnEntryBlocked(ctx *base.EntryContext, blockError *base.BlockError) {
		// Do nothing
	}

	func (s StandaloneStatSlot) OnCompleted(ctx *base.EntryContext) {
		// Do nothing
	}
Copy the code

SlotChain.exit()

	// core/base/slot_chain.go
	type SlotChain struct{}func (sc *SlotChain) exit(ctx *EntryContext) {
		// The OnCompleted is called only when entry passed
		if ctx.IsBlocked() {
			return
		}
		for _, s := range sc.stats {
			s.OnCompleted(ctx)
		}
	}
Copy the code

SentinelEntry.Exit()

	// core/base/entry.go
	type SentinelEntry struct {
		sc *SlotChain
		exitCtl sync.Once
	}

	func (e *SentinelEntry) Exit(a) {
		e.exitCtl.Do(func(a) {
			ife.sc ! =nil {
				e.sc.exit(ctx)
			}
		})
	}
Copy the code

As you can see from above, statslot.oncompleted () is called after the Action (such as an RPC request-response Invokation) has completed. If a component needs to calculate the elapsed time for an Action, calculate the elapsed time according to entryContext.startTime in its corresponding statslot.onCompleted ().

3.5 SlotChain

Sentinel is essentially a flow control package, which not only provides the flow limiting function, but also provides many other functional flow control components such as adaptive flow protection, fusing degradation, cold start, global flow Metrics results, etc. Sentinel-go package defines a SlotChain entity to store all its flow control components.

   // core/base/slot_chain.go

	// SlotChain hold all system slots and customized slot.
	// SlotChain support plug-in slots developed by developer.
	type SlotChain struct {
		statPres   []StatPrepareSlot
		ruleChecks []RuleCheckSlot
		stats      []StatSlot
	}

	// The entrance of slot chain
	// Return the TokenResult and nil if internal panic.
	func (sc *SlotChain) Entry(ctx *EntryContext) *TokenResult {
		// execute prepare slot
		sps := sc.statPres
		if len(sps) > 0 {
			for _, s := range sps {
				s.Prepare(ctx)
			}
		}

		// execute rule based checking slot
		rcs := sc.ruleChecks
		var ruleCheckRet *TokenResult
		if len(rcs) > 0 {
			for _, s := range rcs {
				sr := s.Check(ctx)
				if sr == nil {
					// nil equals to check pass
					continue
				}
				// check slot result
				if sr.IsBlocked() {
					ruleCheckRet = sr
					break}}}if ruleCheckRet == nil {
			ctx.RuleCheckResult.ResetToPass()
		} else {
			ctx.RuleCheckResult = ruleCheckRet
		}

		// execute statistic slot
		ss := sc.stats
		ruleCheckRet = ctx.RuleCheckResult
		if len(ss) > 0 {
			for _, s := range ss {
				// indicate the result of rule based checking slot.
				if! ruleCheckRet.IsBlocked() { s.OnEntryPassed(ctx) }else {
					// The block error should not be nil.
					s.OnEntryBlocked(ctx, ruleCheckRet.blockErr)
				}
			}
		}
		return ruleCheckRet
	}

	func (sc *SlotChain) exit(ctx *EntryContext) {
		if ctx == nil || ctx.Entry() == nil {
			logging.Error(errors.New("nil EntryContext or SentinelEntry"), "")
			return
		}
		// The OnCompleted is called only when entry passed
		if ctx.IsBlocked() {
			return
		}
		for _, s := range sc.stats {
			s.OnCompleted(ctx)
		}
		// relieve the context here
	}
Copy the code

Suggestion: The Sentinel package does not know exactly which component is being used for a Resource. At run time, the Sentinel package performs Rule for all components in sequence against the EntryContext of a Resource. Why doesn’t Sentinel-Golang provide users with an interface to set the set of flow control components to use to reduce rulecheckslot.check () execution in slotchain-entry ()? Related improvements have been submitted to PR 264 [added, codes have been merged, and according to the owner’s pressure test, sentinel-Go efficiency has been improved by 15% overall].

globalSlotChain

Sentinel-go defines a SlotChain package level global private variable globalSlotChain to store all of its flow control component objects. The following is an example of related code. Because this article focuses only on stream limiting components, only the registration code for stream limiting components is presented below.

   // api/slot_chain.go

	func BuildDefaultSlotChain(a) *base.SlotChain {
		sc := base.NewSlotChain()
		sc.AddStatPrepareSlotLast(&stat.ResourceNodePrepareSlot{})

		sc.AddRuleCheckSlotLast(&flow.Slot{})

		sc.AddStatSlotLast(&flow.StandaloneStatSlot{})

		return sc
	}

	var globalSlotChain = BuildDefaultSlotChain()
Copy the code

Entry

In the sentinel-Go API /api.go:Entry(), the most important Entry function outside of Sentinel-Go, globalSlotChain is used as the SlotChain parameter of EntryOptions.

	// api/api.go

	// Entry is the basic API of Sentinel.
	func Entry(resource string, opts ... EntryOption) (*base.SentinelEntry, *base.BlockError) {
		options := entryOptsPool.Get().(*EntryOptions)
		options.slotChain = globalSlotChain

		return entry(resource, options)
	}
Copy the code

The evolution of Sentinel is inseparable from the contributions of the community. Sentinel Go 1.0 GA release is coming soon, bringing more cloud native related features. Interested developers are welcome to contribute and help lead the evolution of future releases. We encourage contributions of any kind, including but not limited to:

• Bug fixes • New features/ Improvements • Dashboard • Document /website • Test cases

Developers can select issues of interest from the Good First Issues list on GitHub to participate in discussions and contribute. We focus on developers who are actively contributing, and core contributors are nominated as Committers to lead the community. We also welcome any questions or suggestions you may have, via GitHub Issue, Gitter, or the Stubble Group (30150716). Now start hacking!

• Sentinel Go Repo: github.com/alibaba/sen… • Corporate users are welcome to register: github.com/alibaba/Sen…

Author’s brief introduction

Yu (Github @AlexStocks), apache/ Dubbo-Go project leader, a programmer with more than 10 years of experience in server-side infrastructure development, is currently working on container Choreography and Service Mesh in the Trusted Native Department of Ant Financial. I am passionate about open source. Since 2015, I have contributed code to Redis, and have successively improved well-known projects such as Muduo/Pika/Dubbo/ Dubbo-Go.

“Alibaba Cloud originator focuses on micro-service, Serverless, container, Service Mesh and other technical fields, focuses on the trend of cloud native popular technology, large-scale implementation of cloud native practice, and becomes the public account that most understands cloud native developers.”