go-zero过载保护源码解读

 更新时间:2023年08月10日 11:11:51   作者:TTSimple  
这篇文章主要为大家介绍了go-zero过载保护源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

在看文章之前可以看看万总的这篇文章《服务自适应降载保护设计》,文章已经给我们介绍很清楚了,从基础原理到架构需求再到代码注释,无不细致入微,感谢万总。

之前在设计架构的时候对于服务过载保护只会想到在客户端、网关层来实现,没考虑过在服务端也可以达到这种效果,一来涉及这种技术的文章较少(可能是我孤陋寡闻了),二来服务端不确定的情况比较多,比如服务器出现问题,或者其他在同一台服务器运行的软件把服务器直接搞挂,这样在服务端实现过载保护在某些层面来说鲁棒性可能不太好 ,但在和熔断器结合后,用服务端来实现过载保护也是合情合理的。

我们来看下过载保护设计到的几个算法

自旋锁

  • 原理

问:假设有1个变量lock,2个协程怎么用锁实现lock++lock的结果最后为2

答:

  • 锁也是1个变量,初值设为0;
  • 1个协程将锁原子性的置为1;
  • 操作变量lock
  • 操作完成后,将锁原子性的置为0,释放锁。
  • 在1个协程获取锁时,另一个协程一直尝试,直到能够获取锁(不断循环),这就是自旋锁。

自旋锁的缺点

  • 某个协程持有锁时间长,等待的协程一直在循环等待,消耗CPU资源。
  • 不公平,有可能存在有的协程等待时间过程,出现线程饥饿(这里就是协程饥饿)

go-zero 自旋锁源码

type SpinLock struct {
    // 锁变量
    lock uint32
}
// Lock locks the SpinLock.
func (sl *SpinLock) Lock() {
    for !sl.TryLock() {
        // 暂停当前goroutine,让其他goroutine先行运算
        runtime.Gosched()
    }
}
// TryLock tries to lock the SpinLock.
func (sl *SpinLock) TryLock() bool {
    // 原子交换,0换成1
    return atomic.CompareAndSwapUint32(&sl.lock, 0, 1)
}
// Unlock unlocks the SpinLock.
func (sl *SpinLock) Unlock() {
    // 原子置零
    atomic.StoreUint32(&sl.lock, 0)
}

源码中还使用了 golang 的运行时操作包 runtime

runtime.Gosched()暂停当前goroutine,让其他goroutine先行运算

注意:只是暂停,不是挂起。

当时间片轮转到该协程时,Gosched()后面的操作将自动恢复

我们来写写几行代码,看看他的作用是啥

func output(s string) {
    for i := 0; i < 3; i++ {
        fmt.Println(s)
    }
}
// 未使用Gosched的代码
func Test_GoschedDisable(t *testing.T) {
    go output("goroutine 2")
    output("goroutine 1")
}
// === RUN   Test_GoschedDisable
// goroutine 1
// goroutine 1
// goroutine 1
// --- PASS: Test_GoschedDisable (0.00s)

小结

还没等到子协程执行,主协程就已经执行完退出了,子协程将不再执行,所以打印的全部是主协程的数据。当然,实际上这个执行结果也是不确定的,只是大概率出现以上输出,因为主协程和子协程间并没有绝对的顺序关系

func output(s string) {
    for i := 0; i < 3; i++ {
        fmt.Println(s)
    }
}
// 使用Gosched的代码
func Test_GoschedEnable(t *testing.T) {
    go output("goroutine 2")
    runtime.Gosched()
    output("goroutine 1")
}
// === RUN   Test_GoschedEnable
// goroutine 2
// goroutine 2
// goroutine 2
// goroutine 1
// goroutine 1
// goroutine 1
// --- PASS: Test_GoschedEnable (0.00s)

结论:在打印goroutine 1之前,主协程调用了runtime.Gosched()方法,暂停了主协程。子协程获得了调度,从而先行打印了goroutine 2。主协程不是一定要等其他协程执行完才会继续执行,而是一定时间。如果这个时间内其他协程没有执行完,那么主协程将继续执行,例如以下例子

