熔断在分布式系统的作用已经被强调过很多次了
可以通过这篇文章来了解价值,Netflix在自己的分布式系统中应用熔断技术来保护系统
blog.51cto.com/developeryc…
内部的实现机制可以参考
martinfowler.com/bliki/Circu…
本篇文章将介绍go chassis如何通过熔断机制,隔离上游服务,保护下游服务。
Go chassis如何保证上游错误不影响下游系统
go chassis引用并包装了https://github.com/afex/hystrix-go带来了熔断和降级功能。
当运行时内部处理中的协程达到一定阈值,错误率达到一定阈值,或者超时达到一定阈值时,就会触发熔断,用户可按需定制调教熔断器配置项设定这些参数。
hystrix-go内部的熔断逻辑
go chassis使用统一的invocation抽象来代表每一次远程调用,hystrix-go使用command抽象来封装任何一个执行片段,invocation会被强制封装到command中,并在一个circuit中执行。
每个Circuit都是唯一的Name,并且有一个Ticket桶,用来存放ticket,一开始它是关闭状态,即一切运转正常
调用将被强制性的包装进入circuit独立协程池中,并领取一个ticket。
command最终只有2种状态,超时,或者完成。每当达到这两个状态就会归还ticket
在这里可以看到ticket机制其实跟限流中的令牌桶算法很像。
当超时或者拿不到ticket时就会被记为一次错误,当错误达到一定阈值,circuit就会打开,拒绝发送网络请求
服务级别隔离
每个service内部会有多个circuit,每个circuit对应一个上游微服务。当service3出现问题时(如死锁,或是并发量太大),将物理进行隔绝,即不再发送任何请求,以保证系统健康,service1依然可以正常和2,4交互,保证大部分业务正常。
这么来看还是很理想的,serivce3的错误调用不至于拖垮service1(如果死锁了,很容易就拖垮service1,导致这个由四个服务组成的系统瘫痪),但真的如此么,让我们看看层级复杂些的系统。
为何服务级别隔离还不够?
每个服务都是基于go chassis开发的
假设api2需要调用service4完成,api1调用3完成,api3调用5完成
service4内的死锁导致api2失败了,最终触发熔断。service1将整个service2全部隔离了,导致一个小小的死锁,引发了系统快速失败。
看上去熔断在这里反而起到了坏的效果,那让我们看看没熔断会发生什么
不加入熔断
这时就看哪个客户端做了超时处理了,因为死锁的存在,会导致整条调用链路挂死,最终导致客户端端口耗尽后,进而快速失败
现在来看,死锁在一个不健壮的系统中是一定会拖垮整个分布式系统的,无解
有熔断和没熔断效果都一样,最终都是快速失败。那么如何解决
API级别熔断
每个circuit只负责一个API的执行,监控,隔离
当service2调用service4时,单独的接口进入到隔离状态而不影响其他API调用。
总结
通过这篇文章我们知道了服务级别的错误隔离是不够的,结构不复杂的系统尚可接受,但是复杂后不能因为一个API的错误而隔离整个服务,而是细粒度的进行隔离。go chassis提供了API级别熔断帮助开发者快速隔离问题服务。
熔断的手段有超时实践,并发数,错误率等。它强制性的保护起每一次远程调用,无需开发者自己编写代码处理超时,死锁,网络错误等问题,解放了开发者,让他们更多的去关注业务代码而不是分布式系统带来的复杂性
项目资料
go chassis开发框架:https://github.com/go-chassis/go-chassis
熔断文档:https://go-chassis.readthedocs.io/en/latest/user-guides/cb-and-fallback.html
go chassis系列文章:
https://juejin.im/post/5ba34495e51d450e9e440d1f
https://juejin.im/post/5ba460556fb9a05d2469bb81