知识卡片
用ThreadLocal承载监控上下文:让埋点方式贴合Web容器和RPC框架的天然线程模型
内容
CAT客户端在收集端数据时选择用ThreadLocal(线程局部变量)来承载监控上下文,这个选择不是随意的,而是精确匹配了监控场景下应用本身天然的执行结构。无论是为用户提供服务的Web容器(Tomcat、Jetty)还是后端RPC服务端(Dubbo、Pigeon),都是基于线程池实现的,业务方处理业务逻辑时的典型模式是——在同一个线程内部依次调用后端服务、数据库、缓存等资源,把这些结果收集回来做业务逻辑封装,最后把结果返回给用户,整个处理链路自然而然就落在了同一个线程的生命周期之内。这意味着”一次请求对应的所有监控数据”和”处理这次请求的那个线程”存在天然的一一对应关系——ThreadLocal恰好提供了”每个线程独立持有一份变量副本、互不冲突”的机制,业务执行逻辑时把这次请求对应的监控信息(以一个监控树结构)存入当前线程的ThreadLocal上下文,等业务线程执行结束,再把整棵监控树取出、存入异步内存队列,由专门的消费线程异步发送到服务端,全程不需要额外的锁或者跨线程传递监控数据的机制。这个案例给出了一条埋点/监控设计的重要原则:设计一套跨越应用广泛使用的底层能力(比如监控、日志、链路追踪)时,最省心也最不容易出错的方式,往往不是发明一套全新的数据传递机制,而是先仔细观察目标应用本身天然的执行模型(这里是”一次请求的处理过程通常落在单个线程内完成”),找到一个能和这个天然结构精确对应的语言/框架特性(这里是ThreadLocal),让新增的埋点能力顺势嵌入到已有结构里,而不是强行在已有结构之上叠加一套独立运作、容易产生额外协调开销的新机制。
参考来源
- 位置:《高可用架构(第1卷)》第5章《运维保障》"5.2 深度剖析开源分布式监控CAT"节,"5.2.3 客户端设计"(源文件:_epub-src/OEBPS/Text/Chapter5_2_4.xhtml)
- 结论依据:原文说明"CAT客户端在收集端数据方面使用ThreadLocal……在监控场景下,为用户提供服务的都是Web容器……后端的RPC服务端……也都是基于线程池来实现的……所以将所有的监控请求作为一个监控的上下文存入线程变量就非常合适",直接支撑本卡片结论。
- 原始内容:CAT客户端在收集端数据方面使用ThreadLocal(线程局部变量)……业务方在处理业务逻辑时基本都是在一个线程内部调用后端服务、数据库、缓存等……所以将所有的监控请求作为一个监控的上下文存入线程变量就非常合适。