Go微服務容錯與韌性(Service Resilience)

Service Resilience是指當服務的的運行環境出現了問題,例如網絡故障或服務過載或某些微服務宕機的狀況下,程序仍可以提供部分或大部分服務,這時咱們就說服務的韌性很強。它是微服務中很重要的一部份內容,並被普遍討論。它是衡量服務質量的一個重要指標。Service Resilience從內容上講翻譯成「容錯」可能更接近, 但「容錯」英文是「Fault Tolerance」,它的含義與「Service Resilience」是不一樣的。所以我以爲翻譯成「服務韌性「比較合適。服務韌性在微服務體系中很是重要,它經過提升服務的韌性來彌補環境上的不足。html

服務韌性經過下面幾種技術來提高服務的可靠性:git

  • 服務超時 (Timeout)
  • 服務重試 (Retry)
  • 服務限流(Rate Limiting)
  • 熔斷器 (Circuit Breaker)
  • 故障注入(Fault Injection)
  • 艙壁隔離技術(Bulkhead)

程序實現:

服務韌性能經過不一樣的方式來實現,咱們先用代碼的來實現。程序並不複雜,但問題是服務韌性的代碼會和業務代碼混在一塊兒,這帶來了如下問題:github

  • 誤改業務邏輯:當你修改服務韌性的代碼時有可能會失手誤改業務邏輯。
  • 系統架構不夠靈活:未來若是要改爲別的架構會很困難,例如未來要改爲由基礎設施來完成這部分功能的話,須要把服務韌性的代碼摘出來,這會很是麻煩。
  • 程序可讀性差:由於業務邏輯和非功能性需求混在一塊兒,很難看懂這段程序到底須要完成什麼功能。有些人可能以爲這不很重要,但對我來講這個是一個致命的問題。
  • 加劇測試負擔:無論你是要修改業務邏輯仍是非功能性需求,你都要進行系統的迴歸測試, 這大大加劇了測試負擔。

多數狀況下咱們要面對的問題是如今已經有了實現業務邏輯的函數,但要把上面提到的功能加入到這個業務函數中,又不想修改業務函數自己的代碼。咱們採用的方法叫修飾模式(Decorator Pattern),在Go中通常叫他中間件模式(Middleware Pattern)。修飾模式(Decorator Pattern)的關鍵是定義一系列修飾函數,每一個函數都完成一個不一樣的功能,但他們的返回類型(是一個Interface)都相同,所以咱們能夠把這些函數一個個疊加上去,來完成所有功能。下面看一下具體實現。數據庫

咱們用一個簡單的gRPC微服務來展現服務韌性的功能。下圖是程序結構,它分爲客戶端(client)和服務端(server),它們的內部結構是相似的。「middleware」包是實現服務韌性功能的包。 「service」包是業務邏輯,在服務端就是微服務的實現函數,客戶端就是調用服務端的函數。在客戶端(client)下的「middleware」包中包含四個文件並實現了三個功能:服務超時,服務重試和熔斷器。「clientMiddleware.go"是總入口。在服務端(server)下的「middleware」包中包含兩個文件並實現了一個功能,服務限流。「serverMiddleware.go"是總入口。編程

file

修飾模式:

修飾模式有不一樣的實現方式,本程序中的方式是從Go kit中學來的,它是我看到的是一種最靈活的實現方式。segmentfault

下面是「service」包中的「cacheClient.go", 它是用來調用服務端函的。「CacheClient」是一個空結構,是爲了實現「CallGet()」函數,也就實現了「callGetter」接口(下面會講到)。全部的業務邏輯都在這裏,它是修飾模式要完成的主要功能,其他的功能都是對它的修飾。緩存

type CacheClient struct {
}

func (cc *CacheClient) CallGet(ctx context.Context, key string, csc pb.CacheServiceClient) ( []byte, error) {
    getReq:=&pb.GetReq{Key:key}
    getResp, err :=csc.Get(ctx, getReq )
    if err != nil {
        return nil, err
    }
    value := getResp.Value
    return value, err
}

