【熔断限流】sentinel——侵入go代码


前言

建议先在官方文档中了解sentinel的机制

sentinel官方文档:https://sentinelguard.io/zh-cn/docs/golang/basic-api-usage.html

Sentinel是一款开源的分布式系统防护框架,它提供了实时的流量控制、熔断降级、系统负载保护等功能,帮助开发者解决分布式系统中的各种流量控制和熔断降级问题。

Sentinel的核心功能包括:

  • 流量控制: Sentinel支持基于QPS、线程数等多种维度的流量控制策略,可以实现实时的流量控制。
  • 熔断降级: Sentinel支持基于响应时间、异常比例等多种维度的熔断降级策略,可以实现快速的熔断降级,保护系统免受异常请求的影响。
  • **系统负载保护:**Sentinel可以根据系统的负载情况进行流量控制和熔断降级,保证系统的可用性和稳定性。
  • 统计监控: Sentinel提供了全面的统计监控功能,可以对系统的各种指标进行监控和报警。

Sentinel的优点:

  • 轻量级: Sentinel使用极少的依赖,可以轻松地集成到任何Java应用程序中。
  • 易于使用: Sentinel提供了简单易用的API,可以快速地实现流量控制和熔断降级等功能。
  • 高性能: Sentinel使用高性能的算法和数据结构,可以实现实时的流量控制和熔断降级。
  • 分布式: Sentinel可以轻松地实现分布式部署,支持多节点协同工作,可以保证系统的可用性和稳定性。

Sentinel的缺点:

  • 依赖ZooKeeper: Sentinel需要依赖ZooKeeper来实现分布式部署和协同工作,这增加了系统的复杂度和运维成本。
  • 学习成本较高: Sentinel的API和配置比较复杂,需要一定的学习成本。

总之,Sentinel是一个非常优秀的分布式系统防护框架,它可以帮助开发者实现流量控制、熔断降级等功能,保证系统的可用性和稳定性。如果您的系统需要实现这些功能,可以考虑使用Sentinel。


一、什么是限流、熔断和降级

在分布式系统中,限流、熔断和降级都是常见的应对高并发和故障的手段。

详细可以看我以前的文章:https://blog.csdn.net/the_shy_faker/article/details/129000466

限流(Flow Control)

限流是指通过对请求的流量进行控制,使系统能够稳定地运行。当系统的流量超出了系统的承载能力时,可以通过限制请求的数量、速度或者在繁忙时段暂停服务等手段来保护系统不被过载。限流可以在客户端、服务端或者代理层面实现,常见的实现方式包括令牌桶算法、漏桶算法等。

熔断(Circuit Breaker)

熔断是指当系统出现故障或异常时,自动地断开某个服务的调用,避免故障在系统中继续扩散,从而保护系统的可用性。熔断器通常有三种状态:关闭状态、开启状态和半开状态。当系统调用某个服务的失败率达到一定的阈值时,熔断器会从关闭状态转换为开启状态,并且在开启状态下,所有对该服务的请求都会被熔断器直接拒绝;在一段时间后,熔断器会从开启状态转换为半开状态,这时系统允许部分请求通过,以检测该服务是否已经恢复正常。

降级(Degradation)

降级是指在系统出现故障或者高峰期时,通过牺牲一些功能或者服务,保障系统的核心功能和服务能够正常运行。降级可以在应用层面或者系统层面实现,例如在应用层面可以关闭一些不重要的功能,减少一些耗时的操作等,而在系统层面可以关闭一些不必要的服务、减少一些负载等。降级需要根据系统的实际情况来确定降级的程度和对象,以保障核心功能的正常运行。


二、sentinel和hystrix对比

sentinel官方文档:https://sentinelguard.io/zh-cn/docs/golang/basic-api-usage.html

在这里插入图片描述


三、sentinel限流机制

使用sentinel-golang的开源库:https://github.com/alibaba/sentinel-golang
example里有案例

1. QPS限流:

package main

