随着近几年好大夫在线全面转向微服务化,服务间调用复杂度急剧上升,故障难定位,技术债务难量化等相关问题随之而来,如服务稳定性没有统一衡量标准,服务容量配比是否合适等等。面对这些问题,好大夫在线基础架构部积极探索落地全链路监控系统,主要包括链路诊断、容量监控、应用运行时健康画像、实时告警及应用风险评估模型。本系列文章将从不同点切入,对好大夫在线落地全链路监控系统做一个介绍。
SRE(Site Reliability Engineering)在选择指标SLI(Service Level Indicator)时,有四个黄金指标:延迟、流量、错误和饱和度(取自 Google SRE 的书籍)。
本期我们分析一下“延迟”指标如何作用于应用风险识别系统。
接口请求响应时间指示着应用单机峰值的QPS/TPS(在机器硬件配置相同的情况下)。
响应越快能并发处理的用户请求也就越多,水平扩展时收益也越大,能减少获客成本和机器成本。
请求慢有很多因素,从用户角度来看,CDN回源、css/js/html渲染、网络抖动、微服务接口粒度过细、局域网内部请求过多、机器硬件、依赖中间件等等都会影响请求的耗时,接下来我们从微服务局域网请求角度来分析。
慢接口:直观来看就是接口慢。那接口耗时多少算慢呢?统计接口平均耗时大于某个阈值?一次响应慢就算吗?
MessageQueue消费事件算吗?定时任务里面的方法执行慢算吗?链路入口的慢接口又是如何判定的?基于这些疑问,我们根据经验总结出慢接口的具体说明。
慢接口:当服务发起方单次RPC请求响应耗时大于当前应用95%请求耗时计数累加1,一个分析周期后,当前应用累计数排名TOPK的接口标记为慢接口。
名词解释:
因为通常对于延迟这个指标,我们不会直接做所有请求延迟的平均,因为整个延迟的分布也符合正态分布,所以通常会以类似 “90% 请求的延迟 <= 80ms,或者 95% 请求的延迟 <=120ms ”这样的方式来设定延迟 SLO(Service Level Objective) ,熟悉数理统计的同学应该知道,这个 90% 或 95% 我们称之为置信区间。
由于每个系统目前的机器分布不均衡,调用量不相同,再加上历史遗留代码,改造成本也不一样。为了让优化收益最大化,我们目前的规则是:按应用内接口横向比较,取应用的p95耗时,再根据调用频率,优先改排名前5的接口。优化是一个持续的过程,如果所有的应用都卡一个指标去优化,对那些老系统来说,短期内很难达成目标。先优化收益高的任务,有效果了,再优化新生成的任务,形成积极的正反馈。
对延迟有一定了解之后,我们要怎么做呢,也就是说我们的目标是什么,延迟SLO(Service Level Objective) 优化目标,根据前面的分析,我们选择为应用接口访问量大耗时大于当前应用95%请求的接口。这样随着优化的进行,p95 在逐渐下降 等p95达标后,可以优化p99,p99.9。
a. 日志存储本地磁盘,基于Flume异步收集,推送到Kafka,日志分析系统Snow(自研)订阅Kafka消息进行流式处理,准实时分析;
b. 每条日志携带的信息比较丰富,日志分析系统采用Goroutine,按照目前定义的规则(如慢接口、异常事件、慢SQL、循环远程调用等)进行并发分析;
c. 分析后,生成风险指标Metrics,判断系统(Dolphin,见:好大夫在线监控系统答辩的60分钟)周期性拉取Metrics存入Prometheus,通过预设的判定规则,触发生成风险事件;
d. Task任务追踪系统(自研)订阅风险事件,生成可追踪的任务后,下发到各个事业部的开发人员名下(直接生成对应的JIRA任务);
e. 测试同学管理自己部门相关的改进任务 -> 开发同学进行优化后上线 -> 测试同学线上验证后关闭;
f. 整个流程从风险评估系统发起,到风险评估系统持续追踪验证,形成闭环;
宏观架构如下:
我们如何找到慢接口?
我们将所有RPC请求的耗时,记录到日志(从发出请求 到 接收到请求 一个来回),辅助诊断工具“APM链路分析”,标注风险点。
现在的风险评估模型,分析出来的任务都统一分配给了服务调用方,即分给了上游服务,我们认为,应用负责人应该感知自己调用下游的健康状态,对影响接口的因素要做到心中有数,如果链路跨度比较大, 比如 SystemA::methodA()调用SystemB::methodB()而SystemB::methodB()又调用了SystemC::methodC(),如果SystemC::methodC() 耗时超过100ms。这时候SystemA,SystemB,SystemC 都会生成任务,SystemA,SystemB负载人 推动SystemC负责人 去优化SystemC::methodC()。
任务状态流转,从创建到关闭形成闭环:
每个应用面临的状态不一样,我们会优先选择收益高的慢接口生成任务,应用方再结合调用量,和修改难易程度和项目排期自己合理安排。
由于网络抖动产生的临时事件,或者依赖的框架异常,中间件突发异常生成的任务,可以直接静默该任务,默认静默一个月。
有些应用历史包袱比较重,调用跨度深,或者准备重构的均可以参考以下思路处理。
慢接口模型会分析出这类接口,在优化和平时写代码的时候一定要注意,如果要延迟处理部分逻辑。可以考虑提交事务后触发mq异步时间,如果是缓解并发问题可以利用加锁,而不是随机增加sleep时间。
如controller层调用接单服务,优化后,本来SystemB::AddItemToFlow() 属于SystemA::CreateOrder()的领域逻辑,拆到Controller后,虽然SystemA健康了,但整个链路耗时依然很大,并且破坏了SystemA的领域逻辑。
比如按用户展示推荐文章列表,如果直接按入参设计片段缓存,由于userid不一样,会大量申请缓存,命中率没有提高,相当于每次还是直接查询数据库。
如何验证优化效果,应用变好或变坏的信号? 关注应用风险评估系统的每周周报,关注风险评级指标,关注优化后的95线趋势,以及优化后的任务是否被重新打开。
为了方便大家持续跟进任务,我们开发任务跟进系统:“dany”,如下图:
按事业部划分应用归属,既可以查看每个事业部的风险趋势,也可以选择自己应用的任务列表,选择慢接口类型,可以借助APM链路分析,更方便的定位问题,测试也可以根据任务状态推进开发优化。接下来我们具体来分析一个:
我们选一个循环调用的方法借助APM诊断工具,分析一下:
APM 诊断工具,会给出分析报告,绘出上下游拓扑图,绘出整个调用树,绘出每个方法的时间线,给不健康的方法加上标签 我们可以看出 这条链路中,存在3处双向依赖,循环调用15组,调用跨度75,总耗时1.4s左右。为了更好的用户体验,我们对界面做了很多支持动态配置的功能,比如筛选指定的风险点。
具体到1.4 这条链路中,我们可以看到总耗时147ms。
两处循环调用1.4.1 和 1.4.2 都是调用getByHostIdAndType总耗时21ms,优化建议可以合并成一次请求。
双向依赖有三处 1.4.4 -> 1.4.4.1 -> 1.4.4.2,我们可以看出层级关系,上游发起请求后,下游又回调了上游应用,这种交叉依赖,很容易形成依赖环,从而导致交叉故障,更早的到达故障临界点。优化建议,聚合接口,上游一次性把参数传给下游,剪掉双向依赖。
从时间线外面可以判断出,下游调用时间 一共消耗85ms, 去掉下游耗时,1.4 入口耗时62ms,也就是说,入口方法本身还存在优化空间。
其他的风险点都可以采用类似的方法进行分析,从而制定自己的优化方案。
作者介绍:
方勇:好大夫在线系统开发工程师,专注于微服务、中间件的稳定性和可用性建设,整体负责好大夫风险评估系统的设计和搭建;
翟康奇:好大夫在线系统开发工程师,专注于分布式任务调度,RabbitMQ高可用解决方案,负责全链路应用风险分析建模;
刘伟:好大夫在线系统开发工程师,主要负责统一推送平台,中间件运行时指标的监控挖掘及调优,主导RabbitMQ多集群管理平台建设。
领取专属 10元无门槛券
私享最新 技术干货