func (cc *CacheClient) CallStore(key string, value []byte, client pb.CacheServiceClient) ( *pb.StoreResp, error) {
    storeReq := pb.StoreReq{Key: key, Value: value}
    storeResp, err := client.Store(context.Background(), &storeReq)
    if err != nil {
        return nil, err
    }
    return storeResp, err
}

下面是客戶端的入口文件「clientMiddleware.go". 它定義了」callGetter「接口,這個是修飾模式的核心,每個修飾(功能)都要實現這個接口。接口裏只有一個函數「CallGet」,就是這個函數會被每一個修飾功能不斷調用。 這個函數的簽名是按照業務函數來定義的。它還定義了一個結構(struct)CallGetMiddleware,裏面只有一個成員「Next」, 它的類型是修飾模式接口(callGetter),這樣咱們就能夠經過「Next」來調用下一個修飾功能。每個修飾功能結構都會有一個相應的修飾結構,咱們須要把他們串起來,才能完成依次疊加調用。服務器

「BuildGetMiddleware()」就是用來實現這個功能的。CircuitBreakerCallGet,RetryCallGet和TimeoutCallGet分別是熔斷器,服務重試和服務超時的實現結構。它們每一個裏面也都只有一個成員「Next」。在建立時,要把它們一個個依次帶入,要注意順序,最早建立的 「CircuitBreakerCallGet」 最後執行。在每個修飾功能的最後都要調用「Next.CallGet()」,這樣就把控制傳給了下一個修飾功能。網絡

type callGetter interface {
    CallGet(ctx context.Context, key string, c pb.CacheServiceClient) ( []byte, error)
}
type CallGetMiddleware struct {
    Next callGetter
}
func BuildGetMiddleware(cc callGetter) callGetter {
    cbcg := CircuitBreakerCallGet{cc}
    tcg := TimeoutCallGet{&cbcg}
    rcg := RetryCallGet{&tcg}
    return &rcg
}

func (cg *CallGetMiddleware) CallGet(ctx context.Context, key string, csc pb.CacheServiceClient) ( []byte, error) {
    return cg.Next.CallGet(ctx, key, csc)
}

服務重試:

當網絡不穩定時,服務有可能暫時失靈,這種狀況通常持續時間很短,只要重試一下就能解決問題。下面是程序。它的邏輯比較簡單,就是若是有錯的話就不斷地調用「tcg.Next.CallGet(ctx, key, csc)」,直到沒錯了或達到了重試上限。每次重試之間有一個重試間隔(retry_interval)。架構

const (
    retry_count    = 3
    retry_interval = 200
)
type RetryCallGet struct {
    Next callGetter
}
func (tcg *RetryCallGet) CallGet(ctx context.Context, key string, csc pb.CacheServiceClient) ( []byte, error) {
    var err error
    var value []byte
    for i:=0; i<retry_count; i++ {
        value, err = tcg.Next.CallGet(ctx, key, csc)
        log.Printf("Retry number %v|error=%v", i+1, err)
        if err == nil {
            break
        }
        time.Sleep(time.Duration(retry_interval)*time.Millisecond)
    }
    return value, err
}

服務重試跟其餘功能不一樣的地方在於它有比較大的反作用,所以要當心使用。由於重試會成倍地增長系統負荷,甚至會形成系統雪崩。有兩點要注意:

  1. 重試次數:通常來說次數不要過多,這樣纔不會給系統帶來過大負擔
  2. 重試間隔時間:重試間隔時間要愈來愈長,這樣能錯開重試時間,並且越日後失敗的可能性越高,所以間隔時間要越長。通常是用斐波那契數列(Fibonacci sequence)或2的冪。固然若是重試次數少的話可酌情調整。示例中用了最簡單的方式,恆定間隔,生產環境中最好不要這樣設置。

