1. 熔断器作用
熔断器是对于一段时间内请求失败数超过设定的阈值的客户端,之后不再请求后端服务,直接返回出错信息,以防请求任务堆积。过期之后的请求正常访问后端服务。
2. grpc拦截器
grpc拦截器分为两类:
- 一元拦截器UnaryInterceptor, 可以拦截一元rpc请求
- 流式拦截器StreamInterceptor, 可以拦截服务端流式rpc、客户端流式、双向流失rpc请求
常用来做日志,认证,metric 等等
3. go-zero 熔断器的使用
go-zero 使用grpc的一元和流式,总共有6个拦截器。
func (c *client) buildDialOptions(opts ...ClientOption) []grpc.DialOption {
options = append(options,
WithUnaryClientInterceptors(
clientinterceptors.UnaryTracingInterceptor,
clientinterceptors.DurationInterceptor,
clientinterceptors.PrometheusInterceptor,
clientinterceptors.BreakerInterceptor, //熔断器使用
clientinterceptors.TimeoutInterceptor(cliOpts.Timeout),
),
WithStreamClientInterceptors(
clientinterceptors.StreamTracingInterceptor,
),
)
}
func (c *client) dial(server string, opts ...ClientOption) error {
options := c.buildDialOptions(opts...) //构造options
timeCtx, cancel := context.WithTimeout(context.Background(), dialTimeout)
defer cancel()
conn, err := grpc.DialContext(timeCtx, server, options...)//调用入口
if err != nil {
service := server
if errors.Is(err, context.DeadlineExceeded) {
pos := strings.LastIndexByte(server, separator)
// len(server) - 1 is the index of last char
if 0 < pos && pos < len(server)-1 {
service = server[pos+1:]
}
}
return fmt.Errorf("rpc dial: %s, error: %s, make sure rpc service %q is already started",
server, err.Error(), service)
}
c.conn = conn
return nil
}
// WithStreamClientInterceptors uses given client stream interceptors.
func WithStreamClientInterceptors(interceptors ...grpc.StreamClientInterceptor) grpc.DialOption {
return grpc.WithChainStreamInterceptor(interceptors...)
}
// WithUnaryClientInterceptors uses given client unary interceptors.
func WithUnaryClientInterceptors(interceptors ...grpc.UnaryClientInterceptor) grpc.DialOption {
return grpc.WithChainUnaryInterceptor(interceptors...)
}
其他拦截器本篇暂不介绍,先重点看一下熔断器拦截器BreakerInterceptor
func BreakerInterceptor(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
breakerName := path.Join(cc.Target(), method) //拦截器名是target+method
return breaker.DoWithAcceptable(breakerName, func() error {
return invoker(ctx, method, req, reply, cc, opts...)//发起一次grpc请求
}, codes.Acceptable)
}
//定义哪些错误码为需要拦截的。
// Acceptable checks if given error is acceptable.
func Acceptable(err error) bool {
switch status.Code(err) {
case codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss, codes.Unimplemented:
return false
default:
return true
}
}
// DoWithAcceptable calls Breaker.DoWithAcceptable on the Breaker with given name.
func DoWithAcceptable(name string, req func() error, acceptable Acceptable) error {
return do(name, func(b Breaker) error {
return b.DoWithAcceptable(req, acceptable)
})
}
//获取拦截器
func do(name string, execute func(b Breaker) error) error {
return execute(GetBreaker(name))
}
// GetBreaker returns the Breaker with the given name.
func GetBreaker(name string) Breaker {
lock.RLock()
b, ok := breakers[name]
lock.RUnlock()
if ok {
return b
}
lock.Lock()
b, ok = breakers[name]
if !ok {
b = NewBreaker(WithName(name))
breakers[name] = b
}
lock.Unlock()
return b
}
最终调用的是
func (cb *circuitBreaker) DoWithAcceptable(req func() error, acceptable Acceptable) error {
return cb.throttle.doReq(req, nil, acceptable)
}
底层使用的是googleBreaker,其判断是否熔断的条件是:
//按照最近一段时间的请求数据计算是否熔断
func (b *googleBreaker) accept() error {
//获取最近一段时间的统计数据
accepts, total := b.history()
//计算动态熔断概率
weightedAccepts := b.k * float64(accepts)
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
//概率为0,通过
if dropRatio <= 0 {
return nil
}
//随机产生0.0-1.0之间的随机数与上面计算出来的熔断概率相比较
//如果随机数比熔断概率小则进行熔断
if b.proba.TrueOnProba(dropRatio) {
return ErrServiceUnavailable
}
return nil
}
参考文章:
grpc拦截器: https://zhuanlan.zhihu.com/p/...
go-zero服务治理-自适应熔断器:https://juejin.cn/post/702853...