以下由golang教學專欄跟大家介紹關於golang封裝一個bash函數,用來執行bash指令析,希望對需要的朋友有幫助!
開篇
這週在看內部一個熔斷限流套件時,發現它是基於一個開源專案hystrix-go
實現了,因此有了這篇文章。
Hystrix
是由 Netflex
開發的開源元件,提供了基礎的熔斷功能。 Hystrix
將降級的策略封裝在Command
中,提供了run
和fallback
兩個方法,前者表示正常的邏輯,例如微服務之間的呼叫…,如果發生了故障,再執行fallback
方法回傳結果,我們可以把它理解成保底操作。如果正常邏輯在短時間內頻繁發生故障,那麼可能會觸發短路,也就是之後的請求不再執行 run
,而是直接執行 fallback
。更多關於Hystrix
的資訊可以查看https://github.com/Netflix/Hystrix
,而hystrix-go
則是用go
實作的hystrix
版,更確切的說,就是簡化版。只是上一次更新還是 2018年 的一次 pr
,也就畢業了?
為什麼需要這些工具?
例如一個微服務化的產品線上,每一個服務都專注於自己的業務,並對外提供相應的服務接口,或者依賴於外部服務的某個邏輯接口,就像下面這樣。
假設我們目前是服務A
,有部分邏輯依賴服務C
,服務C
又依賴服務E
,當前微服務之間進行rpc
或http
通信,假設此時服務C
調用服務E 失敗,例如由於網路波動導致逾時或者服務E由於過載,系統E 已經down掉了。
呼叫失敗,一般會有失敗重試等機制。但再想想,假設服務E已然不可用的情況下,此時新的呼叫不斷產生,同時伴隨著呼叫等待和失敗重試,會導致服務C對服務E的呼叫而產生大量的積壓,慢慢會耗盡服務C的資源,進而導致服務C也down掉,這樣惡性循環下,會影響整個微服務體系,產生雪崩效應。
雖然導致雪崩的發生不只這一種,但是我們需要採取一定的措施,來保證不讓這個惡夢發生。而 hystrix-go
就很好的提供了 熔斷和降級的措施。它的主要思想在於,設定一些閥值,例如最大並發數(當並發數大於設定的並發數,攔截),錯誤率百分比(請求數量大於等於設定的閥值,並且錯誤率達到設定的百分比時,觸發熔斷)以及熔斷嘗試恢復時間等。
hystrix-go
的使用非常簡單,你可以呼叫它的Go
或Do
方法,只是Go
方法是異步的方式。而 Do
方法是同步方式。我們從一個簡單的範例開啟。
_ = hystrix.Do("wuqq", func() error { // talk to other services _, err := http.Get("https://www.baidu.com/") if err != nil { fmt.Println("get error:%v",err) return err } return nil }, func(err error) error { fmt.Printf("handle error:%v\n", err) return nil })
Do
函數需要三個參數,第一個參數commmand
名稱,你可以把每個名稱當成一個獨立當服務,第二個參數是處理正常的邏輯,例如http
呼叫服務,傳回參數是err
。如果處理|呼叫失敗,那麼就執行第三個參數邏輯, 我們稱為保底操作。由於服務錯誤率過高導致熔斷器開啟,那麼之後的請求也直接回調此函數。
既然熔斷器是依照配置的規則而進行是否開啟的操作,那麼我們當然可以設定我們想要的值。
hystrix.ConfigureCommand("wuqq", hystrix.CommandConfig{ Timeout: int(3 * time.Second), MaxConcurrentRequests: 10, SleepWindow: 5000, RequestVolumeThreshold: 10, ErrorPercentThreshold: 30, }) _ = hystrix.Do("wuqq", func() error { // talk to other services _, err := http.Get("https://www.baidu.com/") if err != nil { fmt.Println("get error:%v",err) return err } return nil }, func(err error) error { fmt.Printf("handle error:%v\n", err) return nil })
稍微解釋一下上面配置的值意義:
command
的超时时间。command
的最大并发量 。SleepWindow
的时间就是控制过多久后去尝试服务是否可用了。RequestVolumeThreshold
并且错误率到达这个百分比后就会启动熔断
当然你不设置的话,那么自动走的默认值。
我们再来看一个简单的例子:
package mainimport ( "fmt" "github.com/afex/hystrix-go/hystrix" "net/http" "time")type Handle struct{}func (h *Handle) ServeHTTP(r http.ResponseWriter, request *http.Request) { h.Common(r, request)}func (h *Handle) Common(r http.ResponseWriter, request *http.Request) { hystrix.ConfigureCommand("mycommand", hystrix.CommandConfig{ Timeout: int(3 * time.Second), MaxConcurrentRequests: 10, SleepWindow: 5000, RequestVolumeThreshold: 20, ErrorPercentThreshold: 30, }) msg := "success" _ = hystrix.Do("mycommand", func() error { _, err := http.Get("https://www.baidu.com") if err != nil { fmt.Printf("请求失败:%v", err) return err } return nil }, func(err error) error { fmt.Printf("handle error:%v\n", err) msg = "error" return nil }) r.Write([]byte(msg))}func main() { http.ListenAndServe(":8090", &Handle{})}
我们开启了一个 http
服务,监听端口号 8090
,所有请求的处理逻辑都在 Common
方法中,在这个方法中,我们主要是发起一次 http
请求,请求成功响应success
,如果失败,响应失败原因。
我们再写另一个简单程序,并发 11
次的请求 8090
端口。
package mainimport ( "fmt" "io/ioutil" "net/http" "sync" "time")var client *http.Clientfunc init() { tr := &http.Transport{ MaxIdleConns: 100, IdleConnTimeout: 1 * time.Second, } client = &http.Client{Transport: tr}}type info struct { Data interface{} `json:"data"`}func main() { var wg sync.WaitGroup for i := 0; i <p>由于我们配置 <code>MaxConcurrentRequests</code> 为10,那么意味着还有个 g 请求会失败:<br><img src="https://img.php.cn/upload/article/000/000/020/a47bae3dcfa1730bb3b1136eddb4f59b-5.png" alt=""><br>和我们想的一样。</p><p>接着我们把网络断开,并发请求改成10次。再次运行程序并发请求 <code>8090</code> 端口,此时由于网络已关闭,导致请求百度失败:<br><img src="https://img.php.cn/upload/article/000/000/020/a47bae3dcfa1730bb3b1136eddb4f59b-6.png" alt=""><br>接着继续请求:<br><img src="https://img.php.cn/upload/article/000/000/020/a47bae3dcfa1730bb3b1136eddb4f59b-7.png" alt=""><br>熔断器已开启,上面我们配置的<code>RequestVolumeThreshold</code> 和 <code>ErrorPercentThreshold</code> 生效。</p><p>然后我们把网连上,五秒后 (<code>SleepWindow</code>的值)继续并发调用,当前熔断器处于半开的状态,此时请求允许调用依赖,如果成功则关闭,失败则继续开启熔断器。<br><img src="https://img.php.cn/upload/article/000/000/020/a47bae3dcfa1730bb3b1136eddb4f59b-8.png" alt=""><br>可以看到,有一个成功了,那么此时熔断器已关闭,接下来继续运行函数并发调用:<br><img src="https://img.php.cn/upload/article/000/000/020/24be5795ca6d697af3d1d7ffdfdafc03-9.png" alt=""><br>可以看到,10个都已经是正常成功的状态了。</p><p>那么问题来了,为什么最上面的图只有一个是成功的?5秒已经过了,并且当前网络正常,应该是10个请求都成功,但是我们看到的只有一个是成功状态。通过源码我们可以找到答案:<br>具体逻辑在判断当前请求是否可以调用依赖</p><pre class="brush:php;toolbar:false">if !cmd.circuit.AllowRequest() { ...... return }
func (circuit *CircuitBreaker) AllowRequest() bool { return !circuit.IsOpen() || circuit.allowSingleTest()}func (circuit *CircuitBreaker) allowSingleTest() bool { circuit.mutex.RLock() defer circuit.mutex.RUnlock() now := time.Now().UnixNano() openedOrLastTestedTime := atomic.LoadInt64(&circuit.openedOrLastTestedTime) if circuit.open && now > openedOrLastTestedTime+getSettings(circuit.Name).SleepWindow.Nanoseconds() { / swapped := atomic.CompareAndSwapInt64(&circuit.openedOrLastTestedTime, openedOrLastTestedTime, now) //这一句才是关键 if swapped { log.Printf("hystrix-go: allowing single test to possibly close circuit %v", circuit.Name) } return swapped } return false}
这段代码首先判断了熔断器是否开启,并且当前时间大于 上一次开启熔断器的时间+ SleepWindow
的时间,如果条件都符合的话,更新此熔断器最新的 openedOrLastTestedTime
,是通过 CompareAndSwapInt64
原子操作完成的,意外着必然只会有一个成功。
此时熔断器还是半开的状态,接着如果能拿到令牌,执行run
函数(也就是Do传入的第二个简单封装后的函数),发起 http
请求,如果成功,上报成功状态,关闭熔断器。如果失败,那么熔断器依旧开启。
以上就是大体的流程讲解,下一篇文章将解读核心源码以及进一步当思考。
更多相关技术文章,请访问go语言教程栏目!
以上是詳解 hystrix-go 使用與原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!