Go微服务容错与韧性(Service Resilience)

2020-07-09 00:00:00 功能 函数 服务 修饰 熔断器

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"是总入口。

修饰模式:

修饰模式有不同的实现方式,本程序中的方式是从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
}

相关文章