並非全部函數都須要重試功能,只有很是重要的,不能失敗的才須要。

服務超時:

服務超時給每一個服務設定一個最大時間限制,超過以後服務中止,返回錯誤信息。它的好處是第一能夠減小用戶等待時間,由於若是一個普通操做幾秒以後還不出結果就多半出了問題,不必再等了。第二,一個請求通常都會佔用系統資源,如線程,數據庫連接,若是有大量等待請求會耗盡系統資源,致使系統宕機或性能下降。提早結束請求能夠儘快釋放系統資源。下面是程序。它在context裏設置了超時,並經過通道選擇器來判斷運行結果。當超時時,ctx的通道被關(ctx.Done()),函數中止運行,並調用cancelFunc()中止下游操做。若是沒有超時,則程序正常完成。

type TimeoutCallGet struct {
    Next callGetter
}
func (tcg *TimeoutCallGet) CallGet(ctx context.Context, key string, c pb.CacheServiceClient) ( []byte, error) {
    var cancelFunc context.CancelFunc
    var ch = make(chan bool)
    var err error
    var value []byte
    ctx, cancelFunc= context.WithTimeout(ctx, get_timeout*time.Millisecond)
    go func () {
        value, err = tcg.Next.CallGet(ctx, key, c)
        ch<- true
    } ()
    select {
        case <-ctx.Done():
            log.Println("ctx timeout")
            //ctx timeout, call cancelFunc to cancel all the sub-processes in the calling chain
            cancelFunc()
            err = ctx.Err()
        case <-ch:
            log.Println("call finished normally")
    }
    return value, err
}

這個功能應該設置在客戶端仍是服務端?服務重試沒有問題只能在客戶端。服務超時在服務端和客戶端均可以設置,但設置在客戶端更好,這樣命運是掌握在本身手裏。

下一個問題是順序選擇。 你是先作服務重試仍是先作服務超時?結果是不同的。先作服務重試時,超時是設定在全部重試上;先作服務超時,超時是設定在每一次重試上。這個要根據你的具體需求來決定,我是把超時定在每一次重試上。

服務限流(Rate Limiting):

服務限流根據服務的承載能力來設定一個請求數量的上限,通常是每秒鐘能處理多少個併發請求。超過以後,其餘全部請求所有返回錯誤信息。這樣能夠保證服務質量,不能獲得服務的請求也能快速獲得結果。這個功能與其餘不一樣,它定義在服務端。固然你也能夠給客戶端限流,但最終仍是要限制在服務端才更有意義。

下面是服務端「service」包中的「cacheServer.go", 它是服務端的接口函數。「CacheService」是它的結構,它實現了「Get」函數,也就服務端的業務邏輯。其餘的修飾功能都是對他的補充修飾。

// CacheService struct
type CacheService struct {
    Storage map[string][]byte
}

// Get function
func (c *CacheService) Get(ctx context.Context, req *pb.GetReq) (*pb.GetResp, error) {
    fmt.Println("start server side Get called: ")
    //time.Sleep(3000*time.Millisecond)
    key := req.GetKey()
    value := c.Storage[key]
    resp := &pb.GetResp{Value: value}
    fmt.Println("Get called with return of value: ", value)
    return resp, nil
}
...

下面是「serverMiddleware.go」,它是服務端middleware的入口。它定義告終構「CacheServiceMiddleware」, 裏面只有一個成員「Next", 它的類型是 「pb.CacheServiceServer」,是gRPC服務端的接口。注意這裏咱們的處理方式與客戶端不一樣,它沒有建立另外的接口, 而是直接使用了gRPC的服務端接口。客戶端的作法是每一個函數創建一個入口(接口),這樣控制的顆粒度更細,但代碼量更大。服務端全部函數共用一個入口,控制的顆粒度較粗,但代碼量較少。這樣作的緣由是客戶端須要更精準的控制。具體實現時,你能夠根據應用程序的需求來決定選哪一種方式。「BuildGetMiddleware」是服務端建立修飾結構的函數。ThrottleMiddleware是服務限流的實現結構。它裏面也只有一個成員「Next」。在建立時,要把具體的middleware功能依次帶入,如今只有一個就是「ThrottleMiddleware」。

