Go每日一库之106:hystrix(熔断)

发布时间 2023-09-29 21:01:13作者: 阿瑞娜

背景

随着微服务的流行,服务之间的调用可能变得越来越复杂,一个业务流程可能需要调用五六个甚至更多服务,这就会导致,假设某个服务出现问题,严重可能出现服务器负载过高,导致服务雪崩的现象。
因此为了防止此现象的发生,就需要考虑服务熔断机制,根据自身业务的需求,将其应用到服务中。

什么是熔断:可以联想到我们家里的电表的保险丝,当电压负载过高后,保险丝熔断,确保家里的电器等其他安全。

熔断器

在我们的服务中,当我们当用第三方服务时失败到达一定的失败次数或者超时等问题时,我们将熔断机制融入到我们的客户端调用方,当失败次数等达到阈值时,开启熔断器,及时进行弥补处理,或者给上层友好提示。

在熔断器中有三种状态:

  • 关闭:让请求通过的默认状态。如果请求成功/失败但低于阈值,则状态保持不变。可能出现的错误是超过最大并发数和超时错误。
  • 打开:当熔断器打开的时候,所有的请求都会被标记为失败;这是故障快速失败机制,而不需要等待超时时间完成。
  • 半开:定期的尝试发起请求来确认系统是否恢复。如果恢复了,熔断器将转为关闭状态或者保持打开

hystrix介绍

hystrix是一个go语言中最有名的一个熔断库,旨在隔离指向远程系统,服务和第三方库的请求,杜绝级联故障,并在复杂的分布式系统中实现弹性,毕竟在分布式系统中,故障是不可避免的。
此项目脱胎于由Netflix开源的同名java项目。https://github.com/Netflix/Hystrix

hystrix内部处理逻辑

每日一库之106:hystrix(熔断)-0

使用

像Hystrix命令一样执行代码

定义依赖于外部系统的应用逻辑,将函数传给Go。当外部系统处于健康状态,这个函数将是唯一被执行的代码。

hystrix.Go("my_command", func() error {
	// talk to other services
	return nil
}, nil)

定义fallback行为

如果希望外部系统挂了的时候执行一些动作,可以给Go传递第二个函数。理想情况下,这里的逻辑可以让你的应用优雅地处理外部系统不可用的情况。
当第一个函数返回error,或者在一系列健康检查的情况下函数无法运行结束,都会触发fallback。更详细的参考在这里

hystrix.Go("my_command", func() error {
	// talk to other services
	return nil
}, func(err error) error {
	// do this when services are down
	return nil
})

等待输出

调用Go就像执行了一个goroutine,除了你能获取到一个error的channel并且监控它。

output := make(chan bool, 1)
errors := hystrix.Go("my_command", func() error {
	// talk to other services
	output <- true
	return nil
}, nil)
select {
case out := <-output:
	// success
case err := <-errors:
	// failure
}

}

同步API

同步调用一个接口并且等待返回是一个常见的场景(对应于goroutine),Hystrix提供了一个Do函数,返回一个error

err := hystrix.Do("my_command", func() error {
	// talk to other services
	return nil
}, nil)

配置

在应用启动期间,你可以调用ConfigureCommand来为每个command添加配置:

hystrix.ConfigureCommand("my_command", hystrix.CommandConfig{
	Timeout:               1000,
	MaxConcurrentRequests: 100,
	ErrorPercentThreshold: 25,
})

也有别的配置方法,更详细的介绍请参考官方文档

例子

最后给大家举个例子

package main
import (
	"fmt"
	"github.com/afex/hystrix-go/hystrix"
	"net/http"
	"time"
)
func main() {
	hystrix.Go("get_baidu", func() error {
		// talk to other services
		_, err := http.Get("https://www.baidu.com/")
		if err != nil {
			fmt.Println("get error")
			return err
		}
		return nil
	}, func(err error) error {
		fmt.Println("get an error, handle it")
		return nil
	})
 
	time.Sleep(2 * time.Second)  // 调用Go方法就是起了一个goroutine,这里要sleep一下,不然看不到效果
}

大家把网络断开后就能够模拟外部服务挂掉的情况。

总结

熔断机制在分布式系统中几乎是必备的组件,下面总结一下:

特点

  1. hystrix作用在客户端,客户端程序依赖hystrix相关的第三方包,使得客户端与所依赖的服务,形成隔离(goroutine的隔离)。依赖服务的延迟与失败变的可控。保护调用者goroutine的执行。
  2. 避免了分布式系统中,单个组件的失败导致的级联影响。
  3. 快速失败,迅速恢复。 hystrix有快速失败机制,单个组件服务失败率到一定程度后,再请求,会直接响应失败。再这之后,会有重试机制。减少系统在错误服务调用上的开销。
  4. 降级应用

hystrix的设计原则

  1. 防止任何单个依赖服务耗尽所有用户线程
  2. 直接响应失败,而不是一直等待
  3. 提供错误返回接口,而不是让用户线程直接处理依赖服务抛出的异常
  4. 使用隔离或熔断技术来降低并限制单个依赖对整个系统造成的影响

附:同步调用的例子

package main

import (
    "fmt"
    "github.com/afex/hystrix-go/hystrix"
    "log"
    "net"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    hystrix.ConfigureCommand("default", hystrix.CommandConfig{
        Timeout:                1000, // 单次请求 超时时间
        MaxConcurrentRequests:  1,    // 最大并发量
        SleepWindow:            5000, // 熔断后多久去尝试服务是否可用
        RequestVolumeThreshold: 1,    // 验证熔断的 请求数量, 10秒内采样
        ErrorPercentThreshold:  1,    // 验证熔断的 错误百分比
    })

    //开启一个http监控服务
    //可以使用hystrix-dashboard面板查看具体情况
    //https://github.com/mlabouardy/hystrix-dashboard-docker
    hystrixStreamHandler := hystrix.NewStreamHandler()
    hystrixStreamHandler.Start()
    go func() {
        err := http.ListenAndServe(net.JoinHostPort("", "8888"), hystrixStreamHandler)
        log.Fatal(err)
    }()

    for i := 0; i < 100000; i++ {
         Do(i)
    }

    quit := make(chan os.Signal)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
}

func Do(params int) {
    err := hystrix.Do("defalut", func() error {
        fmt.Println(params)
        return nil
    }, nil)

    if err != nil {
        //加入自动降级处理,如获取缓存数据等
        switch err {
        case hystrix.ErrCircuitOpen:
            fmt.Println("circuit error:" + err.Error())
        case hystrix.ErrMaxConcurrency:
            fmt.Println("circuit error:" + err.Error())
        default:
            fmt.Println("circuit error:" + err.Error())
        }

        time.Sleep(1 * time.Second)
        log.Println("sleep 1 second")
    }
}