Go语言微服务实战之限流与熔断保护

设想我们的应用成长很快,访问量很大,为了防止系统被大量请求打垮而不可用,我们需要做一些常规的保护措施。

先来了解几个基本概念:

限流:后端服务有可能会面临大量的请求,这可能是因为用户量确实很大,也可能是客户端代码中有bug(例如出现递归之类的问题),还有可能是不法分子恶意攻击。大量的请求最终有可能导致服务不可用,如果是核心服务造成的影响会更严重,这时候就需要服务端根据QPS的情况做限流,一旦请求量超出阈值,则采取某种措施(等待或者直接拒绝处理)。

熔断:如果服务因为某种原因而频繁的出现请求超时的情况,此时需要对后续的请求进行短路处理,也就是不实际调用后台服务,而是返回给调用方一个mock的值,等到服务恢复以后,用户可以继续正常访问服务。

接下来我们继续扩展之前的示例代码,我们先加限流,下一篇文章在加熔断。

1、给服务做限流保护

我们已经了解了限流的概念,下面来看看现实当中用的比较多的限流算法:令牌桶算法。

令牌桶算法实际上是个类似于生产者和消费者的一种算法:有一个令牌桶,桶子的初始容量为N个令牌,当有请求过来的时候,先得从令牌桶里拿一个令牌,如果没有令牌就要等待。还有一个生产者,每隔一定时间就往令牌里放一个令牌。可以通过配置相关参数来达到限流的目的。

 go语言已经有令牌桶算法的实现供我们使用,我们就来用用看(当然想自己手动撸一个也是可以的):

首先在go module中导入依赖包:

github.com/juju/ratelimit v1.0.1

这个是github上标星比较多的一个令牌桶算法的实现,另外github上也有uber实现的漏桶算法的实现,可以自行选择。

1.1 理解micro的装饰器

在加限流功能前我们先认识一下micro的装饰器模式,在micro中很多地方都使用了装饰器来让用户对其进行自定义扩展,在客户端进行调用时,可以通过选项来改变调用行为,我们看看之前示例代码中接口的定义:

// Client API for Greeter service

type GreeterService interface {
	Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error)
}

注意最后的opts这个参数,这个东东就是我们提供一些调用选项来对调用进行控制的地方。那有哪些选项可供我们设置呢,看下面的定义就知道了:

type CallOptions struct {
	SelectOptions []selector.SelectOption

	// Address of remote hosts
	Address []string
	// Backoff func
	Backoff BackoffFunc
	// Check if retriable func
	Retry RetryFunc
	// Transport Dial Timeout
	DialTimeout time.Duration
	// Number of Call attempts
	Retries int
	// Request/Response timeout
	RequestTimeout time.Duration
	// Stream timeout for the stream
	StreamTimeout time.Duration
	// Use the services own auth token
	ServiceToken bool

	// Middleware for low level call func
	CallWrappers []CallWrapper

	// Other options for implementations of the interface
	// can be stored in a context
	Context context.Context
}

请求的超时值,重试的次数,采用什么负载均衡策略来选择节点等等都是可以设置的,现在我们重点关注下面这个选项:

CallWrappers

这是一个CallWrapper类型的切片,从字面意思上来看就是对原始调用请求进行包装,我们看看CallWrapper的定义:

// CallFunc represents the individual call func
type CallFunc func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error

// CallWrapper is a low level wrapper for the CallFunc
type CallWrapper func(CallFunc) CallFunc

这是一个函数类型,参数是原始CallFunc,返回一个新的CallFunc,你可以在这个新的CallFunc里上下其手。

这是针对某个特定的接口做限流,如果你想对整个服务做限流,方式也是类似的,可以用micro.WrapClient对整个客户端做包装即可。

1.2 增加限流代码

现在我们其实已经大概知道了要如何扩展代码增加限流功能了,开始动手。新建一个文件,内容非常简单:

/**
 * 带上限流功能,针对整个服务的所有接口,用一个令牌桶控制整个服务的访问量
 * 参数:
 * fillIntervalMs 向令牌桶添加令牌的周期,以毫秒为单位
 * bucketCapicty  令牌桶中的容量
 * quantumAdd     每次添加多少令牌到桶里
 * wait           当令牌耗尽时是否等待
 */
func WithRateLimit(fillIntervalMs int, bucketCapicty, quantumAdd int64, wait bool) micro.Option {
	return micro.WrapClient(newRateLimitWrapper(fillIntervalMs, bucketCapicty, quantumAdd, wait))
}

/**
 * 带上限流功能,针对服务的某个接口,每个接口用一个令牌桶来控制访问量
 * 参数:
 * bucket 控制接口访问的令牌桶
 * wait   当令牌耗尽时是否等待
 */
func WithRateLimitCall(bucket *ratelimit.Bucket, wait bool) client.CallOption {
	wrapper := func(f client.CallFunc) client.CallFunc {
		if wait {
			// 一直等待到令牌桶中有令牌为止
			time.Sleep(bucket.Take(1))
		} else if bucket.TakeAvailable(1) == 0 {
			// 没有拿到令牌,又不等待,直接返回错误给客户端
			return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
				return apperror.TooManyRequest
			}
		}
		return f
	}
	return client.WithCallWrapper(wrapper)
}

我们提供了两个工具函数,一个用于包装整个客户端,一个用于包装接口。

现在我们修改一下上一篇文章中的客户端代码:

var (
	//控制接口Hello访问的令牌桶
	helloBucket = ratelimit.NewBucketWithQuantum(time.Millisecond * time.Duration(10), 50, 1)
)

func (g *Greeter) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
	//通过rpc调用服务端
	response, e := g.Client.Hello(ctx, &pb.Request{Name: "Hello Micro"}, tool.WithRateLimitCall(helloBucket, false))
	if e != nil {
		return e
	}
	rsp.Msg = response.Msg
	return nil
}

代码基本一样,增加了一个用于控制Hello调用的令牌桶,这个令牌桶容量为50,每10ms向令牌中添加1个令牌,然后在RPC的时候增加了限流选项:tool.WithRateLimitCall。

现在,我们的Greeter.Hello接口已经具备限流的能力了,下面我们来测试一下。

1.3 测试限流功能

我们把代码跑起来,跟上一篇文章一样,先启动网关,在启动服务端,最后启动客户端。

然后我们来用wrk压测工具来测试一下:

起5个线程,建100个连接模拟100个客户,测试结果如图,我们发现有大量的请求没有成功(Non-2xx or 3xx responses),这是因为被我们的限流算法把请求给拒绝掉了。

做为对比,我们去掉限流代码在测试一次:

可以看到,去掉限流之后不会有失败的请求了。

2、小结

这篇文章我们继续扩展微服务示例代码,给接口加上了限流功能,顺便跟着这个功能了解了micro中的装饰器模式的实现,我们的示例服务又强壮了一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值