import (
	"fmt"
	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/base"
	"github.com/alibaba/sentinel-golang/core/flow"
	"log"
)

func main() {
	// 务必先进行初始化
	err := sentinel.InitDefault()
	if err != nil {
		log.Fatalf(err.Error())
	}
	// 配置一条限流规则
	_, err = flow.LoadRules([]*flow.Rule{
		{
			Resource:               "some-test", //熔断器规则生效的埋点资源的名称
			TokenCalculateStrategy: flow.Direct, //Direct表示直接使用字段
			ControlBehavior:        flow.Reject, //Reject表示超过阈值直接拒绝
			Threshold:              10,        //QPS流控阈值10个
			StatIntervalInMs:       1000,		//QPS时间长度1s
		},
	})
	if err != nil {
		log.Fatalf("配置规则出错:", err.Error())
	}
	//模拟流量
	for i:=0;i<12;i++{
		e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound))
		if b != nil {
			fmt.Println("限流了")
		} else {
			fmt.Println("检查通过")
			e.Exit()
		}
	}
}

运行后可以看到只有10个通过,2个被拒绝
在这里插入图片描述

2. sentinel的预热和冷启动

冷启动:

package main

import (
	"fmt"
	"log"
	"math/rand"
	"time"

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

func main() {
	//先初始化sentinel
	err := sentinel.InitDefault()
	if err != nil {
		log.Fatalf("初始化sentinel 异常: %v", err)
	}

	var globalTotal int //总共有多少流量
	var passTotal int   //通过了多少流量
	var blockTotal int  //拒绝了多少流量
	//阻塞主线程
	ch := make(chan struct{})

	//配置限流规则
	_, err = flow.LoadRules([]*flow.Rule{
		{
			Resource:               "some-test",
			TokenCalculateStrategy: flow.WarmUp, //冷启动策略
			ControlBehavior:        flow.Reject, //直接拒绝
			Threshold:              1000,        //上限
			WarmUpPeriodSec:        30,          //30s之内陆续到达上限,其他预热条件看官方文档,比如:预热因子
		},
	})

	if err != nil {
		log.Fatalf("加载规则失败: %v", err)
	}

	//我会在每一秒统计一次,这一秒只能 你通过了多少,总共有多少, block了多少, 每一秒会产生很多的block
	for i := 0; i < 100; i++ {
		go func() {
			for {
				globalTotal++
				e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound))
				if b != nil {
					//fmt.Println("限流了")
					blockTotal++
					time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)
				} else {
					passTotal++
					time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)
					e.Exit()
				}
			}
		}()
	}

	go func() {
		var oldTotal int //过去1s总共有多少个
		var oldPass int  //过去1s总共pass多少个
		var oldBlock int //过去1s总共block多少个
		for {
			oneSecondTotal := globalTotal - oldTotal
			oldTotal = globalTotal

			oneSecondPass := passTotal - oldPass
			oldPass = passTotal

			oneSecondBlock := blockTotal - oldBlock
			oldBlock = blockTotal

			time.Sleep(time.Second)
			fmt.Printf("total:%d, pass:%d, block:%d\n", oneSecondTotal, oneSecondPass, oneSecondBlock)
		}
	}()
	//阻塞主线程
	<-ch
}

3. sentinel的Throttling配置策略

对于Throttling官方的解释:

Throttling:表示匀速排队的统计策略。它的中心思想是,以固定的间隔时间让请求通过。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过(排队等待处理);若预期的通过时间超出最大排队时长,则直接拒接这个请求。

在这里插入图片描述
代码演示:

package main

import (
	"fmt"
	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/base"
	"github.com/alibaba/sentinel-golang/core/flow"
	"log"
	"time"
)

