前言
go-zero 群里常常有同学问:
服务监控是通过什么算法实现的?
滑动窗口是怎么工作的?是否讲讲这块的原理?
熔断算法是怎么设计的?为啥没有半开半闭状态呢?
go-zero
指标怎么统计
breaker
type googleBreaker struct {
k float64
stat *collection.RollingWindow
proba *mathx.Proba
}
go-zerobreaker
breaker
func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
...
// 执行理论申请函数
err := req()
if acceptable(err) {
// 理论执行:b.stat.Add(1)
// 也就是说:外部指标统计胜利+1
b.markSuccess()
} else {
// 原理同上
b.markFailure()
}
return err
}
所以其实底层说白了就是:申请执行结束,会依据谬误产生次数,外部的统计数据结构会相应地加上统计值(可正可负)。同时随着工夫迁徙,统计值也须要随工夫进化。
简略来说:工夫序列内存数据库【也没数据库这么猛,就是一个存储,只是一个内存版的】
上面就来说说这个工夫序列用什么数据结构组织的。
滑动窗口
rollingwindow
type RollingWindow struct {
lock sync.RWMutex
size int
win *window
interval time.Duration
offset int
ignoreCurrent bool
lastTime time.Duration
}
window
rollingwindow
SumCountbreakeracceptstotal
滑动是怎么产生的
breakerbucketbucket
Bucket
bucketbucket
bucketbucketbucket
rollingwindowbucket
breakerb.stat.Add(1)
func (rw *RollingWindow) Add(v float64) {
rw.lock.Lock()
defer rw.lock.Unlock()
// 滑动的动作产生在此
rw.updateOffset()
rw.win.add(rw.offset, v)
}
func (rw *RollingWindow) updateOffset() {
span := rw.span()
if span <= 0 {
return
}
offset := rw.offset
// 重置过期的 bucket
for i := 0; i < span; i++ {
rw.win.resetBucket((offset + i + 1) % rw.size)
}
rw.offset = (offset + span) % rw.size
now := timex.Now()
// 更新工夫
rw.lastTime = now - (now-rw.lastTime)%rw.interval
}
func (w *window) add(offset int, v float64) {
// 往执行的 bucket 退出指定的指标
w.buckets[offset%w.size].add(v)
}
Add(delta)bucket
updateOffsetbucketbucketbucketbucket intervalbucketresetoffsetbucketlastTimeoffsetbucket
bucket
bucketlastTimebuckettimex.Since(rw.lastTime) / rw.interval
Add()lastTimenowTime
总结
go-zerorollingWindowstore/redis
滑动窗口实用于流控中对指标进行计算,同时也能够做到控流。
go-zero
我的项目地址
https://github.com/tal-tech/go-zero
欢送应用 go-zero 并 star 反对咱们!
微信交换群
关注『微服务实际』公众号并点击 交换群 获取社区群二维码。
go-zero 系列文章见『微服务实际』公众号