我們知道,對于一個項(xiàng)目之初,我們不可能上來就按幾千的并發(fā)去配置,為什么?兩個方面,第一個是成本高。第二個是維護(hù)難度大。即便是天貓?zhí)詫氝@種,也是采用的動態(tài)擴(kuò)容的方式來應(yīng)對雙十一。那么一個項(xiàng)目如何應(yīng)對突然的高并發(fā),我們有哪些常用的措施和處理呢?我們接下來就來看看 限流熔斷和降級
限流
比如系統(tǒng)本來可以處理1000個請求,忽然一下子來了2000個,為了不讓系統(tǒng)崩潰,最簡單的方式,就是限流,我只接1000個,超出1000個的不提供服務(wù)。
那么限流的方法有哪些呢?我們繼續(xù)看。
流量計數(shù)器
看名字就知道了,比如限制每秒的請求數(shù),比如我們每秒限1000。但是這樣有一個問題,就是這種流量計數(shù)在某個時間點(diǎn)可能是失效的。因?yàn)檫@種計數(shù)的前提是在1s內(nèi)我們假定了請求是勻速的。但是如果是這種情況,就可能起不到限流的效果。
滑動時間窗口
既然以時間為界限不行的話,我們就以時間窗口為界限,保證每個時間段內(nèi)都不能超過1000qps
漏桶算法
一個請求在被系統(tǒng)處理之前,先找一個漏桶存起來,然后再以固定速率流出,比如這個漏桶可以存1000個請求,如果漏桶中有超出1000個未被處理的請求,那么這部分請求就會溢出,也就是被丟棄,比如我們常用的消息隊(duì)列。
令牌桶算法
令牌桶就是以固定的速率產(chǎn)生,并緩存到令牌桶中,每個請求必選先獲取令牌才能系統(tǒng)處理,令牌桶存滿的時候,多余的令牌被丟棄。
nginx的限流
我們以nginx為例,看一下nginx是怎么限流的。
nginx的限流主要有兩種方式,一種是限制訪問頻率,一種是限制并發(fā)連接數(shù)。
- limit_req_zone:用來限制單位時間內(nèi)的請求數(shù),即速率限制 , 采用的漏桶算法 “l(fā)eaky bucket”。例如下面的配置
http {
# 定義限流策略 $binary_remote_addr代表限流對象,表示基于客戶端ip限流
# zone:定義內(nèi)存區(qū)大小,表示用10m的空間來存儲ip
# rate 1r/s表示每秒處理一個請求,nginx實(shí)際的管理單位時間是毫秒,其實(shí)就是 1000ms處理一個請求
limit_req_zone $binary_remote_addr zone=rateLimit:10m rate=1r/s ;
# 搜索服務(wù)的虛擬主機(jī)
server {
location / {
# 使用限流策略,burst=5,重點(diǎn)說明一下這個配置,burst 爆發(fā)的意思,這個配置的意思是設(shè)置一個大小為 5 的緩沖區(qū)(隊(duì)列)當(dāng)有大量請求(爆發(fā))過來時,
# 超過了訪問頻次限制的請求可以先放到這個緩沖區(qū)內(nèi)。nodelay,如果設(shè)置,超過訪問頻次而且緩沖區(qū)也滿了的時候就會直接返回 503,如果沒有設(shè)置,則所
# 有請求會等待排隊(duì)。
limit_req zone=rateLimit burst=5 nodelay;
}
}
}
我們1s內(nèi)發(fā)起200個請求看一下,6個請求成功了,也就是每秒1個的請求和5個緩沖區(qū)請求成功,其他的返回503
- limit_conn_zone:用來限制同一時間連接數(shù),即并發(fā)限制。
http {
# 定義限流策略
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
# 搜索服務(wù)的虛擬主機(jī)
server {
location / {
# 對應(yīng)的 key 是 $binary_remote_addr,表示限制單個 IP 同時最多能持有 1 個連接。
limit_conn perip 1;
# 對應(yīng)的 key 是 $server_name,表示虛擬主機(jī)(server) 同時能處理并發(fā)連接的總數(shù)。注意,只有當(dāng) request header 被
后端 server 處理后,這個連接才進(jìn)行計數(shù)。
limit_conn perserver 10 ;
proxy_pass http://train-manager-search ;
}
}
}
降級
我們前面看到了,限流之后有一部分的請求直接返回的503,這樣對用戶體驗(yàn)非常不好,但是我們可能有時候會看到這樣的頁面,比如 xx活動異?;鸨?,請稍后再試
等提示頁?;蛘呤悄硞€商品詳情頁,優(yōu)先推送緩存數(shù)據(jù)而非實(shí)時數(shù)據(jù),這就是服務(wù)降級。服務(wù)降級的核心是對非核心的,非關(guān)鍵的業(yè)務(wù)進(jìn)行降級。
熔斷
熔斷這個詞大家應(yīng)該是熟悉的,比如家里的保險絲,當(dāng)電流過大或者發(fā)生短路的時候,就會熔斷,從而避免發(fā)生更大的危害。服務(wù)熔斷也類似,當(dāng)被調(diào)用方出現(xiàn)故障。調(diào)用方出于自我保護(hù)的目的,主動停止調(diào)用。為什么要主動停止調(diào)用?我之前就有過這樣的經(jīng)歷,由于某個MySQL服務(wù)器性能的問題,執(zhí)行一個SQL要好幾秒。MySQL的請求越來越多,最終導(dǎo)致MySQL宕機(jī),接著php的請求也越來越多,php進(jìn)程打滿,最終php也跟著報錯。最終整個服務(wù)掛掉。
技術(shù)選型
前面我們講了限流 熔斷 和降級 ,并且用nginx演示了限流的示例。我們接下來就來看看go中,有哪些組件可以實(shí)現(xiàn)流量控制呢,這里我們挑出來兩個常見的sentinel和hystrix來對比一下,基于功能性,我們選擇了sentinel。
sentinel
Sentinel 是阿里中間件團(tuán)隊(duì)開源的,面向分布式服務(wù)架構(gòu)的輕量級高可用流量控制組件,主要以流量為切入點(diǎn),從流量控制、熔斷降級、系統(tǒng)負(fù)載保護(hù)等多個維度來幫助用戶保護(hù)服務(wù)的穩(wěn)定性??梢钥吹剑瑂entinel是2020年推出了go版本。
sentinel-golang實(shí)現(xiàn)限流
- 首先,文檔地址先找到
流控的配置規(guī)則
一條流控規(guī)則主要由下面幾個因素組成,我們可以組合這些元素來實(shí)現(xiàn)不同的限流效果:
- Resource:資源名,即規(guī)則的作用目標(biāo)。
- TokenCalculateStrategy: 當(dāng)前流量控制器的Token計算策略。Direct表示直接使用字段 Threshold 作為閾值;WarmUp表示使用預(yù)熱方式計算Token的閾值。
- ControlBehavior: 表示流量控制器的控制策略;Reject表示超過閾值直接拒絕,Throttling表示勻速排隊(duì)。
- Threshold: 表示流控閾值;如果字段 StatIntervalInMs 是1000(也就是1秒),那么Threshold就表示QPS,流量控制器也就會依據(jù)資源的QPS來做流控。
- RelationStrategy: 調(diào)用關(guān)系限流策略,CurrentResource表示使用當(dāng)前規(guī)則的resource做流控;AssociatedResource表示使用關(guān)聯(lián)的resource做流控,關(guān)聯(lián)的resource在字段 RefResource 定義;
- RefResource: 關(guān)聯(lián)的resource;
- WarmUpPeriodSec: 預(yù)熱的時間長度,該字段僅僅對 WarmUp 的TokenCalculateStrategy生效;
- WarmUpColdFactor: 預(yù)熱的因子,默認(rèn)是3,該值的設(shè)置會影響預(yù)熱的速度,該字段僅僅對 - WarmUp 的TokenCalculateStrategy生效;
- MaxQueueingTimeMs: 勻速排隊(duì)的最大等待時間,該字段僅僅對 Throttling ControlBehavior生效;
- StatIntervalInMs: 規(guī)則對應(yīng)的流量控制器的獨(dú)立統(tǒng)計結(jié)構(gòu)的統(tǒng)計周期。如果StatIntervalInMs是1000,也就是統(tǒng)計QPS。
這里特別強(qiáng)調(diào)一下 StatIntervalInMs 和 Threshold 這兩個字段,這兩個字段決定了流量控制器的靈敏度。以 Direct + Reject 的流控策略為例,流量控制器的行為就是在 StatIntervalInMs 周期內(nèi),允許的最大請求數(shù)量是Threshold。比如如果 StatIntervalInMs 是 10000,Threshold 是10000,那么流量控制器的行為就是控制該資源10s內(nèi)運(yùn)行最多10000次訪問。
接下來我們參照官方的demo,寫一個更簡單易懂的一段代碼來解釋
- 先來看一下最簡潔的丐版
package main
import (
sentinel "github.com/alibaba/sentinel-golang/api"
"github.com/alibaba/sentinel-golang/core/base"
"github.com/alibaba/sentinel-golang/core/flow"
"log"
)
func main() {
//初始化sentinel
err := sentinel.InitDefault()
if err != nil {
log.Fatalf("初始化sentinel失敗:%v", err)
}
//配置限流規(guī)則 可以根據(jù)resource配置多個規(guī)則
_, err = flow.LoadRules([]*flow.Rule{
//rule1規(guī)則:1000ms內(nèi)最多處理10個請求,多余的直接拒絕
{
Resource: "rule1", //規(guī)則的名稱
TokenCalculateStrategy: flow.Direct, //當(dāng)前流量控制器的Token計算策略。Direct表示直接使用字段 Threshold 作為閾值;WarmUp表示使用預(yù)熱方式計算Token的閾值。
ControlBehavior: flow.Reject, //表示流量控制器的控制策略;Reject表示超過閾值直接拒絕,Throttling表示勻速排隊(duì)。
Threshold: 10, //表示流控閾值;如果字段 StatIntervalInMs 是1000(也就是1秒),那么Threshold就表示QPS,流量控制器也就會依據(jù)資源的QPS來做流控。
StatIntervalInMs: 1000, //StatIntervalInMs 和 Threshold 這兩個字段,這兩個字段決定了流量控制器的靈敏度。以 Direct + Reject 的流控策略為例,流量控制器的行為就是在 StatIntervalInMs 周期內(nèi),允許的最大請求數(shù)量是Threshold。比如如果 StatIntervalInMs 是 10000,Threshold 是10000,那么流量控制器的行為就是控制該資源10s內(nèi)運(yùn)行最多10000次訪問。
},
})
if err != nil {
log.Fatalf("初始化sentinel加載限流規(guī)則失敗:%v", err)
}
//最終的限流實(shí)現(xiàn)通過這個方法實(shí)現(xiàn)
e, b := sentinel.Entry("rule1", sentinel.WithTrafficType(base.Inbound))
if b != nil {
log.Println("限流了")
} else {
log.Println("未限流")
e.Exit()
}
}
- 上面我們看到,是通過Entry來計數(shù)的,接下來我們通過for循環(huán)來,一秒內(nèi)發(fā)送20個計數(shù)看看什么樣
for i := 0; i < 20; i++ {
//最終的限流實(shí)現(xiàn)通過這個方法實(shí)現(xiàn)
e, b := sentinel.Entry("rule1", sentinel.WithTrafficType(base.Inbound))
if b != nil {
log.Printf("限流了%d\n", i)
} else {
log.Printf("未限流%d\n", i)
e.Exit()
}
}
- 我們看到結(jié)果,前10個通過,后10個限流了
預(yù)熱
當(dāng)前流量控制器的Token計算策略。Direct表示直接使用字段 Threshold 作為閾值;WarmUp表示使用預(yù)熱方式計算Token的閾值。
- WarmUp
Direct我知道了,但是預(yù)熱是什么意思呢?我們舉個例子,比如我們的流量一直是低水位的,限流配置的顆粒度也比較大,5s內(nèi)控制2000個流量,但是我們前面說過,流量計數(shù)的話,你配置了5s內(nèi)2000個,很可能在100ms內(nèi)就打進(jìn)來2000個請求,那系統(tǒng)可能就奔潰了。所以sentinel提供了這種WarmUp 方式,即預(yù)熱/冷啟動方式。通過"冷啟動",讓通過的流量緩慢增加,在一定時間內(nèi)逐漸增加到閾值上限,給冷系統(tǒng)一個預(yù)熱的時間,避免冷系統(tǒng)被壓垮。 - 相關(guān)代碼如下
func main() {
//初始化sentinel
err := sentinel.InitDefault()
if err != nil {
log.Fatalf("初始化sentinel失敗:%v", err)
}
//配置限流規(guī)則 可以根據(jù)resource配置多個規(guī)則
_, err = flow.LoadRules([]*flow.Rule{
//rule1規(guī)則:1000ms內(nèi)最多處理10個請求,多余的直接拒絕
{
Resource: "rule1", //規(guī)則的名稱
TokenCalculateStrategy: flow.WarmUp, //冷啟動
ControlBehavior: flow.Throttling, //表示流量控制器的控制策略;Reject表示超過閾值直接拒絕,Throttling表示勻速排隊(duì)。
Threshold: 1000, //表示流控閾值;如果字段 StatIntervalInMs 是1000(
// 也就是1秒),那么Threshold就表示QPS,流量控制器也就會依據(jù)資源的QPS來做流控。
//配置預(yù)熱時長
WarmUpPeriodSec: 20, //30s內(nèi)達(dá)到1000
},
})
if err != nil {
log.Fatalf("初始化sentinel加載限流規(guī)則失敗:%v", err)
}
ch := make(chan int) //阻塞程序
var globalTotal, passTotal, blockTotal int64
var perGlobalTotal, perPassTotal, perBlockTotal int64
var globalTotalOld, passTotalOld, blockTotalOld int64
//在每一秒統(tǒng)計一次 一秒內(nèi)通過了多少,總共有多少,block了多少
for i := 0; i < 100; i++ {
go func() {
for {
//10ms執(zhí)行一次
time.Sleep(1 * 10)
globalTotal++
//最終的限流實(shí)現(xiàn)通過這個方法實(shí)現(xiàn)
e, b := sentinel.Entry("rule1", sentinel.WithTrafficType(base.Inbound))
if b != nil {
blockTotal++
} else {
passTotal++
e.Exit()
}
}
}()
}
//每秒打印一下統(tǒng)計數(shù)
go func() {
nowSec := 1
for {
time.Sleep(1 * time.Second)
perGlobalTotal = globalTotal - globalTotalOld
perPassTotal = passTotal - passTotalOld
perBlockTotal = blockTotal - blockTotalOld
log.Printf("第%d秒接收請求數(shù):%d,通過請求數(shù):%d,block請求數(shù):%d", nowSec, perGlobalTotal, perPassTotal, perBlockTotal)
globalTotalOld = globalTotal
passTotalOld = passTotal
blockTotalOld = blockTotal
nowSec++
}
}()
<-ch
}
- 20s內(nèi)逐漸提升至1000
Throttling勻速通過
字段 ControlBehavior 表示表示流量控制器的控制行為,目前 Sentinel 支持兩種控制行為:
Reject:表示如果當(dāng)前統(tǒng)計周期內(nèi),統(tǒng)計結(jié)構(gòu)統(tǒng)計的請求數(shù)超過了閾值,就直接拒絕。
Throttling:表示勻速排隊(duì)的統(tǒng)計策略。它的中心思想是,以固定的間隔時間讓請求通過。當(dāng)請求到來的時候,如果當(dāng)前請求距離上個通過的請求通過的時間間隔不小于預(yù)設(shè)值,則讓當(dāng)前請求通過;否則,計算當(dāng)前請求的預(yù)期通過時間,如果該請求的預(yù)期通過時間小于規(guī)則預(yù)設(shè)的 timeout 時間,則該請求會等待直到預(yù)設(shè)時間到來通過(排隊(duì)等待處理);若預(yù)期的通過時間超出最大排隊(duì)時長,則直接拒接這個請求。
勻速排隊(duì)方式會嚴(yán)格控制請求通過的間隔時間,也即是讓請求以均勻的速度通過,對應(yīng)的是漏桶算法。該方式的作用如下圖所示:
這種方式主要用于處理間隔性突發(fā)的流量,例如消息隊(duì)列。想象一下這樣的場景,在某一秒有大量的請求到來,而接下來的幾秒則處于空閑狀態(tài),我們希望系統(tǒng)能夠在接下來的空閑期間逐漸處理這些請求,而不是在第一秒直接拒絕多余的請求。
比如我們設(shè)置一個1秒內(nèi)允許通過2個,來試一下,相關(guān)代碼如下
func main() {
ch := make(chan int)
//初始化sentinel
err := sentinel.InitDefault()
if err != nil {
log.Fatalf("初始化sentinel失敗:%v", err)
}
//配置限流規(guī)則 可以根據(jù)resource配置多個規(guī)則
_, err = flow.LoadRules([]*flow.Rule{
//rule1規(guī)則:1000ms內(nèi)最多處理10個請求,多余的直接拒絕
{
Resource: "rule1", //規(guī)則的名稱
TokenCalculateStrategy: flow.Direct, //當(dāng)前流量控制器的Token計算策略。Direct表示直接使用字段 Threshold 作為閾值;WarmUp表示使用預(yù)熱方式計算Token的閾值。
ControlBehavior: flow.Throttling, //表示流量控制器的控制策略;Reject表示超過閾值直接拒絕,Throttling表示勻速排隊(duì)。
Threshold: 2, //表示流控閾值;如果字段 StatIntervalInMs 是1000(
// 也就是1秒),那么Threshold就表示QPS,流量控制器也就會依據(jù)資源的QPS來做流控。
StatIntervalInMs: 1000, //StatIntervalInMs 和 Threshold 這兩個字段,這兩個字段決定了流量控制器的靈敏度。以 Direct + Reject 的流控策略為例,流量控制器的行為就是在 StatIntervalInMs 周期內(nèi),允許的最大請求數(shù)量是Threshold。比如如果 StatIntervalInMs 是 10000,Threshold 是10000,那么流量控制器的行為就是控制該資源10s內(nèi)運(yùn)行最多10000次訪問。
},
})
if err != nil {
log.Fatalf("初始化sentinel加載限流規(guī)則失敗:%v", err)
}
go func() {
for {
stamp := time.Now().UnixMicro()
//最終的限流實(shí)現(xiàn)通過這個方法實(shí)現(xiàn)
e, b := sentinel.Entry("rule1", sentinel.WithTrafficType(base.Inbound))
if b != nil {
//log.Printf("限流了%d\n",stamp )
} else {
log.Printf("未限流%d\n", stamp)
e.Exit()
}
}
}()
<-ch
}
sentinel-golang實(shí)現(xiàn)熔斷
- 熔斷器模型
Sentinel 熔斷降級基于熔斷器模式 (circuit breaker pattern) 實(shí)現(xiàn)。熔斷器內(nèi)部維護(hù)了一個熔斷器的狀態(tài)機(jī),狀態(tài)機(jī)的轉(zhuǎn)換關(guān)系如下圖所示:
-
靜默期:熔斷器的靜默期是指在系統(tǒng)檢測到異常后的一段時間內(nèi),熔斷器暫時停止對該異常進(jìn)行處理或服務(wù)的響應(yīng)。這個時間段是為了讓系統(tǒng)有時間自我恢復(fù)或避免過度頻繁地觸發(fā)熔斷。
-
靜默數(shù):在熔斷器的靜默期內(nèi),系統(tǒng)允許的異常發(fā)生次數(shù)。當(dāng)異常發(fā)生的次數(shù)超過了靜默數(shù),熔斷器可能會觸發(fā)并采取相應(yīng)的措施,例如暫時中止服務(wù)以防止進(jìn)一步的問題。這有助于保護(hù)系統(tǒng)免受潛在的破壞性異常的影響。
Sentinel 支持以下幾種熔斷策略:
- 慢調(diào)用比例策略 (SlowRequestRatio):Sentinel 的熔斷器不在靜默期,并且慢調(diào)用的比例大于設(shè)置的閾值,則接下來的熔斷周期內(nèi)對資源的訪問會自動地被熔斷。該策略下需要設(shè)置允許的調(diào)用 RT 臨界值(即最大的響應(yīng)時間),對該資源訪問的響應(yīng)時間大于該閾值則統(tǒng)計為慢調(diào)用。
- 錯誤比例策略 (ErrorRatio):Sentinel 的熔斷器不在靜默期,并且在統(tǒng)計周期內(nèi)資源請求訪問異常的比例大于設(shè)定的閾值,則接下來的熔斷周期內(nèi)對資源的訪問會自動地被熔斷。
- 錯誤計數(shù)策略 (ErrorCount):Sentinel 的熔斷器不在靜默期,并且在統(tǒng)計周期內(nèi)資源請求訪問異常數(shù)大于設(shè)定的閾值,則接下來的熔斷周期內(nèi)對資源的訪問會自動地被熔斷。
- 注意:這里的錯誤比例熔斷和錯誤計數(shù)熔斷指的業(yè)務(wù)返回錯誤的比例或則計數(shù)。也就是說,如果規(guī)則指定熔斷器策略采用錯誤比例或則錯誤計數(shù),那么為了統(tǒng)計錯誤比例或錯誤計數(shù),需要調(diào)用API: api.TraceError(entry, err) 埋點(diǎn)每個請求的業(yè)務(wù)異常。
我們先通過簡單的丐版代碼來看一下
package main
import (
"errors"
"fmt"
"log"
"math/rand"
"time"
sentinel "github.com/alibaba/sentinel-golang/api"
"github.com/alibaba/sentinel-golang/core/circuitbreaker"
)
func main() {
err := sentinel.InitDefault()
if err != nil {
log.Fatalf("初始化sentinel失敗:%v", err)
}
ch := make(chan int)
//定制熔斷規(guī)則
_, err = circuitbreaker.LoadRules([]*circuitbreaker.Rule{
{
Resource: "abc", //熔斷器的名字
Strategy: circuitbreaker.ErrorCount, //熔斷策略:錯誤計數(shù)策略
RetryTimeoutMs: 3000, //3s內(nèi)嘗試恢復(fù)
MinRequestAmount: 10, //靜默數(shù)
StatIntervalMs: 5000, //5s內(nèi)統(tǒng)計
StatSlidingWindowBucketCount: 10,
Threshold: 50, //50個錯誤數(shù)
},
})
if err != nil {
log.Fatal(err)
}
var total, pass, block, errnum int64
go func() {
for {
now := time.Now().Second()
total++
e, b := sentinel.Entry("abc")
if b != nil {
block++
fmt.Printf("熔斷了,相關(guān)參數(shù) total:%d,pass:%d,block:%d,errnum:%d,time:%d\n", total, pass, block, errnum, now)
time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond)
} else {
//假設(shè)這里我調(diào)用了redis服務(wù),如果返回錯誤,那么我將trace error
if getRedis() != nil {
errnum++
sentinel.TraceError(e, errors.New("redis error"))
}
pass++
time.Sleep(time.Duration(rand.Uint64()%80+10) * time.Millisecond)
e.Exit()
}
}
}()
go func() {
for {
now := time.Now().Second()
fmt.Printf("total:%d,pass:%d,block:%d,errnum:%d,time:%d\n", total, pass, block, errnum, now)
time.Sleep(time.Second * 1)
}
}()
<-ch
}
// 此方法模擬調(diào)用redis
func getRedis() error {
if rand.Uint64()%20 > 9 {
return errors.New("i am error")
}
return nil
}
我們來看一下執(zhí)行結(jié)果,5s內(nèi)鏈接失敗超過50個,開始熔斷
但是我們還看到如下圖的結(jié)果,說明熔斷之后服務(wù)嘗試去恢復(fù)了。這部分流量叫做探測流量。
我們前面說了,熔斷器有開啟狀態(tài),關(guān)閉狀態(tài)和半開狀態(tài),這種狀態(tài)我們怎么監(jiān)聽呢,我們接下來給熔斷器加上監(jiān)聽功能,主要代碼如下文章來源:http://www.zghlxwxcb.cn/news/detail-806034.html
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: %d, 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(){
//.......
//增加監(jiān)聽
circuitbreaker.RegisterStateChangeListeners(&stateChangeTestListener{})
//.......
}
我們來看一下執(zhí)行結(jié)果,初始狀態(tài)closed到open
從open到halfopen到closed文章來源地址http://www.zghlxwxcb.cn/news/detail-806034.html
在gin中使用sentinel
- 我在middleware里面實(shí)現(xiàn)限流方法如下
func FlowControl(c *gin.Context) {
//初始化sentinel
err := sentinel.InitDefault()
if err != nil {
log.Fatalf("初始化sentinel失敗:%v", err)
}
//配置限流規(guī)則 可以根據(jù)resource配置多個規(guī)則
_, err = flow.LoadRules([]*flow.Rule{
//rule1規(guī)則:1000ms內(nèi)最多處理2個請求,多余的直接拒絕
{
Resource: "rule1", //規(guī)則的名稱
TokenCalculateStrategy: flow.Direct, //當(dāng)前流量控制器的Token計算策略。Direct表示直接使用字段 Threshold 作為閾值;WarmUp表示使用預(yù)熱方式計算Token的閾值。
ControlBehavior: flow.Reject, //表示流量控制器的控制策略;Reject表示超過閾值直接拒絕,Throttling表示勻速排隊(duì)。
Threshold: 2, //表示流控閾值;如果字段 StatIntervalInMs 是1000(
// 也就是1秒),那么Threshold就表示QPS,流量控制器也就會依據(jù)資源的QPS來做流控。
StatIntervalInMs: 1000, //StatIntervalInMs 和 Threshold 這兩個字段,這兩個字段決定了流量控制器的靈敏度。以 Direct + Reject 的流控策略為例,流量控制器的行為就是在 StatIntervalInMs 周期內(nèi),允許的最大請求數(shù)量是Threshold。比如如果 StatIntervalInMs 是 10000,Threshold 是10000,那么流量控制器的行為就是控制該資源10s內(nèi)運(yùn)行最多10000次訪問。
},
})
if err != nil {
log.Fatalf("初始化sentinel加載限流規(guī)則失敗:%v", err)
}
//最終的限流實(shí)現(xiàn)通過這個方法實(shí)現(xiàn)
e, b := sentinel.Entry("rule1", sentinel.WithTrafficType(base.Inbound))
if b != nil {
log.Println("被限流了\n")
c.JSON(403, gin.H{"code": 403, "msg": "限流了"})
} else {
log.Println("未限流\n")
c.Next()
e.Exit()
}
}
- 在路由中指定使用中間件
// 簡單組: v1
v1 := router.Group("/v1")
v1.Use(middleware.TraceLog)
v1.Use(middleware.FlowControl)
{
v1.GET("/blog/detail", handlers.BlogDetail)
}
- 通過jmeter發(fā)送20個請求,并查看結(jié)果
- 查看打印日志
到了這里,關(guān)于聊一聊服務(wù)治理三板斧:限流、熔斷、降級和go-sentinel的實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!