func main() {
	// 务必先进行初始化
	err := sentinel.InitDefault()
	if err != nil {
		log.Fatalf(err.Error())
	}
	// 配置一条限流规则
	_, err = flow.LoadRules([]*flow.Rule{
		{
			Resource:               "some-test", //熔断器规则生效的埋点资源的名称
			TokenCalculateStrategy: flow.Direct, //Direct表示直接使用字段
			//ControlBehavior:        flow.Reject, //Reject表示超过阈值直接拒绝
			ControlBehavior:  flow.Throttling, //匀速通过
			Threshold:        10,              //QPS流控阈值10个
			StatIntervalInMs: 1000,            //QPS时间长度1s
			//WarmUpPeriodSec: 1, //预热的时间长度
		},
	})
	if err != nil {
		log.Fatalf("配置规则出错:", err.Error())
	}
	for i := 0; i < 12; i++ {
		e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound))
		if b != nil {
			fmt.Println("限流了")
		} else {
			fmt.Println("检查通过")
			e.Exit()
		}
		//配合冷启动策略
		time.Sleep(time.Millisecond * 100)
	}
}

四、熔断降级

官方的熔断降级模型:https://sentinelguard.io/zh-cn/docs/golang/circuit-breaking.html

在这里插入图片描述

1. sentinel的熔断接口-基于错误数

官方文档:https://sentinelguard.io/zh-cn/docs/golang/circuit-breaking.html

官方插件示例:https://github.com/alibaba/sentinel-golang/blob/master/example/circuitbreaker/error_count/circuit_breaker_error_count_example.go

main.go

package main

import (
	"errors"
	"fmt"
	"log"
	"math/rand"
	"time"

	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/circuitbreaker"
	"github.com/alibaba/sentinel-golang/core/config"
	"github.com/alibaba/sentinel-golang/logging"
	"github.com/alibaba/sentinel-golang/util"
)

//监听每一次的状态
type stateChangeTestListener struct {
}

