微服务组件之限流器与熔断器

在微服务架构里面一个很常见的问题就是服务之间的延迟和通信失败问题,极端的情况下,甚至会因为某个服务的性能下降或者故障宕机,导致访问超时,层层传递,引发雪崩,最终导致整个系统崩溃,而限流器和熔断器(这两个组件都是客户端的)能很好的解决这个问题,提高系统的可靠性和稳定性

限流器

限流器,从字面上理解就是用来限制流量,有时候流量突增(可预期的比如“双11”,不可预期的微博的热门话题等),会将后端服务压垮,甚至直接宕机,使用限流器能限制访问后端的流量,起到一个保护作用,被限制的流量,可以根据具体的业务逻辑去处理,直接返回错误或者返回默认值等等

golang 提供了拓展库(golang.org/x/time/rate)提供了限流器组件,用法上也很简单直观,通过下面这段代码就可以创建一个限流器

// 每 800ms 产生 1 个 token,最多缓存 1 个 token,如果缓存满了,新的 token 会被丢弃
limiter := rate.NewLimiter(rate.Every(time.Duration(800)*time.Millisecond), 1)

限流器提供三种使用方式,Allow, Wait, Reserve

Allow: 返回是否有 token,没有 token 返回 false,或者消耗 1 个 token 返回 true
Wait: 阻塞等待,知道取到 1 个 token
Reserve: 返回 token 信息,Allow 其实相当于 Reserve().OK,此外还会返回需要等待多久才有新的 token

一般使用 Wait 的场景会比较多一些

if err := limiter.Wait(context.Background()); err != nil {
    panic(err)
}

// do you business logic

熔断器

和限流器对依赖服务的保护机制不一样,熔断器是当依赖的服务已经出现故障时,为了保证自身服务的正常运行不再访问依赖的服务,防止雪崩效应

熔断器有三种状态:

  • 关闭状态:服务正常,并维护一个失败率统计,当失败率达到阀值时,转到开启状态
  • 开启状态:服务异常,调用 fallback 函数,一段时间之后,进入半开启状态
  • 半开启装态:尝试恢复服务,失败率高于阀值,进入开启状态,低于阀值,进入关闭状态

github.com/afex/hystrix-go,提供了 go 熔断器实现,使用上面也很方便,首先创建一个熔断器

hystrix.ConfigureCommand(
    "addservice", // 熔断器名字,可以用服务名称命名,一个名字对应一个熔断器,对应一份熔断策略
    hystrix.CommandConfig{
        Timeout:                100,  // 超时时间 100ms
        MaxConcurrentRequests:  2,    // 最大并发数,超过并发返回错误
        RequestVolumeThreshold: 4,    // 请求数量的阀值,用这些数量的请求来计算阀值
        ErrorPercentThreshold:  25,   // 错误数量阀值,达到阀值,启动熔断
        SleepWindow:            1000, // 熔断尝试恢复时间
    },
)

提供了阻塞和非阻塞两种使用方式,完整代码可以参考如下链接: https://github.com/hatlonely/hellogolang/blob/master/sample/addservice/cmd/client/main.go

阻塞使用 Do 方法,返回一个 err

err := hystrix.Do("addservice", func() error {
    // 正常业务逻辑,一般是访问其他资源
    var err error
    // 设置总体超时时间 10 ms 超时
    ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50*time.Millisecond))
    defer cancel()
    res, err = client.Add(
        ctx, req,
        // 这里可以再次设置重试次数,重试时间,重试返回码
        grpc_retry.WithMax(3),
        grpc_retry.WithPerRetryTimeout(time.Duration(5)*time.Millisecond),
        grpc_retry.WithCodes(codes.DeadlineExceeded),
    )
    return err
}, func(err error) error {
    // 失败处理逻辑,访问其他资源失败时,或者处于熔断开启状态时,会调用这段逻辑
    // 可以简单构造一个response返回,也可以有一定的策略,比如访问备份资源
    // 也可以直接返回 err,这样不用和远端失败的资源通信,防止雪崩
    // 这里因为我们的场景太简单,所以我们可以在本地在作一个加法就可以了
    fmt.Println(err)
    res = &addservice.AddResponse{V: req.A + req.B}
    return nil
})

非阻塞方法使用 Go 方法,返回一个 error 的 channel,建议在有多个资源需要并发访问的场景下是使用

errc1 := hystrix.Go("addservice", func() error {
    var err error
    ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50*time.Millisecond))
    defer cancel()
    res1, err = client.Add(ctx, req)
    if err == nil {
        success <- struct{}{}
    }
    return err
}, nil)

// 有 fallback 处理
errc2 := hystrix.Go("addservice", func() error {
    var err error
    ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50*time.Millisecond))
    defer cancel()
    res2, err = client.Add(ctx, req)
    if err == nil {
        success <- struct{}{}
    }
    return err
}, func(err error) error {
    fmt.Println(err)
    res2 = &addservice.AddResponse{V: req.A + req.B}
    success <- struct{}{}
    return nil
})

for i := 0; i < 2; i++ {
    select {
    case <-success:
        fmt.Println("success", i)
    case err := <-errc1:
        fmt.Println("err1:", err)
    case err := <-errc2:
        // 这个分支永远不会走到,因为熔断机制里面永远不会返回错误
        fmt.Println("err2:", err)
    }
}

参考链接

转载请注明出处
本文链接:http://www.hatlonely.com/2018/06/21/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%BB%84%E4%BB%B6%E4%B9%8B%E9%99%90%E6%B5%81%E5%99%A8%E4%B8%8E%E7%86%94%E6%96%AD%E5%99%A8/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容