设为首页 加入收藏

TOP

Go微服务容错与韧性(Service Resilience)(二)
2019-09-26 18:14:08 】 浏览:157
Tags:服务 容错 韧性 Service Resilience
就能解决问题。下面是程序。它的逻辑比较简单,就是如果有错的话就不断地调用“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 Cache
首页 上一页 1 2 3 4 5 下一页 尾页 2/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇[系列] go-gin-api 路由中间件 - .. 下一篇阿里云CentOS服务器下安装Golang1..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目