Understanding Sentinel-Go: Core Concepts, Flow Control Architecture, and Implementation Details
This article provides an in‑depth analysis of Sentinel‑Go, covering its fundamental concepts such as Resource and Rule, the Entry and SlotChain mechanisms, flow‑control components like TrafficShapingController, metric storage structures including BucketWrap and LeapArray, and practical code examples illustrating the library’s internal workflow.
Overview
Sentinel‑Go is the Go implementation of the open‑source traffic‑control middleware Sentinel. It provides a resource‑based flow‑control mechanism built on a slot‑chain pipeline and a sliding‑window metric system.
Core Types
Resource
A resource is represented by ResourceWrapper, which stores a unique name, a ResourceType (common, web, RPC) and a TrafficType (inbound/provider or outbound/consumer).
type ResourceType int32
const (
ResTypeCommon ResourceType = iota
ResTypeWeb
ResTypeRPC
)
type TrafficType int32
const (
Inbound TrafficType = iota // provider side
Outbound // consumer side
)
type ResourceWrapper struct {
name string
classification ResourceType
flowType TrafficType
}Rule
A Rule describes the flow‑control strategy for a resource: threshold (QPS), control behavior (reject or throttling), statistic interval and maximum queueing time.
type ControlBehavior int32
const (
Reject ControlBehavior = iota
Throttling
)
type Rule struct {
Resource string `json:"resource"`
ControlBehavior ControlBehavior `json:"controlBehavior"`
Threshold float64 `json:"threshold"` // QPS when StatIntervalInMs = 1000
MaxQueueingTimeMs uint32 `json:"maxQueueingTimeMs"`
StatIntervalInMs uint32 `json:"statIntervalInMs"`
}Entry and Context
An Entry binds a ResourceWrapper with a SlotChain. The EntryContext holds the resource, a StatNode for metrics, input parameters and the result of rule checks.
type EntryOptions struct {
resourceType base.ResourceType
entryType base.TrafficType
acquireCount uint32
slotChain *base.SlotChain
}
type EntryContext struct {
entry *SentinelEntry
startTime uint64
Resource *ResourceWrapper
StatNode StatNode
Input *SentinelInput
RuleCheckResult *TokenResult
}Flow‑Control Components
TrafficShapingController
The controller combines a TrafficShapingCalculator (calculates allowed tokens) and a TrafficShapingChecker (performs the actual check).
type TrafficShapingCalculator interface {
CalculateAllowedTokens(acquireCount uint32, flag int32) float64
}
type DirectTrafficShapingCalculator struct {
threshold float64
}
func (d *DirectTrafficShapingCalculator) CalculateAllowedTokens(uint32, int32) float64 {
return d.threshold
}
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 {
curCount := float64(resStat.GetSum(base.MetricEventPass))
if curCount+float64(acquireCount) > threshold {
return base.NewTokenResultBlockedWithCause(base.BlockTypeFlow, "", d.rule, curCount)
}
return nil
}The controller’s PerformChecking obtains the current metric, calculates allowed tokens and delegates to the checker.
type TrafficShapingController struct {
flowCalculator TrafficShapingCalculator
flowChecker TrafficShapingChecker
rule *Rule
boundStat standaloneStatistic
}
func (t *TrafficShapingController) PerformChecking(acquireCount uint32, flag int32) *base.TokenResult {
allowedTokens := t.flowCalculator.CalculateAllowedTokens(acquireCount, flag)
return t.flowChecker.DoCheck(t.boundStat.readOnlyMetric, acquireCount, allowedTokens)
}TrafficControllerMap
All controllers are stored in a map keyed by resource name.
type TrafficControllerMap map[string][]*TrafficShapingController
var tcMap = make(TrafficControllerMap)When LoadRules() is called, a TrafficShapingController is created for each rule and registered in tcMap.
Sliding‑Window Metrics
Metrics are kept in a time‑wheel built from BucketWrap (start timestamp + atomic MetricBucket), AtomicBucketWrapArray and LeapArray. The smallest unit is a bucket; a LeapArray holds a fixed number of buckets and provides currentBucketOfTime to locate the bucket for a given timestamp.
type BucketWrap struct {
BucketStart uint64
Value atomic.Value // holds *MetricBucket
}
type MetricBucket struct {
counter [base.MetricEventTotal]int64
minRt int64
}
type AtomicBucketWrapArray struct {
base unsafe.Pointer
length int
data []*BucketWrap
}
type LeapArray struct {
bucketLengthInMs uint32
sampleCount uint32
intervalInMs uint32
array *AtomicBucketWrapArray
updateLock mutex
}
func (la *LeapArray) currentBucketOfTime(now uint64, bg BucketGenerator) (*BucketWrap, error) {
// core loop that creates or resets buckets atomically
}
type SlidingWindowMetric struct {
bucketLengthInMs uint32
sampleCount uint32
intervalInMs uint32
real *BucketLeapArray
}Slot Chain Architecture
The processing pipeline consists of three slot categories:
StatPrepareSlot : creates or fetches the ResourceNode and stores it in the context.
RuleCheckSlot : iterates over all rules for the resource and applies the corresponding TrafficShapingController.
StatSlot : records metrics after a pass or block and updates the sliding‑window counters.
During SlotChain.Entry() the slots are executed in the order prepare → rule check → statistic. After the business logic finishes, SlotChain.exit() invokes StatSlot.OnCompleted for each slot.
func (sc *SlotChain) Entry(ctx *EntryContext) *TokenResult {
for _, s := range sc.statPres { s.Prepare(ctx) }
for _, s := range sc.ruleChecks {
r := s.Check(ctx)
if r != nil && r.IsBlocked() {
ctx.RuleCheckResult = r
break
}
}
for _, s := range sc.stats {
if ctx.RuleCheckResult.IsBlocked() {
s.OnEntryBlocked(ctx, ctx.RuleCheckResult.blockErr)
} else {
s.OnEntryPassed(ctx)
}
}
return ctx.RuleCheckResult
}
func (sc *SlotChain) exit(ctx *EntryContext) {
if ctx.IsBlocked() { return }
for _, s := range sc.stats { s.OnCompleted(ctx) }
}The global variable globalSlotChain holds the default chain used by the public api.Entry() function.
func BuildDefaultSlotChain() *base.SlotChain {
sc := base.NewSlotChain()
sc.AddStatPrepareSlotLast(&stat.ResourceNodePrepareSlot{})
sc.AddRuleCheckSlotLast(&flow.Slot{})
sc.AddStatSlotLast(&flow.StandaloneStatSlot{})
return sc
}
var globalSlotChain = BuildDefaultSlotChain()Key Algorithms
Direct + Reject Flow
In the simplest case the controller uses DirectTrafficShapingCalculator (returns the rule threshold) and RejectTrafficShapingChecker. The check logic is:
Read the current pass count from the bound statistic.
If curCount + acquireCount > Rule.Threshold the request is blocked; otherwise it passes.
Throttling (Rate‑Limiting)
The ThrottlingChecker (not shown) splits a time window into micro‑buckets, allowing at most one request per micro‑bucket. The possible TokenResultStatus values are Pass, Wait (request is delayed) and Blocked (wait time exceeds Rule.MaxQueueingTimeMs).
Metric Collection Details
Each bucket records five event counters:
const (
MetricEventPass MetricEvent = iota
MetricEventBlock
MetricEventComplete
MetricEventError
MetricEventRt
MetricEventTotal
)The MetricBucket stores these counters and the minimum response time ( minRt). Buckets are managed by AtomicBucketWrapArray, which holds a fixed‑size slice of pointers to BucketWrap. The LeapArray calculates the bucket index and start time, creates new buckets atomically, and resets stale buckets when the time advances.
Load Rules and Statistic Nodes
core/flow/rule_manager.go:LoadRules()converts user‑defined Rule objects into TrafficShapingController instances, creates a standaloneStatistic (either reusing the resource’s default metric or allocating a dedicated one) and stores the controller in tcMap.
func generateStatFor(rule *Rule) (*standaloneStatistic, error) {
resNode := stat.GetOrCreateResourceNode(rule.Resource, base.ResTypeCommon)
readStat := resNode.DefaultMetric()
retStat.reuseResourceStat = true
retStat.readOnlyMetric = readStat
retStat.writeOnlyMetric = nil
return &retStat, nil
}Entry/Exit Flow
Typical usage:
entry, blockErr := sentinel.Entry("myResource")
if blockErr != nil {
// request is blocked
return
}
defer entry.Exit() // triggers SlotChain.exit()
// business logic hereCommunity Improvements
PR 263 introduced a configurable flow‑control component selection, reducing the number of rule checks and yielding ~15 % performance gain. Further refinements are tracked in PR 264.
PR 263: https://github.com/alibaba/sentinel-golang/pull/263
PR 264: https://github.com/alibaba/sentinel-golang/pull/264
References
Sentinel‑Go repository: https://github.com/alibaba/sentinel-golang
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Native
We publish cloud-native tech news, curate in-depth content, host regular events and live streams, and share Alibaba product and user case studies. Join us to explore and share the cloud-native insights you need.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
