阿里 双11 同款,流量防卫兵 Sentinel go 源码解读

简介: 本文主要分析阿里巴巴集团开源的流量控制中间件 Sentinel,其原生支持了 Java/Go/C++ 等多种语言,本文仅仅分析其 Go 语言实现。下文如无特殊说明,sentinel 指代 Sentinel-Go。

头图.png

作者 | 于雨  apache/dubbo-go 项目负责人

本文作者系 apache/dubbo-go 项目负责人,目前在 dubbogo 项目中已内置可用 sentinel-go,如果想单独使用可参考 在 dubbo-go 中使用 sentinel 一文,若有其他疑问可进 dubbogo社区【钉钉群 23331795】进行沟通。

导读:本文主要分析阿里巴巴集团开源的流量控制中间件 Sentinel,其原生支持了 Java/Go/C++ 等多种语言,本文仅仅分析其 Go 语言实现。下文如无特殊说明,sentinel 指代 Sentinel-Go。

1 基本概念 Resource  和 Rule

1.1 Resource

    // ResourceType represents classification of the resourcestype ResourceType int32const (ResTypeCommon ResourceType = iotaResTypeWebResTypeRPC)// TrafficType describes the traffic type: Inbound or Outboundtype TrafficType int32const (// Inbound represents the inbound traffic (e.g. provider)Inbound TrafficType = iota// Outbound represents the outbound traffic (e.g. consumer)Outbound)// ResourceWrapper represents the invocationtype ResourceWrapper struct {// global unique resource namename string// resource classificationclassification ResourceType// Inbound or OutboundflowType TrafficType}

Resource(ResourceWrapper) 存储了应用场景 ResourceType,以及目标流控的方向 FlowType(TrafficType)。

1.2 Entry

    // EntryOptions represents the options of a Sentinel resource entry.type EntryOptions struct {resourceType base.ResourceTypeentryType    base.TrafficTypeacquireCount uint32slotChain    *base.SlotChain}type EntryContext struct {entry *SentinelEntry// Use to calculate RTstartTime uint64Resource *ResourceWrapperStatNode StatNodeInput *SentinelInput// the result of rule slots checkRuleCheckResult *TokenResult}type SentinelEntry struct {res *ResourceWrapper// one entry bounds with one contextctx *EntryContextsc *SlotChain}

Entry 实体 SentinelEntry 关联了 Resource(ResourceWrapper) 以及其流控规则集合 SlotChain。每个 Entry 实体有一个上下文环境 EntryContext,存储每个 Rule 检测时用到的一些流控参数和流控判定结果。

值得注意的是,SentinelEntry.sc 值来自于 EntryOptions.slotChainEntryOptions.slotChain 存储了全局 SlotChain 对象 api/slot_chain.go:globalSlotChain