type CacheServiceMiddleware struct {
    Next pb.CacheServiceServer
}

func BuildGetMiddleware(cs  pb.CacheServiceServer ) pb.CacheServiceServer {
    tm := ThrottleMiddleware{cs}
    csm := CacheServiceMiddleware{&tm}
    return &csm
}

func (csm *CacheServiceMiddleware) Get(ctx context.Context, req *pb.GetReq) (*pb.GetResp, error) {
    return csm.Next.Get(ctx, req)
}

func (csm *CacheServiceMiddleware) Store(ctx context.Context, req *pb.StoreReq) (*pb.StoreResp, error) {
    return csm.Next.Store(ctx, req)
}

func (csm *CacheServiceMiddleware) Dump(dr *pb.DumpReq, csds pb.CacheService_DumpServer) error {
    return csm.Next.Dump(dr, csds)
}

下面是服務限流的實現程序,它比其餘的功能要稍微複雜一些。其餘功能使用的的控制參數(例如重試次數)在執行過程當中是不會被修改的,而它的(throttle)是能夠並行讀寫的,所以須要用「sync.RWMutex」來控制。具體的限流功能在「Get」函數中,它首先判斷是否超過閥值(throttle),超過則返回錯誤信息,反之則運行。

const (
    service_throttle = 5
)
var tm throttleMutex

type ThrottleMiddleware struct {
    Next  pb.CacheServiceServer
}

type throttleMutex struct {
    mu       sync.RWMutex
    throttle int
}

func (t *throttleMutex )getThrottle () int {
    t.mu.RLock()
    defer t.mu.RUnlock()
    return t.throttle
}
func (t *throttleMutex) changeThrottle(delta int ) {
    t.mu.Lock()
    defer t.mu.Unlock()
    t.throttle =t.throttle+delta
}

func (tg *ThrottleMiddleware) Get(ctx context.Context, req *pb.GetReq) (*pb.GetResp, error) {
    if tm.getThrottle()>=service_throttle {
        log.Printf("Get throttle=%v reached\n", tm.getThrottle())
        return nil, errors.New("service throttle reached, please try later")
    } else {
        tm.changeThrottle(1)
        resp, err := tg.Next.Get(ctx, req)
        tm.changeThrottle(-1)
        return resp, err
    }
}

熔斷器 (Circuit Breaker):

熔斷器是最複雜的。 它的主要功能是當系統檢測到下游服務不順暢通時對這個服務進行熔斷,這樣就阻斷了全部對此服務的調用,減小了下游服務的負載,讓下游服務有個緩衝來恢復功能。與之相關的就是服務降級,下游服務沒有了,須要的數據怎麼辦?通常是定義一個降級函數,或者是從緩存裏取舊的數據或者是直接返回空值給上游函數,這要根據業務邏輯來定。下面是它的服務示意圖。服務A有三個下游服務,服務B,服務C,服務D。其中前兩個服務的熔斷器是關閉的,也就是服務是暢通的。服務D的熔斷器是打開的,也就是服務異常。
file

圖片來源

熔斷器用狀態機(State Machine)來進行管理,它會監測對下游服務的調用失敗狀況,並設立一個失敗上限閥值,由閥值來控制狀態轉換。它有三個狀態:關閉,打開和半打開。這裏的「關閉「是熔斷器的關閉,服務是打開的。下面是它的簡單示意圖。

file

圖片來源

