写点什么

微博平台的链路追踪及服务质量保障系统——Watchman 系统

2014 年 3 月 28 日

如其他大中型互联网应用一样,微博平台由众多的分布式组件构成,用户通过浏览器或移动客户端的每一个 HTTP 请求到达应用服务器后,会经过很多个业务系统或系统组件,并留下足迹(footprint)。但是这些分散的数据对于问题排查,或是流程优化都帮助有限。对于这样一种典型的跨进程 / 跨线程的场景,汇总收集并分析这类日志就显得尤为重要。另一方面,收集每一处足迹(footprint)的性能数据,并根据策略对各子系统做流控或降级也是确保微博平台高可用的重要因素。要能做到追踪每个请求的完整调用链路;收集调用链路上每个服务的性能数据;通过计算性能数据和比对性能指标(SLA)再回馈到控制流程(control flow)中,基于这些目标就诞生了微博的 Watchman 系统。在业界,Twitter 的 Zipkin 和淘宝的鹰眼系统也是类似的系统。

  1. 既然要追踪调用链路要收集数据,通常的做法就是通过代码埋点来记录日志。这样一方面要求在所有需要收集数据的地方侵入代码进行修改,并且(可能)引入新的依赖。比如淘宝的鹰眼系统在跨进程的远程调用两侧(stub 和 skeleton)通过埋点记录数据并传递请求上下文(request-context)。

watchman-runtime 组件利用字节码增强的方式在载入期织入增强逻辑(load-time weaving),为了跨进程 / 线程传递请求上下文,对于跨线程 watchman-enhance 组件通过 javaagent 的方式在应用启动并载入 class 时修改了 JDK 自身的几种线程池(ThreadPool 或几类 Executor)实现,在客户代码提交(execute 或 submit)时对传入的 runnable/callable 对象包上具有追踪能力的实现(proxy-pattern),并且从父线程上去继承或初始化请求上下文(request-context);如下图所示:

而对于跨进程的 RPC 场景,则动态增强传输层的客户端和服务端的逻辑。微博平台使用的 Motan RPC 框架有着类似 filter-chain 的流程,watchman-aspect 会插入自己的 filter 实现;实现的逻辑就是在 RPC 请求前获取请求方的请求上下文,序列化后装配近请求体中,服务方获取请求后,再从请求体中反序列化请求上下文,同时设置到线程上下文中(ThreadLocal)。如下图所示:

这类增强或修改都在运行期完成,对于开发人员完全透明,对于运维人员也很友好;

普通 Java 调用的处理方式(埋点 / 追踪)则是通过 AspectJ 的静态织入,相信广大读者对 AspectJ 都不陌生,它提供非常强大的 AOP 的能力,我们使用 AspectJ 来定义几类切面,分别针对 WeiboAuth、HTTP 接口、资源客户端的下行方法等。再利用 AspectJ 的语法定义各个切点,形如:

