今天我们来讨论微服务架构中的自我恢复能力 。通常情况下,服务间会通过同步或异步的方式进行通信 。我们假定把一个庞大的系统分解成一个个的小块能将各个服务解耦 。管理服务内部的通信可能有点困难了 。你可能听说过这两个著名的概念:熔断和重试 。
熔断器
文章插图
01
想象一个简单的场景:用户发出的请求访问服务 A 随后访问另一个服务 B 。我们可以称 B 是 A 的依赖服务或下游服务 。到服务 B 的请求在到达各个实例前会先通过负载均衡器 。
后端服务发生系统错误的原因有很多,例如慢查询、network blip 和内存争用 。在这种场景下,如果返回 A 的 response 是 timeout 和 server error,我们的用户会再试一次 。在混乱的局面中我们怎样来保护下游服务呢?
文章插图
02
熔断器可以让我们对失败率和资源有更好的控制 。熔断器的设计思路是不等待 TCP 的连接 timeout 快速且优雅地处理 error 。这种 fail fast 机制会保护下游的那一层 。这种机制最重要的部分就是立刻向调用方返回 response 。没有被 pending request 填充的线程池,没有 timeout,而且极有可能烦人的调用链中断者会更少 。此外,下游服务也有了充足的时间来恢复服务能力 。完全杜绝错误很难,但是减小失败的影响范围是有可能的 。
文章插图
03
通过 hystrix 熔断器,我们可以采用降级方案,对上游返回降级后的结果 。例如,服务 B 可以访问一个备份服务或 cache,不再访问原来的服务 C 。引入这种降级方案需要集成测试,因为我们在 hAppy path(译注:所谓 happy path,即测试方法的默认场景,没有异常和错误信息 。具体可参见 wikipedia)可能不会遇到这种网络模式 。
状态
文章插图
04
熔断器有三个主要的状态:
- Closed:让所有请求都通过的默认状态 。在阈值下的请求不管成功还是失败,熔断器的状态都不会改变 。可能出现的错误是 Max Concurrency(最大并发数)和 Timeout(超时) 。
- Open:所有的请求都会返回 Circuit Open 错误并被标记为失败 。这是一种不等待处理结束的 timeout 时间的 fail-fast 机制 。
- Half Open:周期性地向下游服务发出请求,检查它是否已恢复 。如果下游服务已恢复,熔断器切换到 Closed 状态,否则熔断器保持 Open 状态 。
// CommandConfig is used to tune circuit settings at runtimetype CommandConfig struct { Timeout int `json:"timeout"` MaxConcurrentRequests int `json:"max_concurrent_requests"` RequestVolumeThreshold int `json:"request_volume_threshold"` SleepWindow int `json:"sleep_window"` ErrorPercentThreshold int `json:"error_percent_threshold"`}
查看源码可以通过根据两个服务的 SLA( Service Level Agreement,服务级别协议)来定出阈值 。如果在测试时把依赖的其他服务也涉及到了,这些值会得到很好的调整 。
一个好的熔断器的名字应该能精确指出哪个服务连接出了问题 。实际上,请求一个服务时可能会有很多个 API endpoint 。每一个 endpoint 都应该有一个对应的熔断器 。
生产上的熔断器熔断器通常被放在聚合点上 。尽管熔断器提供了一种 fail-fast 机制,但我们仍然需要确保可选的降级方案可行 。如果我们因为假定需要降级方案的场景出现的可能性很小就不去测试它,那(之前的努力)就是白费力气了 。即使在最简单的演练中,我们也要确保阈值是有意义的 。以我的个人经验,把参数配置在 log 中 print 出来对于 debug 很有帮助 。
Demo这段实例代码用的是 hystrix-go 库,hystrix Netflix 库在 Golang 的实现 。
package mainimport ( "errors" "fmt" "log" "net/http" "os" "github.com/afex/hystrix-go/hystrix")const commandName = "producer_api"func main() { hystrix.ConfigureCommand(commandName, hystrix.CommandConfig{ Timeout: 500, MaxConcurrentRequests: 100, ErrorPercentThreshold: 50, RequestVolumeThreshold: 3, SleepWindow: 1000, }) http.HandleFunc("/", logger(handle)) log.Println("listening on :8080") http.ListenAndServe(":8080", nil)}func handle(w http.ResponseWriter, r *http.Request) { output := make(chan bool, 1) errors := hystrix.Go(commandName, func() error { // talk to other services err := callChargeProducerAPI() // err := callWithRetryV1() if err == nil { output <- true } return err }, nil) select { case out := <-output: // success log.Printf("success %v", out) case err := <-errors: // failure log.Printf("failed %s", err) }}// logger is Handler wrapper function for loggingfunc logger(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { log.Println(r.URL.Path, r.Method) fn(w, r) }}func callChargeProducerAPI() error { fmt.Println(os.Getenv("SERVER_ERROR")) if os.Getenv("SERVER_ERROR") == "1" { return errors.New("503 error") } return nil}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 如何做好门店服务 如何提高客户进店服务
- 使用Transformer构建自己的机器翻译服务
- 不用工具直接从微软官网下载Win10正式版ISO镜像
- 我终于搞懂了微服务,太不容易了...
- 一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权
- 玩转Win10时间线技巧汇总
- 淘宝客怎么设置佣金比例和服务费 淘宝店铺佣金
- 车辆出现故障,这4项免费救援服务随叫随到,不要傻傻地找4S店了
- 大红袍的口感,有点稍微的扭曲感
- 微商和网店哪个靠谱 开网店好还是做微商好