今天的主题是断路器和限流器。
断路器和限流器在大规模系统架构中是非常重要的滑条。当我们开始把单体系统拆分为分布式微服务时,这些话题变得更加重要。如果没有断路器和限流器,单个组件的故障很容易导致雪崩效应。这可能导致整个系统崩溃。
在Micro的可插拔架构下,可以很容易的引入上述机制。我们知道Micro支持使用中间件包装请求。这些中间件中最重要的两个是:
- micro.WrapClient, 用于包装出站请求,也称为客户端包装
- micro.WrapHandler, 用于包装入站请求,也称为服务端包装
这两种类型的中间件分别适用于断路器和限流器的场景。下面我们以示例说明如何使用这些中间件来提供系统的鲁棒性。
断路器
通常,这种通用功能都不需要我们自己开发。社区中已经有出色的开源库。例如:hystrix-go,gobreaker.
此外,Micro还提供了适用于上述库的插件,例如hystrix插件和gobreaker插件。
借助这些插件,在Mirco中石油Circuit Breaker变得非常容易。以hystrix为例:
import (
...
"github.com/micro/go-plugins/wrapper/breaker/hystrix"
...
)
func main(){
...
// New Service
service := micro.NewService(
micro.Name("com.foo.breaker.example"),
micro.WrapClient(hystrix.NewClientWrapper()),
)
// Initialise service
service.Init()
...
}
我们要做的就是在service创建过程中分配hystrix插件,
从现在开始,该插件将跟踪对远程服务的所有调用。当请求超时或并发数达到限制时,错误将立即返回给调用方。
那么,并发和超时的默认限制是什么?答案在于hystrix-go插件的源代码。查看github.com/afex/hystrix-go/hystrix/settings.go,您会看到几个包级变量:
...
// DefaultTimeout是等待命令完成的时间,以毫秒为单位
DefaultTimeout = 1000
// DefaultMaxConcurrent是可以同时运行多少个相同类型的命令
DefaultMaxConcurrent = 10
...
因此,默认超时为1秒,默认并发限制为10。
注意:除了这两个以外,还有更多设置,但是对所有这些设置的讨论不在本文讨论范围之内。如果您有兴趣,可以访问hystrix-go库的官方网站以获取详细的文档。
如果默认设置不符合我们的要求,则可以如下进行修改:
import(
...
hystrixGo “github.com/afex/hystrix-go/hystrix”
“github.com/micro/go-plugins/wrapper/breaker/hystrix”
...
)
func main(){
...
//新服务
service:= micro.NewService(
micro.Name(“ com.foo.breaker.example”),
micro.WrapClient(hystrix.NewClientWrapper()),
)
//初始化服务
service.Init()
hystrix.DefaultMaxConcurrent = 3 //将并发更改为3
hystrix.DefaultTimeout = 200 //将超时更改为200毫秒
...
}
如代码所示,我们可以更改默认超时和并发限制
您可能对DefaultMaxConcurrent有疑问:它的范围是什么?假设有3个服务,每个服务有3种不同的方法。我们想同时调用所有这些方法。这是否意味着我们必须将DefaultMaxConcurrent设置为大于3 * 3的数字才能实现完全并发?
要回答这个问题,需要指出两点:
首先,DefaultMaxConcurrent的目标是什么?从hystrix文档中可以看到,这是hystrix中的命令:
DefaultMaxConcurrent是可以同时运行多少个相同类型的命令
接下来,您需要了解hystrix插件如何处理不同方法和命令之间的映射。查看github.com/micro/go-plugins/wrapper/breaker/hystrix/hystrix.go,您会发现相关代码如下:
import(
"github.com/afex/hystrix-go/hystrix"
...
)
...
func (c *clientWrapper) Call(ctx context.Context,req client.Request,rsp interface{},opts... client .CallOption) error{
return hystrix.Do(req.Service()+"." + req.Endpoint(),func() error{
return c.Client.Call(ctx,req,rsp,opts ...)
}} ,nil)
}
...
请注意req.Service () + “.” + Req.Endpoint (),它是hystrix 的命令。并且该命令不包含节点信息,这意味着对于同一服务,单节点部署或多节点部署没有区别,所有节点共享一个限制。
在这一点上很明显:每种服务的每种方法都是独立计数的,并且不会相互影响。无论节点数量如何,DefaultMaxConcurrent的范围都是方法级别。
实际上,不同的方法可能需要不同的限制。如何实现呢?从上面的源代码中,我们知道服务方法已映射到hystrix 命令,并且hystrix通过hystrix.ConfigureCommand以下命令支持对不同命令的独立控制:
...
hystrix.ConfigureCommand("com.serviceA.methodFoo",
hystrix.CommandConfig{
MaxConcurrentRequests: 50,
Timeout: 10,
})
hystrix.ConfigureCommand("com.serviceB.methodBar",
hystrix.CommandConfig{
Timeout: 60,
})
...
使用以上代码,我们为不同的方法设置了不同的限制。如果hystrix.CommandConfig未指定该结构的任何字段,则将使用系统默认值。
摘要:断路器在客户端上起作用。使用适当的阈值,可以确保客户端资源不会耗尽。即使它所依赖的服务不正常,客户端也将迅速返回错误,而不是让调用者等待很长时间。
限流器
与断路器类似,速率限制也是分布式系统中的常用功能。不同之处在于,Rate Limiter在服务器端生效,其作用是保护服务器:请求处理的速度达到预设的限制后,服务器将不再接收新请求,直到处理中的请求完成为止。速率限制器可以避免服务器因大量请求而崩溃。
打个比方:假设我们经营一家餐厅,可容纳10位客人。如果同时有100位客人共进晚餐,那么最好的处理方法是选择前10位客人并告知其他90位客人:我们目前无法提供服务,请改回第二天。尽管这90位客人不满意,但我们保证至少前10位客人可以享用一顿美餐。
没有Rate Limiter,结果是有100位客人进入餐厅,厨房太忙了,所有客人都无处可坐。没有人可以得到服务。整个餐厅最终瘫痪了。
在Micro中使用Rate Limiter也非常简单。我们只需要一行代码即可实现这一目标。当前有两个Rate Limiter插件。本文以uber rate limiter插件为例(当然,如果现有插件不满足要求,您可以随时自行开发更合适的插件)。让我们修改源文件hello-srv / main.go:
package main
import (
...
limiter "github.com/micro/go-plugins/wrapper/ratelimiter/uber"
...
)
func main() {
const QPS = 100
// New Service
service := micro.NewService(
micro.Name("com.foo.srv.hello"),
micro.Version("latest"),
micro.WrapHandler(limiter.NewHandlerWrapper(QPS)),
)
...
}
上面的代码将服务器端速率限制器添加到hello-srv,QPS上限为100。此服务中所有处理程序的方法都共享此限制。换句话说,此限制的范围是服务级别。
网友评论