//Close状态
func (s *stateChangeTestListener) OnTransformToClosed(prev circuitbreaker.State, rule circuitbreaker.Rule) {
	fmt.Printf("rule.steategy: %+v, From %s to Closed, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis())
}

//Open状态
func (s *stateChangeTestListener) OnTransformToOpen(prev circuitbreaker.State, rule circuitbreaker.Rule, snapshot interface{}) {
	fmt.Printf("rule.steategy: %+v, From %s to Open, snapshot: %d, time: %d\n", rule.Strategy, prev.String(), snapshot, util.CurrentTimeMillis())
}

//HalfOpen半消息状态
func (s *stateChangeTestListener) OnTransformToHalfOpen(prev circuitbreaker.State, rule circuitbreaker.Rule) {
	fmt.Printf("rule.steategy: %+v, From %s to Half-Open, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis())
}

func main() {
	total := 0      //总共的流量
	totalPass := 0  //通过了多少
	totalError := 0 //生成了多少错误
	totalBlock := 0 //拒绝了多少

	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)
	}
	//阻塞主线程
	ch := make(chan struct{})
	// Register a state change listener so that we could observer the state change of the internal circuit breaker.
	circuitbreaker.RegisterStateChangeListeners(&stateChangeTestListener{})
	//配置策略
	_, err = circuitbreaker.LoadRules([]*circuitbreaker.Rule{
		// Statistic time span=5s, recoveryTimeout=3s, maxErrorCount=50
		{
			Resource:                     "abc",
			Strategy:                     circuitbreaker.ErrorCount,//基于错误数
			RetryTimeoutMs:               3000, //重试时间3s
			MinRequestAmount:             10,   //静默数
			StatIntervalMs:               5000, //统计时间
			//StatSlidingWindowBucketCount: 10,   //错误统计
			Threshold:                    50,   //多少个以内
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	logging.Info("[CircuitBreaker ErrorCount] Sentinel Go circuit breaking demo is running. You may see the pass/block metric in the metric log.")
	//模拟流量
	go func() {
		for {
			total++
			e, b := sentinel.Entry("abc")
			if b != nil {
				// g1 blocked
				totalBlock++
				fmt.Println("协程熔断")
				time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond)
			} else {
				totalPass++
				//通过后随机生成一个错误
				if rand.Uint64()%20 > 9 {
					totalError++
					// Record current invocation as error.
					sentinel.TraceError(e, errors.New("biz error"))
				}
				// g1 passed
				time.Sleep(time.Duration(rand.Uint64()%20+10) * time.Millisecond)
				e.Exit()
			}
		}
	}()
	//模拟流量
	go func() {
		for {
			total++
			e, b := sentinel.Entry("abc")
			if b != nil {
				totalBlock++
				// g2 blocked
				time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond)
			} else {
				totalPass++
				// g2 passed
				time.Sleep(time.Duration(rand.Uint64()%80) * time.Millisecond)
				e.Exit()
			}
		}
	}()
	//每秒监听状态
	go func() {
		for {
			time.Sleep(time.Second)
			fmt.Printf("total:%d,totalPass:%d,totalError:%d,totalBlock:%d\n", total, totalPass, totalError, totalBlock)
		}
	}()
	<-ch
}

2. sentinel的熔断接口-基于错误率

官方文档:https://sentinelguard.io/zh-cn/docs/golang/circuit-breaking.html

官方案例:https://github.com/alibaba/sentinel-golang/blob/master/example/circuitbreaker/error_ratio/circuit_breaker_error_ratio_example.go

main.go

package main

import (
	"errors"
	"fmt"
	"log"
	"math/rand"
	"time"

	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/circuitbreaker"
	"github.com/alibaba/sentinel-golang/core/config"
	"github.com/alibaba/sentinel-golang/logging"
	"github.com/alibaba/sentinel-golang/util"
)

type stateChangeTestListener struct {
}

func (s *stateChangeTestListener) OnTransformToClosed(prev circuitbreaker.State, rule circuitbreaker.Rule) {
	fmt.Printf("rule.steategy: %+v, From %s to Closed, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis())
}

func (s *stateChangeTestListener) OnTransformToOpen(prev circuitbreaker.State, rule circuitbreaker.Rule, snapshot interface{}) {
	fmt.Printf("rule.steategy: %+v, From %s to Open, snapshot: %.2f, time: %d\n", rule.Strategy, prev.String(), snapshot, util.CurrentTimeMillis())
}

func (s *stateChangeTestListener) OnTransformToHalfOpen(prev circuitbreaker.State, rule circuitbreaker.Rule) {
	fmt.Printf("rule.steategy: %+v, From %s to Half-Open, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis())
}

func main() {
	total := 0//总流量
	totalPass := 0//通过了多少
	totalError := 0//生成了多少错误
	totalBlock := 0//拒绝了多少

	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)
	}
	ch := make(chan struct{})
	// Register a state change listener so that we could observer the state change of the internal circuit breaker.
	circuitbreaker.RegisterStateChangeListeners(&stateChangeTestListener{})

	_, err = circuitbreaker.LoadRules([]*circuitbreaker.Rule{
		// Statistic time span=5s, recoveryTimeout=3s, maxErrorCount=50
		{
			Resource:         "abc",
			Strategy:         circuitbreaker.ErrorRatio,//基于错误率
			RetryTimeoutMs:   3000,//重试时间
			MinRequestAmount: 10,//静默数
			StatIntervalMs:   5000,//统计时间
			Threshold:        0.4,//错误率%40
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	logging.Info("[CircuitBreaker ErrorCount] Sentinel Go circuit breaking demo is running. You may see the pass/block metric in the metric log.")
	go func() {
		for {
			total++
			e, b := sentinel.Entry("abc")
			if b != nil {
				// g1 blocked
				totalBlock++
				fmt.Println("协程熔断")
				time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond)
			} else {
				totalPass++
				if rand.Uint64()%20 > 9 {
					totalError++
					// Record current invocation as error.
					sentinel.TraceError(e, errors.New("biz error"))
				}
				// g1 passed
				time.Sleep(time.Duration(rand.Uint64()%60+10) * time.Millisecond)
				e.Exit()
			}
		}
	}()
	go func() {
		for {
			total++
			e, b := sentinel.Entry("abc")
			if b != nil {
				totalBlock++
				// g2 blocked
				time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond)
			} else {
				totalPass++
				// g2 passed
				time.Sleep(time.Duration(rand.Uint64()%80) * time.Millisecond)
				e.Exit()
			}
		}
	}()
	go func() {
		for {
			time.Sleep(time.Second)
			fmt.Printf("total:%d,totalPass:%d,totalError:%d,totalBlock:%d,%.5f\n", total, totalPass, totalError, totalBlock, float64(totalError)/float64(total))
		}
	}()
	<-ch
}

3. sentinel的熔断接口-基于慢请求

官方文档:https://sentinelguard.io/zh-cn/docs/golang/circuit-breaking.html

官方案例:https://github.com/alibaba/sentinel-golang/blob/master/example/circuitbreaker/slow_rt_ratio/circuit_breaker_slow_rt_ratio_example.go

main.go

package main

import (
	"errors"
	"fmt"
	"log"
	"math/rand"
	"time"

	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/circuitbreaker"
	"github.com/alibaba/sentinel-golang/core/config"
	"github.com/alibaba/sentinel-golang/logging"
	"github.com/alibaba/sentinel-golang/util"
)

type stateChangeTestListener struct {
}

func (s *stateChangeTestListener) OnTransformToClosed(prev circuitbreaker.State, rule circuitbreaker.Rule) {
	fmt.Printf("rule.steategy: %+v, From %s to Closed, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis())
}

func (s *stateChangeTestListener) OnTransformToOpen(prev circuitbreaker.State, rule circuitbreaker.Rule, snapshot interface{}) {
	fmt.Printf("rule.steategy: %+v, From %s to Open, snapshot: %.2f, time: %d\n", rule.Strategy, prev.String(), snapshot, util.CurrentTimeMillis())
}

func (s *stateChangeTestListener) OnTransformToHalfOpen(prev circuitbreaker.State, rule circuitbreaker.Rule) {
	fmt.Printf("rule.steategy: %+v, From %s to Half-Open, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis())
}

func main() {
	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)
	}
	ch := make(chan struct{})
	// Register a state change listener so that we could observer the state change of the internal circuit breaker.
	circuitbreaker.RegisterStateChangeListeners(&stateChangeTestListener{})

	_, err = circuitbreaker.LoadRules([]*circuitbreaker.Rule{
		// Statistic time span=5s, recoveryTimeout=3s, maxErrorRatio=40%
		{
			Resource:         "abc",
			Strategy:         circuitbreaker.SlowRequestRatio,//延时机制
			RetryTimeoutMs:   3000, //恢复时间
			MinRequestAmount: 10,   //静默数
			StatIntervalMs:   5000, //多少时间以内
			MaxAllowedRtMs:   50,   //此请求是否到达临界值50ms
			Threshold:        0.4,  //延时比例(比如100个连接有40个大于50ms)
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	logging.Info("[CircuitBreaker ErrorRatio] Sentinel Go circuit breaking demo is running. You may see the pass/block metric in the metric log.")
	go func() {
		for {
			e, b := sentinel.Entry("abc")
			if b != nil {
				// g1 blocked
				time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond)
			} else {
				if rand.Uint64()%20 > 6 {
					// Record current invocation as error.
					sentinel.TraceError(e, errors.New("biz error"))
				}
				// g1 passed
				time.Sleep(time.Duration(rand.Uint64()%80+10) * time.Millisecond)
				e.Exit()
			}
		}
	}()
	go func() {
		for {
			e, b := sentinel.Entry("abc")
			if b != nil {
				// g2 blocked
				time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond)
			} else {
				// g2 passed
				time.Sleep(time.Duration(rand.Uint64()%80) * time.Millisecond)
				e.Exit()
			}
		}
	}()
	<-ch
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jzin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值