github完整代碼地址 github.com/Allenxuxu/m…node
在分佈式系統中,常常會有服務出現故障,因此良好的重試機制能夠大大的提升系統的可用性。本文主要分析micro的客戶端重試機制,以及實例演示。git
micro框架提供方法設置客戶端重試的次數。github
Client.Init(
client.Retries(3),
)
複製代碼
當client請求失敗時,客戶端會根據selector的策略選擇下一個節點重試請求。這樣當一個服務實例故障時,客戶端能夠自動調用另外一個實例。api
咱們來看看micro 客戶端內部重試的實現:bash
go-micro\client\rpc_client.goapp
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
...
//客戶端call 調用函數, 在下面的循環中調用
call := func(i int) error {
// call backoff first. Someone may want an initial start delay
t, err := callOpts.Backoff(ctx, request, i)
if err != nil {
return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
}
// only sleep if greater than 0
if t.Seconds() > 0 {
time.Sleep(t)
}
// 根據selector策略 選出 下一個節點
node, err := next()
if err != nil && err == selector.ErrNotFound {
return errors.NotFound("go.micro.client", "service %s: %v", request.Service(), err.Error())
} else if err != nil {
return errors.InternalServerError("go.micro.client", "error getting next %s node: %v", request.Service(), err.Error())
}
// 客戶端調用
err = rcall(ctx, node, request, response, callOpts)
r.opts.Selector.Mark(request.Service(), node, err)
return err
}
ch := make(chan error, callOpts.Retries+1)
var gerr error
//根據設定的**Retries**(重試次數)循環調用 call,若是執行成功,調用超時或者設置的**Retry**函數執行出錯則直接退出,不繼續重試
for i := 0; i <= callOpts.Retries; i++ {
go func(i int) {
ch <- call(i)
}(i)
select {
case <-ctx.Done(): //超時
return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
case err := <-ch:
// if the call succeeded lets bail early
if err == nil { //調用成功
return nil
}
retry, rerr := callOpts.Retry(ctx, request, i, err)
if rerr != nil {
return rerr
}
if !retry {
return err
}
gerr = err
}
}
return gerr
}
複製代碼
micro將選舉下一個節點,RPC調用封裝到一個匿名函數中,而後根據設定的重試次數循環調用。若是調用成功或者超時則直接返回,不繼續重試。其中,當callOpts裏設定的Retry函數執行失敗,即第一個返回值爲false,或者第二個返回值爲err不會nil時,也會退出循環直接返回。負載均衡
咱們來看下Retry是什麼:框架
type CallOptions struct {
Retry RetryFunc
}
複製代碼
client的CallOptions中定義了Retry,咱們跳轉到RetryFunc分佈式
go-micro\client\retry.go函數
// note that returning either false or a non-nil error will result in the call not being retried
type RetryFunc func(ctx context.Context, req Request, retryCount int, err error) (bool, error) // RetryAlways always retry on error func RetryAlways(ctx context.Context, req Request, retryCount int, err error) (bool, error) {
return true, nil
}
// RetryOnError retries a request on a 500 or timeout error
func RetryOnError(ctx context.Context, req Request, retryCount int, err error) (bool, error) {
if err == nil {
return false, nil
}
e := errors.Parse(err.Error())
if e == nil {
return false, nil
}
switch e.Code {
// retry on timeout or internal server error
case 408, 500:
return true, nil
default:
return false, nil
}
}
複製代碼
從中咱們能夠發現,做者預實現了兩個Retry函數:RetryAlways、RetryOnError。 RetryAlways直接返回true, nil,即不退出重試。 RetryOnError只有當e.Code(上一次RPC調用結果)爲408或者500時纔會返回true, nil,繼續重試。 micro的默認Retry爲RetryOnError,可是咱們能夠自定義並設置,下面的實驗中將會演示。
DefaultRetry = RetryOnError
// DefaultRetries is the default number of times a request is tried
DefaultRetries = 1
// DefaultRequestTimeout is the default request timeout
DefaultRequestTimeout = time.Second * 5
複製代碼
當客戶端請求另外一個服務時,若是被請求的服務忽然掛了,而此時客戶端依舊會去請求,重試時客戶端會請求另外一個實例(有必定概率還會請求同一個實例,由於默認的負載均衡策略是哈希隨機)。
咱們修改api/user下的服務,在main函數中設置客戶端重試。
sClient := hystrixplugin.NewClientWrapper()(service.Options().Service.Client())
sClient.Init(
client.WrapCall(ocplugin.NewCallWrapper(t)),
client.Retries(3),
client.Retry(func(ctx context.Context, req client.Request, retryCount int, err error) (bool, error) {
log.Log(req.Method(), retryCount, " client retry")
return true, nil
}),
)
複製代碼
而後,咱們依次啓動 micro網關,user API服務,hello SRV服務(啓動兩個實例)。
cd micro && make run
cd api/user && make run
cd srv/hello && make run
cd srv/hello && make run
複製代碼
咱們經過kill -9 殺死其中一個hello服務,而後經過postman請求 GET 172.0.0.1:8080/user/test。
[GIN] 2019/05/14 - 14:52:20 | 200 | 1.253576ms | 127.0.0.1 | GET /user/test
2019/05/14 14:52:48 Received Say.Anything API request
2019/05/14 14:52:48 0x19a1680 0 retry func
2019/05/14 14:52:48 msg:"Hello xuxu"
[GIN] 2019/05/14 - 14:52:48 | 200 | 13.821193ms | 127.0.0.1 | GET /user/test
複製代碼
經過usr API服務的輸出,咱們能夠看到重試一次後,客戶端成功請求了另外一個實例。
github完整代碼地址 github.com/Allenxuxu/m…