正常狀況熔斷器是關閉的,當失敗請求數超過閥值時,熔斷器打開,下游服務關閉。熔斷器打開是有時間限制的,超時以後自動變成半打開狀態,這時只放一小部分請求經過。當請求失敗時,返回打開狀態,當請求成功而且數量超過閥值時,熔斷器狀態變成關閉,恢復正常。下面是它的詳細示意圖。它圖裏有僞程序,能夠仔細讀一下,讀懂了,就明白了實現原理。

file

圖片來源

當有多個修飾功能時,咋一看熔斷器應該是第一個執行的,由於若是服務端出了問題最好的對策就是屏蔽掉請求,其餘的就不要試了,例如服務重試。但仔細一想,這樣的話服務重試就沒有被熔斷器監控,所以熔斷器仍是最後一個執行好。不過具體狀況仍是要根據你有哪些修飾功能來決定。

熔斷器有不少不一樣的實現,其中最出名的多是Netflix的「Hystrix」。本程序用的是一個go開源庫叫gobreaker, 由於它比較純粹(Hystrix把不少東西都集成在一塊兒了),固然熔斷的原理都是同樣的。下面是熔斷器的程序。其中「cb」是「CircuitBreaker」的變量,它是在「init()」裏面建立的,這樣保證它只建立一次。在建立時,就設置了具體參數。「MaxRequests」是在半開狀態下容許經過的最大請求數。」Timeout「是關閉狀態的超時時間(超時後自動變成半開狀態),這裏沒有設置,取缺省值60秒。「ReadyToTrip」函數用來控制狀態轉換,當它返回true時,熔斷器由關閉變成打開。熔斷器的功能是在函數「CallGet」中實現的,它的執行函數是「cb.Execute」, 只要把要運行的函數傳入就好了。若是熔斷器是打開狀態,它就返回缺省值,若是熔斷器是關閉狀態,它就運行傳入的請求。熔斷器本身會監測請求的執行狀態並根據它的信息來控制開關轉換。

var cb *gobreaker.CircuitBreaker

type CircuitBreakerCallGet struct {
    Next callGetter
}
func init() {
    var st gobreaker.Settings
    st.Name = "CircuitBreakerCallGet"
    st.MaxRequests = 2
    st.ReadyToTrip = func(counts gobreaker.Counts) bool {
        failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
        return counts.Requests >= 2 && failureRatio >= 0.6
    }
    cb = gobreaker.NewCircuitBreaker(st)
}

func (tcg *CircuitBreakerCallGet) CallGet(ctx context.Context, key string, c pb.CacheServiceClient) ( []byte, error) {
    var err error
    var value []byte
    var serviceUp bool
    log.Printf("state:%v", cb.State().String())
    _, err = cb.Execute(func() (interface{}, error) {
        value, err = tcg.Next.CallGet(ctx, key, c)
        if err != nil {
            return nil, err
        }
        serviceUp = true
        return value, nil
    })
    if !serviceUp {
        //return a default value here. You can also run a downgrade function here
        log.Printf("circuit breaker return error:%v\n", err)
        return []byte{0}, nil
    }
    return value, nil
}

本示例中熔斷器是設置在客戶端的。從本質上來說它是針對客戶端的功能,當客戶端察覺要調用的服務失效時,它暫時屏蔽掉這個服務。但我以爲它其實設置在服務端更有優點。 設想一下當有不少不一樣節點訪問一個服務時,固然也有可能別人都能訪問,只有我不能訪問。這基本能夠確定是個人節點和服務端節點之間的連接出了問題,根本不須要熔斷器來處理(容器就能夠處理了)。所以,熔斷器要處理的大部分問題是某個微服務宕機了,這時監測服務端更有效,而不是監測客戶端。固然最後的效果仍是要屏蔽客戶端請求,這樣纔能有效減小網絡負載。這就須要服務端和客戶端之間進行協調,所以有必定難度。

