go-zero源码阅读-熔断器#第二期

熔断器

入口源码地址:github.com/zeromicro/go-zero/rest/handler/breakerhandler.go

go-zero 的熔断器基于滑动窗口来实现,我们首先来看看滑动窗口是啥

leetcode 中有这样一个题目:剑指 Offer II 041. 滑动窗口的平均值

给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算滑动窗口里所有数字的平均值。

实现 MovingAverage 类:

MovingAverage(int size) 用窗口大小 size 初始化对象。
double next(int val) 成员函数 next 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 size 个值的移动平均值,即滑动窗口里所有数字的平均值。

示例:

输入:
inputs = ["MovingAverage", "next", "next", "next", "next"]
inputs = [[3], [1], [10], [3], [5]]
输出:
[null, 1.0, 5.5, 4.66667, 6.0]

解释:
MovingAverage movingAverage = new MovingAverage(3);
movingAverage.next(1); // 返回 1.0 = 1 / 1
movingAverage.next(10); // 返回 5.5 = (1 + 10) / 2
movingAverage.next(3); // 返回 4.66667 = (1 + 10 + 3) / 3
movingAverage.next(5); // 返回 6.0 = (10 + 3 + 5) / 3

我们来想一想解题思路:

  • 窗口大小是固定的
  • 窗口每次都会滑动
  • 窗口滑动是替换就数据

我们来解一解题:

type MovingAverage struct {
	index   int   // 当前环形数组的位置
	count   int   // 数组大小
	sum     int   // 数据总量
	buckets []int // 环形数组
}

/** Initialize your data structure here. */
func Constructor(size int) MovingAverage {
	return MovingAverage{index: size - 1, buckets: make([]int, size)}
}

func (ma *MovingAverage) Next(val int) float64 {
	ma.sum += val
	ma.index = (ma.index + 1) % len(ma.buckets) // 循环数组索引
	if ma.count < len(ma.buckets) {
		ma.count++
		ma.buckets[ma.index] = val
	} else {
		ma.sum -= ma.buckets[ma.index] // 减去旧数据
		ma.buckets[ma.index] = val     // 替换旧数据
	}
	return float64(ma.sum) / float64(ma.count)
}

func Test_Demo(t *testing.T) {
	ma := Constructor(3)
	fmt.Println(ma.Next(1))  // 返回 1.0 = 1 / 1
	fmt.Println(ma.Next(10)) // 返回 5.5 = (1 + 10) / 2
	fmt.Println(ma.Next(3))  // 返回 4.66667 = (1 + 10 + 3) / 3
	fmt.Println(ma.Next(5))  // 返回 6.0 = (10 + 3 + 5) / 3
}

从解题的代码中我们可以看到滑动窗口的本质是循环数组,而循环数组的核心思路是

  1. 循环数组的索引
ma.index = (ma.index + 1) % len(ma.cache) // 循环数组索引
  1. 新数据替换旧数据
ma.sum -= ma.cache[ma.index] // 减去旧数据
ma.cache[ma.index] = val     // 替换旧数据

再来看看 go-zero 的 rollingwidnow,是不是和前面学习的滑动窗口是一样一样的呀 : )

type window struct {
	buckets []*Bucket // 环形数组
	size    int
}

// 初始化窗口
func newWindow(size int) *window {
	buckets := make([]*Bucket, size)
	for i := 0; i < size; i++ {
		buckets[i] = new(Bucket)
	}
	return &window{
		buckets: buckets,
		size:    size,
	}
}

// 往执行的 bucket 加入指定的指标数据
func (w *window) add(offset int, v float64) {
    // 窗口滑动代码
    // rw.offset = (offset + span) % rw.size
	w.buckets[offset%w.size].add(v)
}

滑动窗口看完了,我们再来看看柳暗花明又一村的Google Sre过载保护算法

其算法数学表达式如下:

client_rejection2

  • requests:请求数量(调用方发起请求的数量总和)
  • accepts:请求接受数量(被调用方正常处理的请求数量)
  • K:倍值(越小越敏感)

go-zero 算法代码实现

// 判断是否触发熔断
func (b *googleBreaker) accept() error {
	accepts, total := b.History()
	weightedAccepts := b.k * float64(accepts)
	// Google Sre过载保护算法 https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
	dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
	if dropRatio <= 0 {
		return nil
	}

	if b.proba.TrueOnProba(dropRatio) {
		return ErrServiceUnavailable
	}

	return nil
}

go-zero 熔断器给我们提供如下方法,更我们使用:

type (
	// 自定义判定执行结果
	Acceptable func(err error) bool
	// 手动回调
	Promise interface {
		// Accept tells the Breaker that the call is successful.
		// 请求成功
		Accept()
		// Reject tells the Breaker that the call is failed.
		// 请求失败
		Reject(reason string)
	}
	Breaker interface {
		// 熔断器名称
		Name() string

		// 熔断方法,执行请求时必须手动上报执行结果
		// 适用于简单无需自定义快速失败,无需自定义判定请求结果的场景
		// 相当于手动挡。。。
		Allow() (Promise, error)

		// 熔断方法,自动上报执行结果
		// 自动挡。。。
		Do(req func() error) error

		// 熔断方法
		// acceptable - 支持自定义判定执行结果
		DoWithAcceptable(req func() error, acceptable Acceptable) error

		// 熔断方法
		// fallback - 支持自定义快速失败
		DoWithFallback(req func() error, fallback func(err error) error) error

		// 熔断方法
		// fallback - 支持自定义快速失败
		// acceptable - 支持自定义判定执行结果
		DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error
	}
)

关于 go-zero 熔断器的文章就到这里啦,看完之后是不是觉得很简单,觉得不简单可以多读几遍,感谢大家的阅读。

熔断器保姆级带注释源码,点击可得

引用文章:

go-zero是一个开源的Go语言框架,它在构建微服务和高并发应用方面具有突破性的优势。其中一个突出的特点就是它整合了masterminds/squirrel,从而实现了优雅的多数据库支持。 masterminds/squirrel是一个流行的SQL查询构建器,它以非常直观和灵活的方式提供了编写SQL查询的功能。而go-zero在此基础上做了进一步的封装和优化,使得使用者能够更加方便地编写和执行SQL查询。 首先,go-zero提供了一组简洁而强大的API,使得构建SQL查询非常容易。开发者只需要按照一定的约定来创建查询参数和条件,然后使用go-zero提供的API来构建查询语句,即可完成复杂的SQL查询。 其次,go-zero还增加了一些高级功能,进一步提升了多数据库查询的灵活性和性能。例如,它支持数据库连接池管理,可以动态调整数据库连接数以适应并发请求;还支持分表分库功能,可以按照一定的规则将数据分散存储在不同的数据库或表中,从而提高查询效率。 最重要的是,go-zero通过内置的代码生成工具,提供了自动化生成数据库访问代码的能力。开发者只需要定义数据表的结构,然后运行代码生成工具,就能够自动生成包含增删改查等一系列数据库操作的代码。这极大地提高了开发效率,减少了出错的机会。 综上所述,go-zero整合了masterminds/squirrel,通过提供简洁强大的API、高级功能和自动化代码生成工具,实现了优雅的多数据库支持。它在微服务和高并发应用场景下的表现突出,为开发者提供了极大的便利和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值