首页 >后端开发 >Golang >Go 应用程序中的断路器

Go 应用程序中的断路器

王林
王林原创
2024-09-03 14:15:111064浏览

如今,我们的应用程序具有多个依赖项是很常见的,尤其是在微服务环境中工作时。当我们的应用程序报告错误时,我们发现一个依赖项已关闭,这种情况并不罕见。

提高我们恢复能力的一个好习惯是关闭与那些表现不佳的应用程序的通信。看看其他领域,我们从电气工程中学到了断路器的概念,即当发生故障时开关会关闭。在巴西,所有房屋都有这些开关,如果我们的电网不稳定,这些开关会自动关闭。

在计算机科学中,我们的断路器有点复杂,因为它也有一个中间状态。下图详细解释了其工作原理:

Circuit Breaker in Go apps

简而言之,可能的状态有:

  • open:应用程序之间没有通信。当达到此状态时,计时器启动,允许重新建立依赖关系。当计时器结束时,我们将进入半开状态。
  • 关闭:应用程序之间存在通信。每次请求失败时,都会更新计数器。如果达到故障阈值,我们会将电路移至开路状态。
  • 半开:这是一种治愈状态,直到我们可以照常工作。在此过程中,如果我们达到成功阈值,我们就会关闭。如果请求仍然失败,我们将重新开放。

很酷,对吧?为了更好地解释这个概念,为什么不创建一个呢?

首先,让我们构建我们的服务A。它将负责接收所有请求,换句话说,它将是我们的主应用程序所依赖的服务。为了简化,我们将公开两个端点:始终响应 200 的 /success 和始终响应 500 的 /failure。

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/success", func(w http.ResponseWriter, r *http.Request) { 
    w.WriteHeader(http.StatusOK) })
    http.HandleFunc("/failure", func(w http.ResponseWriter, r *http.Request) { 
    w.WriteHeader(http.StatusInternalServerError) })

    fmt.Println("Server is running at http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

我们的服务 B 将负责调用服务 A 并构建我们的断路器。 Go 社区已经拥有已经实现了该模式的 lib gobreaker。首先,我们定义断路器属性:

var st gobreaker.Settings
st.Name = "Circuit Breaker PoC"
st.Timeout = time.Second * 5
st.MaxRequests = 2
st.ReadyToTrip = func(counts gobreaker.Counts) bool {
    return counts.ConsecutiveFailures >= 1
}

即使该库允许我们自定义更多属性,我们也只会关注三个:

  • 超时:处于打开状态的时间长度。在此示例中,我们选择五秒。
  • MaxRequests:关闭之前有多少个成功请求。在此示例中,我们决定了两个请求。
  • ReadyToTrip:定义从关闭状态变为打开状态的条件。简单来说,一次失败就够了。

现在我们只需启动断路器并发送请求:

cb := gobreaker.NewCircuitBreaker[int](st)

url := "http://localhost:8080/success"
cb.Execute(func() (int, error) { return Get(url) })
fmt.Println("Circuit Breaker state:", cb.State()) // closed!

url = "http://localhost:8080/failure"
cb.Execute(func() (int, error) { return Get(url) })
fmt.Println("Circuit Breaker state:", cb.State()) // open!

time.Sleep(time.Second * 6)
url = "http://localhost:8080/success"
cb.Execute(func() (int, error) { return Get(url) })
fmt.Println("Circuit Breaker state:", cb.State()) // half-open!

url = "http://localhost:8080/success"
cb.Execute(func() (int, error) { return Get(url) })
fmt.Println("Circuit Breaker state:", cb.State()) // closed!

我们可以注意到 gobreaker 的工作原理就像函数的包装器。如果函数返回错误,则会增加失败计数器,如果没有,则会增加成功计数器。让我们定义该函数:

func Get(url string) (int, error) {
    r, _ := http.Get(url)

    if r.StatusCode != http.StatusOK {
        return r.StatusCode, fmt.Errorf("failed to get %s", url)
    }

    return r.StatusCode, nil
}

这就是我们如何拥有一个带有断路器的 Go 应用程序!使用此模式时,您可以通过提高应用程序对依赖项故障的容忍度来提高应用程序的弹性。此外,使用这个库消除了大部分复杂性,使我们更容易在日常应用程序中采用该模式。如果您想查看此概念验证的代码,请在此处查看。

如果您仍然对其他弹性模式感到好奇,Elton Minetto 还发表了一篇关于它的精彩博客文章!

您也可以在我的个人博客上查看这篇文章和其他帖子。请在评论中告诉我您对这篇博文的看法,并提出一个问题:您以前使用过断路器吗?

以上是Go 应用程序中的断路器的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn