设为首页 加入收藏

TOP

Go微服务容错与韧性(Service Resilience)(一)
2019-09-26 18:14:08 】 浏览:138
Tags:服务 容错 韧性 Service Resilience

Service Resilience是指当服务的的运行环境出现了问题,例如网络故障或服务过载或某些微服务宕机的情况下,程序仍能够提供部分或大部分服务,这时我们就说服务的韧性很强。它是微服务中很重要的一部分内容,并被广泛讨论。它是衡量服务质量的一个重要指标。Service Resilience从内容上讲翻译成“容错”可能更接近, 但“容错”英文是“Fault Tolerance”,它的含义与“Service Resilience”是不同的。因此我觉得翻译成“服务韧性“比较合适。服务韧性在微服务体系中非常重要,它通过提高服务的韧性来弥补环境上的不足。

服务韧性通过下面几种技术来提升服务的可靠性:

  • 服务超时 (Timeout)
  • 服务重试 (Retry)
  • 服务限流(Rate Limiting)
  • 熔断器 (Circuit Breaker)
  • 故障注入(Fault Injection)
  • 舱壁隔离技术(Bulkhead)

程序实现:

服务韧性能通过不同的方式来实现,我们先用代码的来实现。程序并不复杂,但问题是服务韧性的代码会和业务代码混在一起,这带来了以下问题:

  • 误改业务逻辑:当你修改服务韧性的代码时有可能会失手误改业务逻辑。
  • 系统架构不够灵活:将来如果要改成别的架构会很困难,例如将来要改成由基础设施来完成这部分功能的话,需要把服务韧性的代码摘出来,这会非常麻烦。
  • 程序可读性差:因为业务逻辑和非功能性需求混在一起,很难看懂这段程序到底需要完成什么功能。有些人可能觉得这不很重要,但对我来说这个是一个致命的问题。
  • 加重测试负担:不管你是要修改业务逻辑还是非功能性需求,你都要进行系统的回归测试, 这大大加重了测试负担。

多数情况下我们要面对的问题是现在已经有了实现业务逻辑的函数,但要把上面提到的功能加入到这个业务函数中,又不想修改业务函数本身的代码。我们采用的方法叫修饰模式(Decorator Pattern),在Go中一般叫他中间件模式(Middleware Pattern)。修饰模式(Decorator Pattern)的关键是定义一系列修饰函数,每个函数都完成一个不同的功能,但他们的返回类型(是一个Interface)都相同,因此我们可以把这些函数一个个叠加上去,来完成全部功能。下面看一下具体实现。

我们用一个简单的gRPC微服务来展示服务韧性的功能。下图是程序结构,它分为客户端(client)和服务端(server),它们的内部结构是类似的。“middleware”包是实现服务韧性功能的包。 “service”包是业务逻辑,在服务端就是微服务的实现函数,客户端就是调用服务端的函数。在客户端(client)下的“middleware”包中包含四个文件并实现了三个功能:服务超时,服务重试和熔断器。“clientMiddleware.go"是总入口。在服务端(server)下的“middleware”包中包含两个文件并实现了一个功能,服务限流。“serverMiddleware.go"是总入口。

file

修饰模式:

修饰模式有不同的实现方式,本程序中的方式是从Go kit中学来的,它是我看到的是一种最灵活的实现方式。

下面是“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)
}

服务重试:

当网络不稳定时,服务有可能暂时失灵,这种情况一般持续时间很短,只要重试一下

首页 上一页 1 2 3 4 5 下一页 尾页 1/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇[系列] go-gin-api 路由中间件 - .. 下一篇阿里云CentOS服务器下安装Golang1..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目