另外還有一個問題就是如何判斷服務宕機了,這個是整個熔斷器的關鍵。若是服務返回錯誤結果,那麼是否意味着服務失效呢?這會有不少不一樣的狀況,熔斷器幾乎不可能作出徹底準確的判斷,從這點上來說,熔斷器仍是有瑕疵的。我以爲要想作出準確的判斷,必須網絡,容器和Service Mesh進行聯合診斷才行。

故障注入(Fault Injection)

故障注入經過人爲地注入錯誤來模擬生產環境中的各類故障和不穩定性。你能夠模擬10%的錯誤率,或服務響應延遲。使用的技術跟上面講的差很少,能夠經過修飾模式來對服務請求進行監控和控制,來達到模擬錯誤的目的。故障注入既能夠在服務端也能夠在客戶端。

艙壁隔離技術(Bulkhead)

艙壁隔離技術指的是對系統資源進行隔離,這樣當一個請求出現問題時不會致使整個系統的癱瘓。比較經常使用的是Thread pool和Connection pool。好比系統裏有數據庫的Connection Pool,它通常都有一個上限值,若是請求超出,多餘的請求就只能處於等待狀態。若是你的系統中既有運行很慢的請求(例如報表),也有運行很快的請求(例如修改一個數據庫字段),那麼一個好的辦法就是設立兩個Connection Pool, 一個給快的一個給慢的。這樣當慢的請求不少時,佔用了全部Connection Pool,但不會影響到快的請求。下面是它的示意圖,它用的是Thread Pool。

file

圖片來源

Netflix的Hystrix同時集成了艙壁隔離技術和熔斷器,它經過限制訪問一個服務的資源(通常是Thread)來達到隔離的目的。它有兩種方式,第一種是經過Thread Pool, 第二種是信號隔離(Semaphore Isolation)。也就是每一個請求都要先獲得受權才能訪問資源,詳細狀況請參閱這裏.

艙壁隔離的實際應用方式要比Hystrix的普遍得多,它不是一種單一的技術,而是能夠應用在許多不一樣的方向(詳細狀況請參閱這裏) 。

新一代技術--自適應併發限制(Adaptive Concurreny Limit)

上面講的技術都是基於靜態閥值的,多數都是每秒多少請求。可是對於擁有自動伸縮(Auto-scaling)的大型分佈式系統,這種方式並不適用。自動伸縮的雲系統會根據服務負載來調整服務器的個數,這樣服務的閥值就變成動態的,而不是靜態的。Netflix的新一代技術能夠創建動態閥值,它叫自適應併發限制(Adaptive Concurrency Limit)。它的原理是根據服務的延遲來計算負載,從而動態地找出服務的閥值。一旦找出動態閥值,這項技術是很容易執行的,困難的地方是如何找出閥值。這項技術能夠應用在下面幾個方向:

  • RPC(gRPC):既能夠應用在客戶端,也能夠應用在服務端。可使用攔截器(Interceptor)來實現
  • Servlet: 可使用過濾器來實現(Filter)
  • Thread Pool:這是一種更通用的方式。它能夠根據服務延遲來自動調節Thread Pool的大小,從而達到併發限制。

詳細狀況請參見Netflix/concurrency-limits.

Service Mesh實現:

從上面的程序實現能夠看出,它們的每一個功能並不複雜,並且不會對業務邏輯進行侵入。但上面只是實現了一個微服務調用的一個函數,若是你有不少微服務,每一個微服務又有很多函數的話,那它的工做量仍是至關大的。有沒有更好的辦法呢?當我接觸服務韌性時,就以爲直接把功能放到程序裏對業務邏輯侵入太大,就用了攔截器(Interceptor),但它不夠靈活,也不方便。後來終於找到了比較靈活的修飾模式的實現方式,這個問題終於解決了。但工做量仍是太大。後來看到了Service Mesh才發現問題的根源。由於服務韌性原本就不是應用程序應該解決的問題,
而是基礎設施或中間件的主場。這裏面涉及到的許多數據都和網絡和基礎設施相關,應用程序原本就不掌握這些信息,所以處理起來就束手束腳。應用程序仍是應該主要關注業務邏輯,而把這些跨領域的問題交給基礎設施來處理。

