在微服務架構中,單體服務被拆分爲若干微服務,一個服務一般須要調用(網絡方式)多個服務才能完成預期功能,服務的穩定性受其餘服務總體穩定性的制約。若一個服務出現故障,將會影響服務消費方沒法正常工做,並將影響逐步放大,甚至致使整個服務集羣崩潰,也就是服務雪崩效應。html
爲防止服務雪崩,研發人員採用了流量控制、改進緩存、服務自動擴容、服務降級與熔斷等方式。本文將介紹服務熔斷,並使用go-kit+Hystrix實現微服務的熔斷方案。git
服務熔斷是指調用方發現服務提供方響應緩慢或者不可用時,調用方爲了自保直接失敗,再也不調用目標服務。考慮到服務提供方可能會恢復,在一段時間後會進行嘗試訪問。本質上這是一個「斷路器模式」的應用,Martin Fowler有專門的文章對該模式進行講解。經過下面的斷路器開關狀態圖進行說明:github
針對服務熔斷,業內使用的最多的當屬Netflix退出的Hystrix,它爲Spring Cloud構建的微服務提供了便利。如下引自官方對Hystrix的介紹:docker
Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.bootstrap
Hystrix是一個延遲和容錯庫,旨在隔離對遠程系統、服務和第三方庫的訪問點,中止級聯故障,並在故障不可避免的複雜分佈式系統中實現恢復能力。segmentfault
本示例將使用Hystrix的go語言版本afex/hystrix-go
實現服務熔斷治理。api
本示例基於arithmetic_trace_demo更改,在gateway
中增長服務熔斷治理策略,register
中除了臨時增長一些故障模擬代碼不作其餘改動。瀏覽器
複製arithmetic_trace_demo目錄,重命名爲arithmetic_circuitbreaker_demo。緩存
下載該示例所需的go依賴:bash
go get github.com/afex/hystrix-go
複製代碼
修改docker/docker-compose.yml,增長hystrix-dashboard實例,內容以下:
version: '2'
consul:
image: progrium/consul:latest
ports:
- 8400:8400
- 8500:8500
- 8600:53/udp
hostname: consulserver
command: -server -bootstrap -ui-dir /ui
zipkin:
image: openzipkin/zipkin
ports:
- 9411:9411
hystrix:
image: mlabouardy/hystrix-dashboard:latest
ports:
- 8181:9002
複製代碼
開始以前先簡單說下hystrix-go的命令模式,它提供了Do
方法經過異步模式執行用戶的業務邏輯,在執行成功或發生錯誤返回以前將被阻塞,定義以下:
func Do(name string, run runFunc, fallback fallbackFunc) error
複製代碼
爲了完成hystrix-go的調用,我把原來反向代理的邏輯封裝到Do方法中,並經過HystrixRouter類型實現了ServeHTTP,在其中封裝了鏈路追蹤和服務發現邏輯(這麼作僅僅爲了演示)。
HystrixRouter
定義和新建方法以下所示,主要對鏈路追蹤、服務發現進行封裝。
// HystrixRouter hystrix路由
type HystrixRouter struct {
svcMap *sync.Map //服務實例,存儲已經經過hystrix監控服務列表
logger log.Logger //日誌工具
fallbackMsg string //回調消息
consulClient *api.Client //consul客戶端對象
tracer *zipkin.Tracer //服務追蹤對象
}
func Routes(client *api.Client, zikkinTracer *zipkin.Tracer, fbMsg string, logger log.Logger) http.Handler {
return HystrixRouter{
svcMap: &sync.Map{},
logger: logger,
fallbackMsg: fbMsg,
consulClient: client,
tracer: zikkinTracer,
}
}
複製代碼
接下來的主要邏輯將在ServeHTTP中實現,主要思路爲:
詳細代碼以下所示,可經過註釋進行理解:
func (router HystrixRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//查詢原始請求路徑,如:/arithmetic/calculate/10/5
reqPath := r.URL.Path
if reqPath == "" {
return
}
//按照分隔符'/'對路徑進行分解,獲取服務名稱serviceName
pathArray := strings.Split(reqPath, "/")
serviceName := pathArray[1]
//檢查是否已經加入監控
if _, ok := router.svcMap.Load(serviceName); !ok {
//把serviceName做爲命令對象,設置參數
hystrix.ConfigureCommand(serviceName, hystrix.CommandConfig{Timeout: 1000})
router.svcMap.Store(serviceName, serviceName)
}
//執行命令
err := hystrix.Do(serviceName, func() (err error) {
//調用consul api查詢serviceNam
result, _, err := router.consulClient.Catalog().Service(serviceName, "", nil)
if err != nil {
router.logger.Log("ReverseProxy failed", "query service instace error", err.Error())
return
}
if len(result) == 0 {
router.logger.Log("ReverseProxy failed", "no such service instance", serviceName)
return errors.New("no such service instance")
}
director := func(req *http.Request) {
//從新組織請求路徑,去掉服務名稱部分
destPath := strings.Join(pathArray[2:], "/")
//隨機選擇一個服務實例
tgt := result[rand.Int()%len(result)]
router.logger.Log("service id", tgt.ServiceID)
//設置代理服務地址信息
req.URL.Scheme = "http"
req.URL.Host = fmt.Sprintf("%s:%d", tgt.ServiceAddress, tgt.ServicePort)
req.URL.Path = "/" + destPath
}
var proxyError error = nil
// 爲反向代理增長追蹤邏輯,使用以下RoundTrip代替默認Transport
roundTrip, _ := zipkinhttpsvr.NewTransport(router.tracer, zipkinhttpsvr.TransportTrace(true))
//反向代理失敗時錯誤處理
errorHandler := func(ew http.ResponseWriter, er *http.Request, err error) {
proxyError = err
}
proxy := &httputil.ReverseProxy{
Director: director,
Transport: roundTrip,
ErrorHandler: errorHandler,
}
proxy.ServeHTTP(w, r)
return proxyError
}, func(err error) error {
//run執行失敗,返回fallback信息
router.logger.Log("fallback error description", err.Error())
return errors.New(router.fallbackMsg)
})
// Do方法執行失敗,響應錯誤信息
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
}
}
複製代碼
首先建立hystrixRouter對象,按照方法參數列表傳遞參數;而後將返回值使用zipkin服務中間件進行包裝。
hystrixRouter := Routes(consulClient, zipkinTracer, "Circuit Breaker:Service unavailable", logger)
handler := zipkinhttpsvr.NewServerMiddleware(
zipkinTracer,
zipkinhttpsvr.SpanName("gateway"),
zipkinhttpsvr.TagResponseSize(true),
zipkinhttpsvr.ServerTags(tags),
)(hystrixRouter)
複製代碼
爲了經過hystrix-dashboard
對服務進行監控,須要啓用hystrix的實時監控服務,代碼以下:
//啓用hystrix實時監控,監聽端口爲9010
hystrixStreamHandler := hystrix.NewStreamHandler()
hystrixStreamHandler.Start()
go func() {
errc <- http.ListenAndServe(net.JoinHostPort("", "9010"), hystrixStreamHandler)
}()
複製代碼
好了,gateway的代碼就修改完成了。
依次啓動docker、register、gateway,而後使用postman的Runner工具進行測試。
#啓動consul、zipkin、hystrix-dashboard
sudo docker-compose -f docker/docker-compose.yml up
./register/register -consul.host localhost -consul.port 8500 -service.host 192.168.192.146 -service.port 9000
./gateway/gateway -consul.host localhost -consul.port 8500
複製代碼
Postman中新增一個collection,命名爲circuitbreaker,新建post請求。打開Runner(左上角),選擇新建的集合,設置時間間隔爲100毫秒,迭代次數爲1000(暫時不運行)。以下圖:
打開瀏覽器,輸入http://localhost:8181/hystrix
,在輸入框輸入hystrix的監控地址http://192.168.192.146:9010
(這裏須要配置你的主機地址),而後啓動監控,以下圖:
準備工做完成了,下面開始測試。
在Postman的Runner界面點擊「Run circuitbreak」按鈕,而後查看Hysytrix監控面板,會看到以下界面,斷路器的狀態爲Closed
:
而後,關閉register服務(直接在終端中止,方便下面快速啓動)。會發現斷路器狀態很快變爲Open
:
再開啓register服務,斷路器狀態恢復爲Closed
:
Hystrix默認設置的參數爲:
初始時register服務正常,全部請求順利執行,因此爲Closed狀態;當關閉register(模擬故障)後,請求失敗次數到達設定閾值時,切換爲Open狀態;服務恢復後,待Hystrix到達重試時機,服務恢復。
本文在go-kit中使用hystrix-go爲網關服務gateway
增長了服務熔斷治理方案,經過模擬register服務從「正常-故障-恢復」,在hystrix-dashboard
中觀察到斷路器狀態的變化。
在實際開發過程當中,因爲服務之間的依賴關係複雜,很是有必要爲咱們的服務增長服務熔斷治理措施,確保及時止損,防止因單個依賴服務的故障影響全部業務線。本文僅做爲服務熔斷的入門,下來還須要深刻研究Hystrix的熔斷與降級機制。
本文首發於本人微信公衆號【兮一昂吧】,歡迎掃碼關注!