func output(s string) {
    for i := 0; i < 3; i++ {
        fmt.Println(s)
    }
}
// 使用Gosched的代码,并故意延长子协程的执行时间,看主协程是否一直等待
func Test_GoschedEnableAndSleep(t *testing.T) {
    go func() {
        time.Sleep(5000)
        output("goroutine 2")
    }()
    runtime.Gosched()
    output("goroutine 1")
}
// === RUN   Test_GoschedEnableAndSleep
// goroutine 2
// goroutine 2
// goroutine 2
// goroutine 1
// goroutine 1
// goroutine 1
// --- PASS: Test_GoschedEnableAndSleep (0.00s)

结论:即使我们故意延长子协程的执行时间,主协程还是会一直等待子协程执行完才会执行。

源码中还使用了 golang 的原子操作包 atomic

atomic.CompareAndSwapUint32()函数用于对uint32值执行比较和交换操作,此函数是并发安全的。

// addr 表示地址
// old  表示uint32值,它是旧的,
// new  表示uint32新值,它将与旧值交换自身。
// 如果交换完成,则返回true,否则返回false。
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)

atomic.StoreUint32() 函数用于将val原子存储到* addr中,此函数是并发安全的。

// addr 表示地址
// val  表示uint32值,它是旧的,
func StoreUint32(addr *uint32, val uint32)

过载保护核心还使用了滑动窗口,滑动窗口的原理和细节可以看前一篇文章,里面有详细解答。

以上就是go-zero过载保护源码解读的详细内容,更多关于go-zero过载保护的资料请关注脚本之家其它相关文章!

相关文章

  • golang实现大文件上传功能全过程

    golang实现大文件上传功能全过程

    Go语言可以用来实现大文件传输,下面这篇文章主要给大家介绍了关于golang实现大文件上传功能的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • go语言结构体指针操作示例详解

    go语言结构体指针操作示例详解

    这篇文章主要为大家介绍了go语言结构体指针操作示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • 一文搞懂Golang 值传递还是引用传递

    一文搞懂Golang 值传递还是引用传递

    最多人犯迷糊的就是 slice、map、chan 等类型,都会认为是 “引用传递”,从而认为 Go 语言的 xxx 就是引用传递。正因为它们还引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据,这篇文章主要介绍了Golang 值传递还是引用传递,需要的朋友可以参考下
    2023-01-01
  • 详解golang 定时任务time.Sleep和time.Tick实现结果比较

    详解golang 定时任务time.Sleep和time.Tick实现结果比较

    本文主要介绍了golang 定时任务time.Sleep和time.Tick实现结果比较,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • 深入分析Go 实现 MySQL 数据库事务

    深入分析Go 实现 MySQL 数据库事务

    本文深入分析了Go语言实现MySQL数据库事务的原理和实现方式,包括事务的ACID特性、事务的隔离级别、事务的实现方式等。同时,本文还介绍了Go语言中的事务处理机制和相关的API函数,以及如何使用Go语言实现MySQL数据库事务。
    2023-06-06
  • Golang内存泄露场景与定位方式的实现

    Golang内存泄露场景与定位方式的实现

    Golang有自动垃圾回收机制,但是仍然可能会出现内存泄漏的情况,本文主要介绍了Golang内存泄露场景与定位方式的实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-04-04
  • Go语言入门之函数的定义与使用

    Go语言入门之函数的定义与使用

    函数是一段代码的片段,包含连续的执行语句,它可以将零个或多个输入参数映射到零个或多个参数输出。本文将通过示例和大家详细聊聊Go语言中函数的定义与使用,感兴趣的可以了解一下
    2022-11-11
  • GO中的时间操作总结(time&dateparse)

    GO中的时间操作总结(time&dateparse)

    日常开发过程中,对于时间的操作可谓是无处不在,但是想实现时间自由还是不简单的,多种时间格式容易混淆,本文为大家整理了一下GO中的时间操作,有需要的可以参考下
    2023-09-09
  • Go语言使用钉钉机器人推送消息的实现示例

    Go语言使用钉钉机器人推送消息的实现示例

    本文主要介绍了Go语言使用钉钉机器人推送消息的实现示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • Golang切片连接成字符串的实现示例

    Golang切片连接成字符串的实现示例

    本文主要介绍了Golang切片连接成字符串的实现示例,可以使用Go语言中的内置函数"String()"可以将字节切片转换为字符串,具有一定的参考价值,感兴趣的可以了解一下
    2023-11-11

最新评论