复制代码
@AfterReturning(value="execution(public $type $signature($param))",returning="$return"

之后在目标项目的 maven 构建过程中依靠 ajc 进行编译期的织入。之所以选择编译期织入方式是因为我们的业务场景是十分 performance-sensitive 的。每一个生效的切点也会在运行时向 configserver 注册 SLA 数据。(这个后面会讲到)
2. watchman-core 组件内置几类策略,分别用来控制收集数据的范围、收集数据的采样率、以及几种控制策略。

每个请求进入 Watchman 系统的边界后(在这里是微博平台 Auth 系统),通过这些策略来决定哪些足迹需要记录,比如 REST API、RPC 调用、存储 / 缓存的操作等;同时也通过策略决定本次请求是否需要采样,采样率可以动态修改;之后创建请求上下文并向后传递。在每一个控制点,又会根据控制策略来确定对本次请求是否丢弃,或是对整个方法以什么样的方式来 gracefully degrade 等;

我们先来看下请求上下问(request-context)的简单定义:

RequestContext 类关联一个阀门策略接口(ThrottleStrategy)和采样策略接口(SampleStrategy),每个 req-ctx 实例被构造时会传入两个策略的具体实现。在记录(trace())时,会根据当前采样策略来决定是否采集数据,并且策略可以动态更新,包括本地配置文件的方式,或者同步 configserver 的方式。从完全关闭、百万分之一到全量采集几个粒度可以选择。

阀门策略,顾名思义,就像一个阀门,用来控制流量的大小,或是开启 / 关闭。默认是全开的,因为认为业务 99.999% 是可用的,同时源源不断的性能数据会被收集,在 watchman-stream 进行汇总计算后会产生与注册在 configserver 中的 SLA 数据的比对结果。比如 A 服务的性能统计结果低于 SLA 水平,那么就会通知到阀门策略,并通过随机丢弃请求的方式来做流控,当性能结果严重低于 SLA 时就关闭,达到降级的效果。

互联网运维中对降级还有一个指标是,是否能优雅的降级,也就是不损害用户体验的情况下进行降级。这一点 watchman-aspect 会根据代码上的注解(annotation)来实现,@Degradable 可以标注在方法上,可以指定 returnType(required)和 returnValue(optional),降级时根据 returnType 来生成伪造的结果并返回,如果使用方有指定 returnValue 就直接用后者返回,如果默认提供的 returnType 不满足需要也可以进行扩展。
3. watchman-aspect 组件通过异步日志(async-logger)会在各个节点上输出日志文件,如何将这些分散的日志源源不断的收集汇总并计算?

通过 watchman-prism 组件(基于 Scribe),将日志推送到 watchman-stream 组件(基于 Storm),利用这两个业界成熟的系统以流式的方式处理数据,stream 中 bolt 会根据需求进行聚合、统计等计算(针对性能数据),规范化、排序(针对调用链数据),之后写入 HBase 中。这个过程通过 benchmark 反映出的结果来看,完全能达到准实时的要求(30s 左右)。 对于日志数据推送:首先应用要以一致的方式输出日志,理所当然就是通过 Log 框架的 logger 来输出,每个节点产生日志后需要再依赖 scribe 推送到日志中心,所以我们实现了自己的 AsyncScribeAppender,如下:

由于 Scribe 是基于 Thrift 进行通信,所以我们的 Appender 扩展于 Log4j 的 AppenderSkeleton,以普通 Logger 的 API 形式供上层使用(异步),同时再作为一个 ThriftClient 直接将数据写到节点上的 ScribeClient,之后再通过网络把日志推到远程。watchman-prism 在这里作为远程接受方,它是扩展于 Scribe 的一个 ThriftServer。 而 Storm 这一侧,众所周知,spout 组件作为数据的入口,分发到各个 bolt 进行流式计算,spout 是拉(pull)的模式,它从 watchman-prism 中不断取数据,经过简单的过滤后发射(emit)到其他 bolt,不同的 bolt 有着不同的计算任务,之后将不同纬度的计算数据写入 HBase 中。
4. 服务质量保障是 Watchman 系统的另一特点,在面向服务的架构(SOA)中,各个服务的提供方需要给出 SLA(service level agreement)数据,量化服务的各种指标(如吞吐、承载)和服务质量(如 99.99% <50ms)。这里的服务包括 http 形式的 REST API,RPC 服务,DB 或 Cache 的接口,以及网络 IO 层面等; 微博平台的各业务方的每一层服务都会在 Vintage(微博的类 Zookeeper 系统)中注册自己的 SLA 数据,运行时 watchman-stream 将不断计算得出的性能数据与通过 watchman-registry 获得的各服务的 SLA 数据进行比对。结果会反映到 Dashboard 上,这里与运维的告警系统等集成,可以及时将状况推送出去,除此之外也会更新 registry 中的指标,ConfigService 根据指标的变化判断是否通知各个注册的客户方,也就是 watchman-aspect,阀门策略就会根据通知调整阈值进行干预。流程如下:

比如某个服务由于瞬时访问高峰,造成底层资源压力变大从而服务响应时间变长,控制策略可以根据设定随机丢弃后续的请求,如果情况加剧就会自动降级该服务,保证核心服务路径。整个过程可以自动完成也可以人工通过 Dashboard 控制。

之后的迭代会进一步增强 watchman-stream 的计算 / 分析能力,争取在更多的维度上挖掘出有价值的数据;同时依靠 watchman-prism 来汇总更丰富的业务日志,力图在一个请求链路上展现更丰富的上下文相关数据。

魏佳(微博昵称: @快滚到锅里来),2009 年硕士毕业于北京交通大学计算机系,之前就职于 IBM 研究院,从事企业级应用虚拟化、 PaaS 平台等相关产品的开发,发表两篇专利。技术爱好者,Java/Python/Scala 语言,专注于分布式系统、消息中间件、轻量级容器等。2012 年加入新浪微博,目前任 职于微博平台架构组,微博平台技术委员会成员,主要负责微博后端基础组件和平台的设计开发。

2014 年 3 月 28 日 03:048996

评论

发布
暂无评论
发现更多内容

就餐卡系统架构设计文档

极客大学架构师训练营

LeetCode 655. Print Binary Tree

liu_liu

算法 LeetCode

ARTS Week5

丽子

别兜售你自己不会购买的东西

Neco.W

创业 销售管理 销售

计算机操作系统基础(一)---操作系统概览

书旅

php laravel 多线程 操作系统 进程

Week2:作业一

车小勺的男神

每日一题-翻转字符串里的单词

王传义

LeetCode

【极客大学】【架构师训练营】【第二周】总结:设计原则

NieXY

极客大学架构师训练营

Redis系列(三):缓存过期该如何剔除?RDB和AOF又是什么?

z小赵

Java redis 高并发 高并发系统设计

如何让你的大脑更健康

兆熊

李艺:建立订阅者意识,当好一名知识服务生,做好知识课程

李艺

知识付费

食堂就餐卡系统设计

John

极客大学架构师训练营

查找算法系列文(一)一文入门二叉树

淡蓝色

Java 数据结构 算法 二叉树

wee1作业总结

极客大学架构师训练营

微信支付的软件架构究竟有多牛逼...

程序员生活志

微信 架构

架构师训练营 - 第三周学习总结

清风徐徐

架构师训练营:第四周第一节,互联网架构系统架构的演化

zcj

极客大学架构师训练营

计算机操作系统基础(三)---进程管理之五状态模型

书旅

php laravel 多线程 操作系统 进程

MySQL InnoDB存储引擎 - 事务

Arthur

第四周 学习总结

冯凯

iOS & Android 去马赛克处理

liu_liu

ios android 去马赛克

来了!8M/S+速度,Pdown复活!

程序员生活志

神器工具:新一代多系统启动 U 盘装机解决方案

JackTian

工具软件 U盘启动盘 安装操作系统 ventoy ISO 镜像文件

设计模式之单例模式和组合模式

dapaul

极客大学架构师训练营

SpringBean的生命周期

编号94530

Java spring Spring Boot 生命周期

多个maven项目启动顺序

冬月末

maven

网络性能篇 (13讲)

王传义

ARTS Week5

时之虫

ARTS 打卡计划

计算机操作系统基础(二)---进程管理之进程实体

书旅

php laravel 多线程 操作系统 进程

计算机操作系统基础(四)---进程管理之进程同步

书旅

php laravel 多线程 操作系统 进程

设计原则与设计模式

dapaul

极客大学架构师训练营

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

微博平台的链路追踪及服务质量保障系统——Watchman系统-InfoQ