设为首页 加入收藏

TOP

Go微服务容错与韧性(Service Resilience)(三)
2019-09-26 18:14:08 】 浏览:162
Tags:服务 容错 韧性 Service Resilience
ServiceMiddleware struct { Next pb.CacheServiceServer } func BuildGetMiddleware(cs pb.CacheServiceServer ) pb.CacheServiceServer { tm := ThrottleMiddleware{cs} csm := CacheServiceMiddleware{&tm} return &csm } func (csm *CacheServiceMiddleware) Get(ctx context.Context, req *pb.GetReq) (*pb.GetResp, error) { return csm.Next.Get(ctx, req) } func (csm *CacheServiceMiddleware) Store(ctx context.Context, req *pb.StoreReq) (*pb.StoreResp, error) { return csm.Next.Store(ctx, req) } func (csm *CacheServiceMiddleware) Dump(dr *pb.DumpReq, csds pb.CacheService_DumpServer) error { return csm.Next.Dump(dr, csds) }

下面是服务限流的实现程序,它比其他的功能要稍微复杂一些。其他功能使用的的控制参数(例如重试次数)在执行过程中是不会被修改的,而它的(throttle)是可以并行读写的,因此需要用“sync.RWMutex”来控制。具体的限流功能在“Get”函数中,它首先判断是否超过阀值(throttle),超过则返回错误信息,反之则运行。

const (
    service_throttle = 5
)
var tm throttleMutex

type ThrottleMiddleware struct {
    Next  pb.CacheServiceServer
}

type throttleMutex struct {
    mu       sync.RWMutex
    throttle int
}

func (t *throttleMutex )getThrottle () int {
    t.mu.RLock()
    defer t.mu.RUnlock()
    return t.throttle
}
func (t *throttleMutex) changeThrottle(delta int ) {
    t.mu.Lock()
    defer t.mu.Unlock()
    t.throttle =t.throttle+delta
}

func (tg *ThrottleMiddleware) Get(ctx context.Context, req *pb.GetReq) (*pb.GetResp, error) {
    if tm.getThrottle()>=service_throttle {
        log.Printf("Get throttle=%v reached\n", tm.getThrottle())
        return nil, errors.New("service throttle reached, please try later")
    } else {
        tm.changeThrottle(1)
        resp, err := tg.Next.Get(ctx, req)
        tm.changeThrottle(-1)
        return resp, err
    }
}

熔断器 (Circuit Breaker):

熔断器是最复杂的。 它的主要功能是当系统检测到下游服务不畅通时对这个服务进行熔断,这样就阻断了所有对此服务的调用,减少了下游服务的负载,让下游服务有个缓冲来恢复功能。与之相关的就是服务降级,下游服务没有了,需要的数据怎么办?一般是定义一个降级函数,或者是从缓存里取旧的数据或者是直接返回空值给上游函数,这要根据业务逻辑来定。下面是它的服务示意图。服务A有三个下游服务,服务B,服务C,服务D。其中前两个服务的熔断器是关闭的,也就是服务是畅通的。服务D的熔断器是打开的,也就是服务异常。
file

图片来源

熔断器用状态机(State Machine)来进行管理,它会监测对下游服务的调用失败情况,并设立一个失败上限阀值,由阀值来控制状态转换。它有三个状态:关闭,打开和半打开。这里的“关闭“是熔断器的关闭,服务是打开的。下面是它的简单示意图。

file

图片来源

正常情况熔断器是关闭的,当失败请求数超过阀值时,熔断器打开,下游服务关闭。熔断器打开是有时间限制的,超时之后自动变成半打开状态,这时只放一小部分请求通过。当请求失败时,返回打开状态,当请求成功并且数量超过阀值时,熔断器状态变成关闭,恢复正常。下面是它的详细示意图。它图里有伪程序,可以仔细读一下,读懂了,就明白了实现原理。

file

图片来源

当有多个修饰功能时,咋一看熔断器应该是第一个执行的,因为如果服务端出了问题最好的对策就是屏蔽掉请求,其他的就不要试了,例如服务重试。但仔细一想,这样的话服务重试就没有被熔断器监控,因此熔断器还是最后一个执行好。不过具体情况还是要根据你有哪些修饰功能来决定。

熔断器有很多不同的实现,其中最出名的可能是Netflix的“Hystrix”。本程序用的是一个go开源库叫gobreaker, 因为它比较纯粹(Hystrix把很多东西都集成在一起了),当然熔断的原理都是一样的。下面是熔断器的程序。其中“cb”是“CircuitBreaker”的变量,它是在“init()”里面创建的,这样保证它只创建一次。在创建时,就设置了具体参数。“MaxRequests”是在半开状态下允许通过的最大请求数。”Timeout“是关闭状态的超时时间(超时后自动变成半开状态),这里没有设置,取缺省值60秒。“ReadyToTrip”函数用来控制状态转换,当它返回true时,熔断器由关闭变成打开。熔断器的功能是在函数“CallGet”中实现的,它的执行函数是“cb.Execute”, 只要把要运行的函数传入就行了。如果熔断器是打开状态,它就返回缺省值,如果熔断器是关闭状态,它就运行传入的请求。熔断器自己会监测请求的执行状态并根据它的信息来控制开关转换。

var cb *gobreaker.CircuitBreaker

type CircuitBreakerCallGet struct {
    Next callGetter
}
func init() {
    var st gobreaker.Settings
    st.Name = "CircuitBreakerCallGet"
    st.MaxRequests = 2
    st.ReadyToTrip = func(counts gobreaker.Counts) bool {
        failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
        return counts.Requests >= 2 && failureRatio >= 0.6
    }
    cb = gobreaker.NewCircuitBreaker(st)
}

func (tcg *Circuit
首页 上一页 1 2 3 4 5 下一页 尾页 3/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇[系列] go-gin-api 路由中间件 - .. 下一篇阿里云CentOS服务器下安装Golang1..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目