咱們知道容器技術(Docker和Kubernetes)的出現大大簡化了程序的部署,特別是對微服務而言。但一開始服務韌性這部分仍是由應用程序來作,最有名的應該是「Netflix OSS」。如今咱們把這部分功能抽出來, 就是Service Mesh, 比較有名的是IstioLinkerd。固然Service Mesh還有其它功能,包括網路流量控制,權限控制與受權,應用程序監測等。它是在容器的基礎上,增強了對應用程序的管理並提供了更多的服務。

當用Service Mesh來實現服務韌性時,你基本不用編程,只須要寫些配置文件,這樣更加完全地把它與業務邏輯分開了,也減輕了碼農的負擔。但它也不是沒有缺點的,編寫配置文件其實是另外一種變向的編程,當文件大了以後很容易出錯。如今已經有了比較好的支持容器的IDE,但對Service Mesh的支持還不是太理想。另外,就是這個技術還比較新,有不少人都在測試它,但在生產環境中應用的好像不是特別多。若是想要在生產環境中使用,須要作好準備去應對各類問題。固然Service Mesh是一個不可阻擋的趨勢,就像容器技術同樣,也許未來它會融入到容器中,成爲容器的一部分。有關生產環境中使用Service Mesh請參閱「下一代的微服務架構基礎是ServiceMesh?」

Service Mesh的另外一個好處是它無需編程,這樣就不須要每一種語言都有一套服務韌性的庫。固然Service Mesh也有不一樣的實現,每一種實如今設置參數時都有它本身的語法格式,理想的狀況是它們都遵照統一的接口,但願之後會是這樣。

何時須要這些技術?

上面提到的技術都不錯,但不論你是用程序仍是用Service Mesh來實現,都有大量的工做要作,尤爲是當服務衆多,而且之間的調用關係複雜時。那麼你是否應該讓全部的服務都具備這些功能呢?個人建議是,在開始時只給最重要的服務增長這些功能,而其餘服務能夠先放一放。當在生產環境運行一段時間以後,就能發現那些是常常出問題的服務,再根據問題的性質來考慮是否增長這些功能。應用本文中的修飾模式的好處是,它增長和刪除修飾功能都很是容易,而且不會影響業務邏輯。

結論:

服務韌性是微服務裏很是重要的一項技術,它能夠在服務環境不可靠的狀況下仍能提供適當的服務,大大提升了服務的容錯能力。它使用的技術包括服務超時 (Timeout),服務重試 (Retry),服務限流(Rate Limiting),熔斷器 (Circuit Breaker),故障注入(Fault Injection)和艙壁隔離技術(Bulkhead)。你能夠用代碼也能夠用Service Mesh來實現這些功能,Service Mesh是未來的方向。但實現它們須要大量的工做,所以須要考慮清楚到底哪些服務真正須要它們。

源碼:

完整源碼的github連接

索引:

[1]Go kit
http://gokit.io/examples/stri...

[2] Circuit Breaker Pattern
https://docs.microsoft.com/en...

[3]gobreaker
https://github.com/sony/gobre...

[4]Bulkhead pattern
https://docs.microsoft.com/en...

[5]How it Works
https://github.com/Netflix/Hy...

[6]It takes more than a Circuit Breaker to create a resilient application
https://developers.redhat.com...

[7]Netflix/concurrency-limits
https://github.com/Netflix/co...

[8]Istio
https://istio.io/

[9]Linkerd
https://linkerd.io/

[10]下一代的微服務架構基礎是ServiceMesh?
https://www.sohu.com/a/271138...

相關文章
相關標籤/搜索