您好,登录后才能下订单哦!
Consul故障分析与优化是怎么样的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
从微服务平台的角度出发希望提供统一的服务注册中心,让任何的业务和团队只要使用这套基础设施,相互发现只需要协商好服务名即可;还需要支持业务做多DC部署和故障切换。由于在扩展性和多DC支持上的良好设计,我们选择了Consul,并采用了Consul推荐的架构,单个DC内有Consul Server和Consul Agent,DC之间是WAN模式并且相互对等,结构如下图所示:
注:图中只画了四个DC,实际生产环境根据公司机房建设以及第三方云的接入情况,共有十几个DC。
爱奇艺内部的容器应用平台QAE与Consul进行了集成。由于早期是基于Mesos/Marathon体系开发,没有Pod容器组概念,无法友好的注入sidecar的容器,因此我们选择了微服务模式中的第三方注册模式,即由QAE系统实时向Consul同步注册信息,如下图所示;并且使用了Consul的external service模式,这样可以避免两个系统状态不一致时引起故障,例如Consul已经将节点或服务实例判定为不健康,但是QAE没有感知到,也就不会重启或重新调度,导致没有健康实例可用。
其中QAE应用与服务的关系表示例如下:
每个QAE应用代表一组容器,应用与服务的映射关系是松耦合的,根据应用实际所在的DC将其关联到对应Consul DC即可,后续应用容器的更新、扩缩容、失败重启等状态变化都会实时体现在Consul的注册数据中。
微服务平台API网关是服务注册中心最重要的使用方之一。网关会根据地区、运营商等因素部署多个集群,每个网关集群会根据内网位置对应到一个Consul集群,并且从Consul查询最近的服务实例,如下图所示:
这里我们使用了Consul的PreparedQuery功能,对所有服务优先返回本DC服务实例,如果本DC没有则根据DC间RTT由近到远查询其它DC数据。
Consul故障
Consul从2016年底上线开始,已经稳定运行超过三年时间,但是最近我们却遇到了故障,收到了某个DC多台Consul Server不响应请求、大量Consul Agent连不上Server的告警,并且没有自动恢复。Server端观察到的现象主要有:
raft协议不停选举失败,无法获得leader;
HTTP&DNS查询接口大量超时,观察到有些超过几十秒才返回(正常应当是毫秒级别返回);
goroutine快速线性上升,内存同步上升,最终触发系统OOM;在日志中没能找到明确的问题,从监控metrics则观察到PreparedQuery的执行耗时异常增大,如下图所示:
此时API网关查询服务信息也超时失败,我们将对应的网关集群切到了其它DC,之后重启Consul进程,恢复正常。
故障分析
经过日志排查,发现故障前发生过DC间的网络抖动(RTT增加,伴随丢包),持续时间大约1分钟,我们初步分析是DC间网络抖动导致正常收到的PreparedQuery请求积压在Server中无法快速返回,随着时间积累越来越多,占用的goroutine和内存也越来越多,最终导致Server异常。
跟随这个想法,尝试在测试环境复现,共有4个DC,单台Server的PreparedQuery QPS为1.5K,每个PreparedQuery查询都会触发3次跨DC查询,然后使用tc-netem工具模拟DC间的RTT增加的情况,得到了以下结果:
当DC间RTT由正常的2ms变为800ms之后,Consul Server的goroutine、内存确实会线性增长,PreparedQuery执行耗时也线性增长,如下图所示:
虽然goroutine、内存在增长,但是在OOM之前,Consul Server的其它功能未受影响,Raft协议工作正常,本DC的数据查询请求也能正常响应;
在DC间RTT恢复到2ms的一瞬间,Consul Server丢失leader,接着Raft不停选举失败,无法恢复。
以上操作能够稳定的复现故障,使分析工作有了方向。首先基本证实了goroutine和内存的增长是由于PreparedQuery请求积压导致的,而积压的原因在初期是网络请求阻塞,在网络恢复后仍然积压原因暂时未知,这时整个进程应当是处于异常状态;那么,为什么网络恢复之后Consul反而故障了呢?Raft只有DC内网络通信,为什么也异常了呢?是最让我们困惑的问题。
最开始的时候将重点放在了Raft问题上,通过跟踪社区issue,找到了hashicorp/raft#6852,其中描述到我们的版本在高负载、网络抖动情况下可能出现raft死锁,现象与我们十分相似。但是按照issue更新Raft库以及Consul相关代码之后,测试环境复现时故障依然存在。
之后尝试给Raft库添加日志,以便看清楚Raft工作的细节,这次我们发现Raft成员从进入Candidate状态,到请求peer节点为自己投票,日志间隔了10s,而代码中仅仅是执行了一行metrics更新,如下图所示:
因此怀疑metrics调用出现了阻塞,导致整个系统运行异常,之后我们在发布历史中找到了相关优化,低版本的armon/go-metrics在Prometheus实现中采用了全局锁sync.Mutex,所有metrics更新都需要先获取这个锁,而v0.3.3版本改用了sync.Map,每个metric作为字典的一个键,只在键初始化的时候需要获取全局锁,之后不同metric更新值的时候就不存在锁竞争,相同metric更新时使用sync.Atomic保证原子操作,整体上效率更高。更新对应的依赖库之后,复现网络抖动之后,Consul Server可以自行恢复正常。
这样看来的确是由于metrics代码阻塞,导致了系统整体异常。但我们依然有疑问,复现环境下单台Server 的PreparedQuery QPS为1.5K,而稳定的网络环境下单台Server压测QPS到2.8K时依然工作正常。也就是说正常情况下原有代码是满足性能需求的,只有在故障时出现了性能问题。
接下来的排查陷入了困境,经过反复试验,我们发现了一个有趣的现象:使用go 1.9编译的版本(也是生产环境使用的版本)能复现出故障;同样的代码使用go 1.14编译就无法复现出故障。经过仔细查看,我们在go的发布历史中找到了以下两条记录:
根据代码我们找到了用户反馈在go1.9~1.13版本,在大量goroutine同时竞争一个sync.Mutex时,会出现性能急剧下降的情况,这能很好的解释我们的问题。由于Consul代码依赖了go 1.9新增的内置库,我们无法用更低的版本编译,因此我们将go 1.14中sync.Mutex相关的优化去掉,如下图所示,然后用这个版本的go编译Consul,果然又可以复现我们的故障了。
回顾语言的更新历史,go 1.9版本添加了公平锁特性,在原有normal模式上添加了starvation模式,来避免锁等待的长尾效应。但是normal模式下新的goroutine在运行时有较高的几率竞争锁成功,从而免去goroutine的切换,整体效率是较高的;而在starvation模式下,新的goroutine不会直接竞争锁,而是会把自己排到等待队列末端,然后休眠等待唤醒,锁按照等待队列FIFO分配,获取到锁的goroutine被调度执行,这样会增加goroutine调度、切换的成本。在go 1.14中针对性能问题进行了改善,在starvation模式下,当goroutine执行解锁操作时,会直接将CPU时间让给下一个等待锁的goroutine执行,整体上会使得被锁保护部分的代码得到加速执行。
到此故障的原因就清楚了,首先网络抖动,导致大量PreparedQuery请求积压在Server中,同时也造成了大量的goroutine和内存使用;在网络恢复之后,积压的PreparedQuery继续执行,在我们的复现场景下,积压的goroutine量会超过150K,这些goroutine在执行时都会更新metrics从而去获取全局的sync.Mutex,此时切换到starvation模式并且性能下降,大量时间都在等待sync.Mutex,请求阻塞超时;除了积压的goroutine,新的PreparedQuery还在不停接收,获取锁时同样被阻塞,结果是sync.Mutex保持在starvation模式无法自动恢复;另一方面raft代码运行会依赖定时器、超时、节点间消息的及时传递与处理,并且这些超时通常是秒、毫秒级别的,但metrics代码阻塞过久,直接导致时序相关的逻辑无法正常运行。
接着生产环境中我们将发现的问题都进行了更新,升级到go 1.14,armon/go-metrics v0.3.3,以及hashicorp/raft v1.1.2版本,使Consul达到一个稳定状态。此外还整理完善了监控指标,核心监控包括以下维度:
进程:CPU、内存、goroutine、连接数
Raft:成员状态变动、提交速率、提交耗时、同步心跳、同步延时
RPC:连接数、跨DC请求数
写负载:注册&解注册速率
读负载:Catalog/Health/PreparedQuery请求量,执行耗时
冗余注册
根据Consul的故障期间的故障现象,我们对服务注册中心的架构进行了重新审视。
在Consul的架构中,某个DC Consul Server全部故障了就代表这个DC故障,要靠其它DC来做灾备。但是实际情况中,很多不在关键路径上的服务、SLA要求不是特别高的服务并没有多DC部署,这时如果所在DC的Consul故障,那么整个服务就会故障。
针对本身并没有做多DC部署的服务,如果可以在冗余DC注册,那么单个DC Consul故障时,其它DC还可以正常发现。因此我们修改了QAE注册关系表,对于本身只有单DC部署的服务,系统自动在其它DC也注册一份,如下图所示:
QAE这种冗余注册相当于在上层做了数据多写操作。Consul本身不会在各DC间同步服务注册数据,因此直接通过Consul Agent方式注册的服务还没有较好的冗余注册方法,还是依赖服务本身做好多DC部署。
保障API网关
目前API网关的正常工作依赖于Consul PreparedQuery查询结果在本地的缓存,目前的交互方式有两方面问题:
网关缓存是lazy的,网关第一次用到时才会从Consul查询加载,Consul故障时查询失败会导致请求转发失败;
PreparedQuery内部可能会涉及多次跨DC查询,耗时较多,属于复杂查询,由于每个网关节点需要单独构建缓存,并且缓存有TTL,会导致相同的PreparedQuery查询执行很多次,查询QPS会随着网关集群规模线性增长。
为了提高网关查询Consul的稳定性和效率,我们选择为每个网关集群部署一个单独的Consul集群,如下图所示:
图中红色的是原有的Consul集群,绿色的是为网关单独部署的Consul集群,它只在单DC内部工作。我们开发了Gateway-Consul-Sync组件,它会周期性的从公共Consul集群读取服务的PreparedQuery查询结果,然后写入到绿色的Consul集群,网关则直接访问绿色的Consul进行数据查询。这样改造之后有以下几方面好处:
从支持网关的角度看,公共集群的负载原来是随网关节点数线性增长,改造后变成随服务个数线性增长,并且单个服务在同步周期内只会执行一次PreparedQuery查询,整体负载会降低;
图中绿色Consul只供网关使用,其PreparedQuery执行时所有数据都在本地,不涉及跨DC查询,因此复杂度降低,不受跨DC网络影响,并且集群整体的读写负载更可控,稳定性更好;
当公共集群故障时,Gateway-Consul-Sync无法正常工作,但绿色的Consul仍然可以返回之前同步好的数据,网关还可以继续工作;
由于网关在改造前后查询Consul的接口和数据格式是完全一致的,当图中绿色Consul集群故障时,可以切回到公共Consul集群,作为一个备用方案。
关于Consul故障分析与优化是怎么样的问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。