文章目录
前言
建议先在官方文档中了解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
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
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
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
}