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

本文详细介绍了在Go语言中实现微服务容错与韧性(Service Resilience)的各种技术,包括服务超时、重试、限流、熔断器、故障注入、舱壁隔离和自适应并发限制。通过修饰模式(Middleware Pattern)来实现这些功能,以避免对业务逻辑的侵入。服务超时和重试有助于控制服务响应时间和应对短暂故障,限流和熔断器保护系统免受过载影响,故障注入用于模拟故障以测试系统稳定性,舱壁隔离确保单个请求问题不会影响整体服务。此外,文章还探讨了Service Mesh作为更现代的解决方案,如何更高效地管理和实现服务韧性,并建议在必要时逐步为关键服务添加这些技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
}

下面是客户端的入口文件“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) ( []
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值