go-kit微服務:服務熔斷

在微服務架構中,單體服務被拆分爲若干微服務,一個服務一般須要調用(網絡方式)多個服務才能完成預期功能,服務的穩定性受其餘服務總體穩定性的制約。若一個服務出現故障,將會影響服務消費方沒法正常工做,並將影響逐步放大,甚至致使整個服務集羣崩潰,也就是服務雪崩效應。html

爲防止服務雪崩,研發人員採用了流量控制、改進緩存、服務自動擴容、服務降級與熔斷等方式。本文將介紹服務熔斷,並使用go-kit+Hystrix實現微服務的熔斷方案。git

服務熔斷

服務熔斷是指調用方發現服務提供方響應緩慢或者不可用時,調用方爲了自保直接失敗,再也不調用目標服務。考慮到服務提供方可能會恢復,在一段時間後會進行嘗試訪問。本質上這是一個「斷路器模式」的應用,Martin Fowler有專門的文章對該模式進行講解。經過下面的斷路器開關狀態圖進行說明:github

狀態圖

  • 初始狀態爲Closed,若請求一直成功,Closed狀態將保持;若失敗次數未超時設定閾值時,也將保持Closed狀態;若失敗次數達到設定閾值,將切換爲Open狀態。
  • Open狀態下,調用方再也不調用目標服務,直接失敗返回。若達到設定的重試時間,狀態切換爲Half Open,容許部分請求進行嘗試。若嘗試成功,則切換爲Closed狀態,若失敗則切換爲Open狀態。

針對服務熔斷,業內使用的最多的當屬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中除了臨時增長一些故障模擬代碼不作其餘改動。瀏覽器

Step-0:代碼準備

複製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
複製代碼

Step-2:新增gateway/router.go

開始以前先簡單說下hystrix-go的命令模式,它提供了Do方法經過異步模式執行用戶的業務邏輯,在執行成功或發生錯誤返回以前將被阻塞,定義以下:

func Do(name string, run runFunc, fallback fallbackFunc) error 
複製代碼
  • name:爲執行的命令名稱,通常設置爲請求的名稱或者服務的名稱。
  • run:業務邏輯方法,把對服務提供方的調用邏輯封裝在該方法裏面。
  • fallback:run運行過程當中發生錯誤時的回調方法,通常作錯誤信息封裝。

爲了完成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中實現,主要思路爲:

  • 解析請求路徑中的服務名稱,檢查是否已經加入Hystrix監控:若否,則配置信息,並緩存至服務列表,如果則跳過。
  • 在Do方法中封裝服務發現、反向搭理、鏈路追蹤邏輯。服務發現失敗,返回錯誤信息;反向代理失敗返回錯誤信息(這裏新增了反向代理錯誤時的回調方法);

詳細代碼以下所示,可經過註釋進行理解:

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()))
	}
}
複製代碼

Step-3:修改gateway/main.go

首先建立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的代碼就修改完成了。

Step-4:運行&測試

運行

依次啓動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(暫時不運行)。以下圖:

Postman配置

打開瀏覽器,輸入http://localhost:8181/hystrix,在輸入框輸入hystrix的監控地址http://192.168.192.146:9010(這裏須要配置你的主機地址),而後啓動監控,以下圖:

Hystrix配置

Hystrix監控

準備工做完成了,下面開始測試。

測試

在Postman的Runner界面點擊「Run circuitbreak」按鈕,而後查看Hysytrix監控面板,會看到以下界面,斷路器的狀態爲Closed

Hystrix正常

而後,關閉register服務(直接在終端中止,方便下面快速啓動)。會發現斷路器狀態很快變爲Open

Hystrix熔斷

再開啓register服務,斷路器狀態恢復爲Closed

Hystrix恢復

分析

Hystrix默認設置的參數爲:

  • DefaultErrorPercentThreshold = 50:請求失敗比例達到50%時,斷路器切換爲Open狀態。
  • DefaultTimeout = 1000:請求超過該時間即視爲服務異常。我在代碼中設置的也是1秒。
  • DefaultSleepWindow = 5000:在Open狀態下,間隔5秒進行重試。

初始時register服務正常,全部請求順利執行,因此爲Closed狀態;當關閉register(模擬故障)後,請求失敗次數到達設定閾值時,切換爲Open狀態;服務恢復後,待Hystrix到達重試時機,服務恢復。

總結

本文在go-kit中使用hystrix-go爲網關服務gateway增長了服務熔斷治理方案,經過模擬register服務從「正常-故障-恢復」,在hystrix-dashboard中觀察到斷路器狀態的變化。

在實際開發過程當中,因爲服務之間的依賴關係複雜,很是有必要爲咱們的服務增長服務熔斷治理措施,確保及時止損,防止因單個依賴服務的故障影響全部業務線。本文僅做爲服務熔斷的入門,下來還須要深刻研究Hystrix的熔斷與降級機制。

本文參考

本文首發於本人微信公衆號【兮一昂吧】,歡迎掃碼關注!

相關文章
相關標籤/搜索