至于何为 SlotChain,就是 sentinel 提供的所有的流控组件的集合,可以简单地认为每个流控组件就是一个 Slot,其详细分析见[[3.5 SlotChain]](#3.5)。

sentinel 一些变量和函数命名的可读性较差,如 EntryOptions.acquireCount 实在无法让人望文生义,看过函数 core/api.go:WithAcquireCount() 的注释才明白:EntryOptions.acquireCount 是批量动作执行次数。如有的一次 RPC 请求中调用了服务端的一个服务接口,则取值 1【也是 EntryOptions.acquireCount 的默认取值】,如果调用了服务端的 3 个服务接口,则取值 3。所以建议改名为 EntryOptions.batchCount 比较好,考虑到最小改动原则,可以在保留 core/api.go:WithAcquireCount() 的同时增加一个同样功能的 core/api.go:WithBatchCount() 接口。相关改进已经提交到  pr 263。

1.3 Rule

    type TokenCalculateStrategy int32const (Direct TokenCalculateStrategy = iotaWarmUp)type ControlBehavior int32const (Reject ControlBehavior = iotaThrottling)// Rule describes the strategy of flow control, the flow control strategy is based on QPS statistic metrictype 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 QPSThreshold         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"`}

Rule 记录了某 Resource 的限流判定阈值 Threshold、限流时间窗口计时长度 StatIntervalInMs 以及 触发限流后的判罚动作 ControlBehavior。

上面核心是 Rule 的接口 RuleCheckSlot,至于 StatSlot 则用于统计 sentinel 自身的运行 metrics。

1.4 Flow

当前章节主要分析流控中的限流(core/flow),根据流控的处理流程梳理 sentinel 整体骨架。

1.4.1 TrafficShapingController

所谓 TrafficShapingController,顾名思义,就是 流量塑形控制器,是流控的具体实施者。

    // 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}

TrafficShapingCalculator 接口用于计算限流的上限,如果不使用 warm-up 功能,可以不去深究其实现,其实体之一 DirectTrafficShapingCalculator 返回 Rule.Threshold【用户设定的限流上限】。

    // 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.readOnlyMetricif 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}

RejectTrafficShapingChecker 依据 Rule.Threshold 判定 Resource 在当前时间窗口是否超限,其限流结果 TokenResultStatus 只可能是 Pass 或者 Blocked。

sentinel flow 还有一个匀速限流 ThrottlingChecker,它的目的是让请求匀速被执行,把一个时间窗口【譬如 1s】根据 threshold 再细分为更细的微时间窗口,在每个微时间窗口最多执行一次请求,其限流结果 TokenResultStatus 只可能是 Pass 或者 Blocked 或者 Wait,其相关意义分别为:

  • Pass:在微时间窗口内无超限,请求通过;
  • Wait:在微时间窗口内超限,被滞后若干时间窗口执行,在这段时间内请求需要等待;
  • Blocked:在微时间窗口内超限,且等待时间超过用户设定的最大愿意等待时间长度【Rule.MaxQueueingTimeMs】,请求被拒绝。
    type TrafficShapingController struct {flowCalculator TrafficShapingCalculatorflowChecker    TrafficShapingCheckerrule *Rule// boundStat is the statistic of current TrafficShapingControllerboundStat standaloneStatistic}func (t *TrafficShapingController) PerformChecking(acquireCount uint32, flag int32) *base.TokenResult {allowedTokens := t.flowCalculator.CalculateAllowedTokens(acquireCount, flag)return t.flowChecker.DoCheck(resStat, acquireCount, allowedTokens)}

在 Direct + Reject 限流的场景下,这三个接口其实并无多大意义,其核心函数 TrafficShapingController.PerformChecking() 的主要流程是:

  • 1  从 TrafficShapingController.boundStat 中获取当前 Resource 的 metrics 值【curCount】;
  • 2 如果 curCount + batchNum(acquireCount) > Rule.Threshold,则 pass,否则就 reject。

在限流场景下, TrafficShapingController 四个成员的意义如下:

  • flowCalculator 计算限流上限;
  • flowChecker 执行限流 Check 动作;
  • rule 存储限流规则;
  • boundStat 存储限流的 Check 结果和时间窗口参数,作为下次限流 Check 动作判定的依据。

1.4.2 TrafficControllerMap

在执行限流判定时,需要根据 Resource 名称获取其对应的 TrafficShapingController

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

package 级别全局私有变量 tcMap 存储了所有的 Rule,其 key 为 Resource 名称,value 则是与 Resource 对应的 TrafficShapingController。

用户级别接口函数 core/flow/rule_manager.go:LoadRules() 会根据用户定义的 Rule 构造其对应的 TrafficShapingController 存入 tcMap,这个接口调用函数 generateStatFor(*Rule) 构造 TrafficShapingController.boundStat

限流场景下,函数 generateStatFor(*Rule) 的核心代码如下:

    func generateStatFor(rule *Rule) (*standaloneStatistic, error) {resNode = stat.GetOrCreateResourceNode(rule.Resource, base.ResTypeCommon)// default case, use the resource's default statisticreadStat := resNode.DefaultMetric()retStat.reuseResourceStat = trueretStat.readOnlyMetric = readStatretStat.writeOnlyMetric = nilreturn &retStat, nil}

2 Metrics

Resource 的指标 Metrics 是进行 Rule 判定的基础。

2.1 原子时间轮 AtomicBucketWrapArray

Sentinel 库功能丰富,但无论是限流还是熔断,其存储基础都是滑动时间窗口。其间包含了众多优化:如无锁定长时间轮。

滑动窗口实现有很多种,时间轮算法是其中一种比较简单的实现,在时间轮算法之上可以实现多种限流方法。时间轮整体框图如下:

1.png

1 BucketWrap

时间轮的最基本单元是一个桶【时间窗口】。

    // 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) bytestype 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}

补充:这里之所以用指针,是因为以 BucketWrap 为基础的 AtomicBucketWrapArray 会被多个 sentinel 流控组件使用,每个组件的流控参数不一,例如:

  • core/circuitbreaker/circuit_breaker.go:slowRtCircuitBreaker 使用的 slowRequestLeapArray 的底层参数 slowRequestCounter
      // core/circuitbreaker/circuit_breaker.gotype slowRequestCounter struct {slowCount  uint64totalCount uint64}
  • core/circuitbreaker/circuit_breaker.go:errorRatioCircuitBreaker 使用的 errorCounterLeapArray 的底层参数 errorCounter
    // core/circuitbreaker/circuit_breaker.gotype errorCounter struct {errorCount uint64totalCount uint64}

1.1 MetricBucket

BucketWrap 可以认作是一种 时间桶模板,具体的桶的实体是 MetricsBucket,其定义如下:

    // 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 statisticcounter [base.MetricEventTotal]int64minRt   int64}

MetricBucket 存储了五种类型的 metric:

    // There are five events to record// pass + block == Totalconst (// sentinel rules check passMetricEventPass MetricEvent = iota// sentinel rules check blockMetricEventBlockMetricEventComplete// Biz error, used for circuit breakerMetricEventError// request execute rt, unit is millisecondMetricEventRt// hack for the number of eventMetricEventTotal)

2 AtomicBucketWrapArray

每个桶只记录了其起始时间和 metric 值,至于每个桶的时间窗口长度这种公共值则统一记录在 AtomicBucketWrapArray 内,AtomicBucketWrapArray 定义如下:

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

AtomicBucketWrapArray.base 的值是 AtomicBucketWrapArray.data slice 的 data 区域的首指针。因为 AtomicBucketWrapArray.data 是一个固定长度的 slice,所以 AtomicBucketWrapArray.base 直接存储数据内存区域的首地址,以加速访问速度。

其次,AtomicBucketWrapArray.data 中存储的是 BucketWrap 的指针,而不是 BucketWrap。

NewAtomicBucketWrapArrayWithTime() 函数会预热一下,把所有的时间桶都生成出来。

2.2 时间轮

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=888type LeapArray struct {bucketLengthInMs uint32sampleCount      uint32intervalInMs     uint32array            *AtomicBucketWrapArray// update lockupdateLock mutex}

LeapArray 各个成员解析:

  • bucketLengthInMs 是漏桶长度,以毫秒为单位;
  • sampleCount 则是时间漏桶个数;
  • intervalInMs 是时间窗口长度,以毫秒为单位。

其注释中的 ASCII 图很好地解释了每个字段的含义。

LeapArray 核心函数是 LeapArray.currentBucketOfTime(),其作用是根据某个时间点获取其做对应的时间桶 BucketWrap,代码如下:

    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 BucketWrapold := la.array.get(idx)if old == nil {// because la.array.data had initiated when new la.array// theoretically, here is not reachablenewWrap := &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 BucketWrapif 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))}}}

其 for-loop 核心逻辑是:

  • 1 获取时间点对应的时间桶 old;
  • 2 如果 old 为空,则新建一个时间桶,以原子操作的方式尝试存入时间窗口的时间轮中,存入失败则重新尝试;
  • 3 如果 old 就是当前时间点所在的时间桶,则返回;
  • 4 如果 old 的时间起点小于当前时间,则通过乐观锁尝试 reset 桶的起始时间等参数值,加锁更新成功则返回;
  • 5 如果 old 的时间起点大于当前时间,则系统发生了时间扭曲,返回错误。

2 BucketLeapArray

leapArray 实现了滑动时间窗口的所有主体,其对外使用接口则是 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     LeapArraydataType string}

从这个 struct 的注释可见,其时间窗口 BucketWrap 的实体是 MetricBucket。

2.3 Metric 数据读写

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 uint32sampleCount      uint32intervalInMs     uint32real             *BucketLeapArray}

SlidingWindowMetric 是对 BucketLeapArray 的一个封装,只提供了只读接口。

ResourceNode

    type BaseStatNode struct {sampleCount uint32intervalMs  uint32goroutineNum int32arr    *sbase.BucketLeapArraymetric *sbase.SlidingWindowMetric}type ResourceNode struct {BaseStatNoderesourceName stringresourceType base.ResourceType}// core/stat/node_storage.gotype ResourceNodeMap map[string]*ResourceNodevar (inboundNode = NewResourceNode(base.TotalInBoundResourceName, base.ResTypeCommon)resNodeMap = make(ResourceNodeMap)rnsMux     = new(sync.RWMutex))

BaseStatNode 对外提供了读写接口,其数据写入 BaseStatNode.arr,读取接口则依赖 BaseStatNode.metric。BaseStatNode.arr 是在 NewBaseStatNode() 中创建的,指针 SlidingWindowMetric.real 也指向它。

ResourceNode 则顾名思义,其代表了某资源和它的 Metrics 存储  ResourceNode.BaseStatNode

全局变量 resNodeMap 存储了所有资源的 Metrics 指标数据。

3 限流流程

本节只分析 Sentinel 库提供的最基础的流量整形功能 -- 限流,限流算法多种多样,可以使用其内置的算法,用户自己也可以进行扩展。

限流过程有三步步骤:

  • 1 针对特定 Resource 构造其 EntryContext,存储其 Metrics、限流开始时间等,Sentinel 称之为 StatPrepareSlot;
  • 2 依据 Resource 的限流算法判定其是否应该进行限流,并给出限流判定结果,Sentinel 称之为 RuleCheckSlot;

    • 补充:这个限流算法是一系列判断方法的合集(SlotChain);
  • 3 判定之后,除了用户自身根据判定结果执行相应的 action,Sentinel 也需要根据判定结果执行自身的 Action,以及把整个判定流程所使用的的时间 RT 等指标存储下来,Sentinel 称之为 StatSlot。

整体流程如下图所示:

2.png

3.1 Slot

针对 Check 三个步骤,有三个对应的 Slot 分别定义如下:

    // StatPrepareSlot is responsible for some preparation before statistic// For example: init structure and so ontype 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 chaintype StatSlot interface {// OnEntryPass function will be invoked when StatPrepareSlots and RuleCheckSlots execute pass// StatSlots will do some statistic logic, such as QPS、log、etcOnEntryPassed(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 detailOnEntryBlocked(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 functionOnCompleted(ctx *EntryContext)}

抛却 Prepare 和 Stat,可以简单的认为:所谓的 slot,就是 sentinel 提供的某个流控组件。

值得注意的是,根据注释 StatSlot.OnCompleted 只有在 RuleCheckSlot.Check 通过才会执行,用于计算从请求开始到结束所使用的 RT 等 Metrics。

3.2 Prepare

    // core/base/slot_chain.go// StatPrepareSlot is responsible for some preparation before statistic// For example: init structure and so ontype 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.gotype 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}

如前面解释,Prepare 主要是构造存储 Resource Metrics 所使用的 ResourceNode。所有 Resource 的 StatNode 都会存储在 package 级别的全局变量 core/stat/node_storage.go:resNodeMap [type: map[string]*ResourceNode]中,函数 GetOrCreateResourceNode 用于根据 Resource Name 从 resNodeMap 中获取其对应的 StatNode,如果不存在则创建一个 StatNode 并存入 resNodeMap

3.3 Check

RuleCheckSlot.Check() 执行流程:

  • 1 根据 Resource 名称获取其所有的 Rule 集合;
  • 2 遍历 Rule 集合,对 Resource 依次执行 Check,任何一个 Rule 判定 Resource 需要进行限流【Blocked】则返回,否则放行。
    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 orderfor _, tc := range tcs {r := canPassCheck(tc, ctx.StatNode, ctx.Input.AcquireCount)if r == nil {// nil means passcontinue}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)}

3.4 Exit

sentinel 对 Resource 进行 Check 后,其后续逻辑执行顺序是:

  • 1 如果 RuleCheckSlot.Check() 判定 pass 通过则执行 StatSlot.OnEntryPassed(),否则 RuleCheckSlot.Check() 判定 reject 则执行 StatSlot.OnEntryBlocked();
  • 2 如果 RuleCheckSlot.Check() 判定 pass 通过,则执行本次 Action;
  • 3 如果 RuleCheckSlot.Check() 判定 pass 通过,则执行 SentinelEntry.Exit() --> SlotChain.ext() --> StatSlot.OnCompleted() 。

第三步骤的调用链路如下:

StatSlot.OnCompleted()

    // core/flow/standalone_stat_slot.gotype StandaloneStatSlot struct {}func (s StandaloneStatSlot) OnEntryPassed(ctx *base.EntryContext) {res := ctx.Resource.Name()for _, tc := range getTrafficControllerListFor(res) {if !tc.boundStat.reuseResourceStat {if tc.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}

SlotChain.exit()

    // core/base/slot_chain.gotype SlotChain struct {}func (sc *SlotChain) exit(ctx *EntryContext) {// The OnCompleted is called only when entry passedif ctx.IsBlocked() {return}for _, s := range sc.stats {s.OnCompleted(ctx)}}

SentinelEntry.Exit()

    // core/base/entry.gotype SentinelEntry struct {sc *SlotChainexitCtl sync.Once}func (e *SentinelEntry) Exit() {e.exitCtl.Do(func() {if e.sc != nil {e.sc.exit(ctx)}})}

从上面执行可见,StatSlot.OnCompleted() 是在 Action 【如一次 RPC 的请求-响应 Invokation】完成之后调用的。如果有的组件需要计算一次 Action 的时间耗费  RT,就在其对应的 StatSlot.OnCompleted() 中依据 EntryContext.startTime 完成时间耗费计算。

[3.5 SlotChain]()

Sentinel 本质是一个流控包,不仅提供了限流功能,还提供了众多其他诸如自适应流量保护、熔断降级、冷启动、全局流量 Metrics 结果等功能流控组件,Sentinel-Go 包定义了一个 SlotChain 实体存储其所有的流控组件。

   // 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   []StatPrepareSlotruleChecks []RuleCheckSlotstats      []StatSlot}// The entrance of slot chain// Return the TokenResult and nil if internal panic.func (sc *SlotChain) Entry(ctx *EntryContext) *TokenResult {// execute prepare slotsps := sc.statPresif len(sps) > 0 {for _, s := range sps {s.Prepare(ctx)}}// execute rule based checking slotrcs := sc.ruleChecksvar ruleCheckRet *TokenResultif len(rcs) > 0 {for _, s := range rcs {sr := s.Check(ctx)if sr == nil {// nil equals to check passcontinue}// check slot resultif sr.IsBlocked() {ruleCheckRet = srbreak}}}if ruleCheckRet == nil {ctx.RuleCheckResult.ResetToPass()} else {ctx.RuleCheckResult = ruleCheckRet}// execute statistic slotss := sc.statsruleCheckRet = ctx.RuleCheckResultif 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 passedif ctx.IsBlocked() {return}for _, s := range sc.stats {s.OnCompleted(ctx)}// relieve the context here}

建议:Sentinel 包针对某个 Resource 无法确知其使用了那个组件,在运行时会针对某个 Resource 的 EntryContext 依次执行所有的组件的 Rule。Sentinel-golang 为何不给用户相关用户提供一个接口让其设置使用的流控组件集合,以减少下面函数 SlotChain.Entry() 中执行 RuleCheckSlot.Check() 执行次数?相关改进已经提交到 pr 264【补充,代码已合并,据负责人压测后回复 sentinel-go 效率整体提升 15%】。

globalSlotChain

Sentinel-Go 定义了一个 SlotChain 的 package 级别的全局私有变量 globalSlotChain 用于存储其所有的流控组件对象。相关代码示例如下。因本文只关注限流组件,所以下面只给出了限流组件的注册代码。

   // api/slot_chain.gofunc BuildDefaultSlotChain() *base.SlotChain {sc := base.NewSlotChain()sc.AddStatPrepareSlotLast(&stat.ResourceNodePrepareSlot{})sc.AddRuleCheckSlotLast(&flow.Slot{})sc.AddStatSlotLast(&flow.StandaloneStatSlot{})return sc}var globalSlotChain = BuildDefaultSlotChain()

Entry

在 Sentinel-Go 对外的最重要的入口函数 api/api.go:Entry() 中,globalSlotChain 会作为 EntryOptions 的 SlotChain 参数被使用。

    // 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 = globalSlotChainreturn entry(resource, options)}

Sentinel 的演进离不开社区的贡献。Sentinel Go 1.0 GA 版本即将在近期发布,带来更多云原生相关的特性。我们非常欢迎感兴趣的开发者参与贡献,一起来主导未来版本的演进。我们鼓励任何形式的贡献,包括但不限于:

• bug fix
• new features/improvements
• dashboard
• document/website
• test cases

开发者可以在 GitHub 上面的 good first issue 列表上挑选感兴趣的 issue 来参与讨论和贡献。我们会重点关注积极参与贡献的开发者,核心贡献者会提名为 Committer,一起主导社区的发展。我们也欢迎大家有任何问题和建议,都可以通过 GitHub issue、Gitter 或钉钉群(群号:30150716)等渠道进行交流。Now start hacking!

• Sentinel Go repo: https://github.com/alibaba/sentinel-golang
• 企业用户欢迎进行登记:https://github.com/alibaba/Sentinel/issues/18

作者简介

于雨(github @AlexStocks),apache/dubbo-go 项目负责人,一个有十多年服务端基础架构研发一线工作经验的程序员,目前在蚂蚁金服可信原生部从事容器编排和 service mesh 工作。热爱开源,从 2015 年给 Redis 贡献代码开始,陆续改进过 Muduo/Pika/Dubbo/Dubbo-go 等知名项目。

 

原文链接
本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/514878.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工业发展 安全护航 2021年工业互联网安全发展峰会成功召开

在数字化创新日益深入的背景下&#xff0c;工业互联网已经成为制造企业构建敏捷、弹性的基础架构的重要转型方向。但与此同时&#xff0c;安全风险与威胁向OT环境渗透&#xff0c;产生了额外的复杂性&#xff0c;对于关键业务与数据带来了严重威胁&#xff0c;构建工业互联网安…

基于 Flink + ClickHouse 打造轻量级点击流实时数仓

作者&#xff1a;LittleMagic Flink 和 ClickHouse 分别是实时计算和&#xff08;近实时&#xff09;OLAP 领域的翘楚&#xff0c;也是近些年非常火爆的开源框架&#xff0c;很多大厂都在将两者结合使用来构建各种用途的实时平台&#xff0c;效果很好。关于两者的优点就不再赘…

Spring boot 2.3优雅下线,距离生产还有多远?

简介&#xff1a; 对于任何一个线上应用&#xff0c;如何在服务更新部署过程中保证业务无感知是开发者必须要解决的问题&#xff0c;即从应用停止到重启恢复服务这个阶段不能影响正常的业务请求&#xff0c;这使得无损下线成为应用生命周期中必不可少的一个环节。 前言 在生产…

发布 128 核 Altra Max,自研内核,明年推出 5nm 处理器,“性能怪兽”Ampere 搞大事?

2015 年&#xff0c;在英特尔就职 28 年的总裁 Renee James 辞职&#xff0c;正在大众纷纷猜测她将如何开启下一段旅程时&#xff0c;她有了创业的想法&#xff0c;2017 年带领新团队创立了专注于为云和边缘打造微处理器的 Ampere 公司。 在云原生浪潮下&#xff0c;底层硬件需…

2020亚太内容分发大会 阿里云荣获“边缘计算领航企业”奖

10月21日&#xff0c;第八届亚太内容分发大会在北京隆重召开。凭借在边缘计算领域的先发优势、技术实力与丰富实践&#xff0c;阿里云荣获“边缘计算领航企业”称号。 伴随着中国5G商用进程提速&#xff0c;大带宽、大连接、低时延的应用场景爆发&#xff0c;将催生产业变革&a…

最佳途径 | 容器规模化落地如何四步走?

随着云原生时代的发展&#xff0c;传统 IT 基础设施加速云化&#xff0c;云原生化成为云上的必然趋势。作为云原生代表技术之一&#xff0c;容器技术可帮助企业提升 IT 架构的敏捷性&#xff0c;加速应用创新&#xff0c;帮助企业更加灵活地应对商业发展中的不确定性。疫情期间…

elasticsearch 嵌入式_Elasticsearch 开箱指南

内容概要ES 基础介绍&#xff0c;重点是其中的核心概念。基础 API 实践操作。1. 基础介绍Elasticsearch (ES) 是一个数据库&#xff0c;提供了分布式的、准实时搜索和分析。基于 Apache Lucene&#xff0c;可以操作结构化数据、非结构化数据、数字类型数据、地理空间数据。数据…

最良心的 chrome 插件可以良心到什么程度?

CSDN下起了红包雨399 元智能音箱199 元天猫精灵300元现金红包/会员100元红包/会员更有千万流量曝光100%有奖......作为日常总发现 " 宝藏 " 的你总体验过一些 " 王炸 " 级别的chrome插件让你想 “ 真诚 ” 安利所以&#xff0c;CSDN开启了彩虹屁chrome插件…

一文教会你如何写复杂业务代码

简介&#xff1a; 这两天在看零售通商品域的代码。面对零售通如此复杂的业务场景&#xff0c;如何在架构和代码层面进行应对&#xff0c;是一个新课题。针对该命题&#xff0c;我进行了比较细致的思考和研究。结合实际的业务场景&#xff0c;我沉淀了一套“如何写复杂业务代码”…

Day.js 常用方法

文章目录1. 初始化日期 / 时间2. 格式化日期 / 时间3. 加 / 减4. 获取某年某月的第一天或最后一天5. 获取星期几6. 获取毫秒数7. 获取时间差&#xff08;默认输出的差值单位是毫秒&#xff09;8. 获取时、分、秒9. 将毫秒转为时分秒10. 判断一个日期是否在另外一个日期之后 isA…

如何使用云原生数据湖,助力线上教育行业逐步智能化

简介&#xff1a; 阿里云基于对象存储OSS构建的数据湖解决方案&#xff0c;帮助企业有效消除数据孤岛的现象&#xff0c;让数据的价值真正被利用起来。 行业综述 线下教育行业因疫情受挫&#xff0c;线上教育却逆势增长 随着90年代互联网的引入&#xff0c;在线教育产品也依托…

caas k8s主控节点如何查询_k8s--04 部署harbor作为k8s镜像仓库

k8s实战部署harbor作为k8s镜像仓库1.实验目标部署k8s私有镜像仓库harbor把demo小项目需要的镜像上传到harbor上修改demo项目的资源配置清单&#xff0c;镜像地址修改为harbord的地址2.再node1上安装harbor[rootnode1 ~]# cd /opt/#上传harbor软件包[rootnode1 /opt]# rz -Erz w…

vue3中使用cookie

前端使用cookie 步骤一 编写方法cookie.ts //获取cookie、 const CooieTool {getCookie: (name: string) > {var arr, reg new RegExp("(^| )" name "([^;]*)(;|$)");if (arr document.cookie.match(reg))return (arr[2]);elsereturn null;},//设…

无人机、IoT 都危险?第五代网络威胁有哪些特点

从无序中寻找踪迹&#xff0c;从眼前事探索未来。2021 年正值黄金十年新开端&#xff0c;CSDN 以中立技术社区专业、客观的角度&#xff0c;深度探讨中国前沿 IT 技术演进&#xff0c;推出年度重磅企划栏目——「拟合」&#xff0c;通过对话企业技术高管大咖&#xff0c;跟踪报…

持续定义SaaS模式云数据仓库+Serverless

导读&#xff1a;今天主要和大家交流的是网易在数据湖 Iceberg 的一些思考与实践。从网易在数据仓库建设中遇到的痛点出发&#xff0c;介绍对数据湖 Iceberg 的探索以及实践之路。 主要内容包括&#xff1a; 数据仓库平台建设的痛点数据湖 Iceberg 的核心原理数据湖 Iceberg 社…

循序渐进db2 第3版_「图书推荐」焊接工程师手册第3版

机械工业出版社陈祝年 陈茂爱 著内容介绍《焊接工程师手册》(第3版)是焊接专业的综合性工具书&#xff0c;基本涵盖了焊接专业的技术内容。本版在保留第2版精华和特色的基础上添加了先进的工艺技术内容。全书共9篇58章。第1篇汇集了焊接工程师最常用而又不易记忆的符号、公式和…

阿里云推出业内首个云原生企业级数据湖解决方案:将在今年双11大规模应用

简介&#xff1a; 数据湖高峰论坛在京召开&#xff0c;阿里云宣布推出业内首个云原生企业级数据湖解决方案&#xff0c;提供EB级数据存储、分析能力&#xff0c;可一站式实现湖存储、湖加速、湖管理、湖计算&#xff0c;帮助企业对数据深入挖掘与分析&#xff0c;洞察其中蕴含的…

Serverless对研发效能的变革和创新

对企业而言&#xff0c;Serverless 架构有着巨大的应用潜力。随着云产品的完善&#xff0c;产品的集成和被集成能力的加强&#xff0c;软件交付流程自动化能力的提高&#xff0c;我们相信在 Serverless 架构下&#xff0c;企业的敏捷性有 10 倍提升的潜力。本次分享我主要分为以…

c3p0 服务启动获取连接超时_微服务架构中的熔断、降级

微服务架构中熔断和降级是保证服务高可用的一项重要功能点&#xff0c;微服务区别于一体化项目的最大区别也再于熔断和降级&#xff0c;很多微服务项目的开发人员对熔断的理解就是当服务不可用的时候&#xff0c;为了让整体服务可以正常运行&#xff0c;需要让后续的请求直接返…

重塑APM标杆,博睿数据战略升级助力企业数字化转型

&#xff08;博睿数据发布仪式&#xff09; 2021年5月26日&#xff0c;由博睿数据举办的“服务可达 达者为先博睿数据2021年战略升级发布巡展”北京站&#xff0c;在北京金茂威斯汀大饭店圆满举行&#xff01;本次战略升级发布巡展不仅揭开了“数据链DNA”的神秘面纱&#xff…