Micrometer 系列【49】统一观测:Micrometer Observation 模块
文章目录前言1. 基础概念1.1 生命周期与回调事件1.2 标签基数区分1.3 解决痛点1.4 Spring 生态集成现状2. 核心组件2.1 ObservationRegistry 观测工程 配置中心2.2 ObservationConvention 约定规范2.3 Observation.Context 上下文2.4 ObservationHandler 自定义处理器2.5 ObservationPredicate 全局观测开关2.6 ObservationFilter 统一修改上下文3. 注解支持3.1 Observed3.2 ObservationKeyValue前言Micrometer Observation是Micrometer官方推出的统一观测API核心设计思想一次埋点多端受益。仅编写一次业务埋点代码通过注册不同处理器即可同时生成监控指标、分布式追踪链路、业务事件日志无需重复编写多套埋点逻辑。Observation作为Micrometer全新顶层抽象统一所有可观测能力编程模型底层自动适配、联动生成三类数据Metrics指标timer/counter等Trace链路Span结构化日志关联注入traceId、spanId、自定义观测上下文Maven坐标dependencygroupIdio.micrometer/groupIdartifactIdmicrometer-observation/artifactId/dependency1. 基础概念1.1 生命周期与回调事件所有观测逻辑依托ObservationRegistry注册ObservationHandler处理器监听观测完整生命周期事件生命周期方法触发时机用途start()观测开始初始化链路、长耗时监控、记录起始时间openScope()创建线程上下文Scope绑定ThreadLocal传递观测上下文error(Exception)业务抛出异常标记错误、记录异常堆栈event(Event)自定义业务事件记录自定义打点事件缓存命中、重试等Scope.close()关闭上下文清理ThreadLocal资源stop()观测结束统计耗时、生成指标、结束Trace跨度状态流转Observation生命周期Created→Started→StoppedScope上下文生命周期Scope Started→Scope Finished1.2 标签基数区分观测支持两种标签底层指标存储策略完全不同低基数标签取值范围有限接口模板/user/{id}、环境、业务类型可作为指标维度持久化不会造成指标爆炸。高基数标签取值无限真实请求URL、用户ID、订单号禁止作为指标维度仅用于链路追踪、日志检索。1.3 解决痛点在Micrometer早期阶段指标、链路追踪是两套独立体系日志无统一关联入口。三套埋点代码割裂、语义不统一、无法一次埋点输出三类可观测数据代码冗余、维护成本高。Observation统一观测模型彻底统一了Micrometer所有可观测能力的顶层抽象实现一次埋点同时生成指标、链路、日志三类可观测数据。1.4 Spring 生态集成现状官方主推Observation但并不是「所有埋点全都基于Observation」存在两套埋点并存。Spring官方原生组件自动创建ObservationSpring WebMVC/WebFlux服务端请求RestClient、WebClient、RestTemplate客户端HTTPSpring DataJDBC/JPA、MongoDBSpringKafka、SpringRabbitMQScheduled定时任务Spring CloudGatewaySpringAIFeign新版适配JVM、容器、线程池、连接池等基础指标依旧直接注册Meter不走Observation。典型JVM内存、GC、线程、类加载指标Tomcat/Jetty线程池、连接指标Lettuce连接池原生指标、HikariCP连接池指标各类内置Gauge内存、队列长度2. 核心组件术语表组件作用ObservationRegistry观测注册中心统一管理Handler、过滤器、断言、全局约定ObservationHandler生命周期处理器监听start/stop/error/event生成指标、Trace、日志Observation.Context可变上下文容器Map结构跨Handler传递业务数据ObservationFilter观测停止前修改上下文统一追加全局标签机房、实例IDObservationPredicate观测开关断言满足条件则忽略本次观测生成空操作NoOpObservationConvention观测元数据规范统一指标名、高低基数标签完整执行流程通过ObservationRegistry创建Observation绑定可变Context执行ObservationPredicate判断是否跳过观测可传入ObservationConvention统一配置观测名称与标签执行start()触发所有ObservationHandler#onStart手动/自动打开Scope绑定线程上下文业务执行中可抛出异常、自定义事件执行stop()前先执行所有ObservationFilter修改上下文触发ObservationHandler#onStop完成指标统计、链路上报。2.1 ObservationRegistry 观测工程 配置中心ObservationRegistry是Micrometer Observation观测体系的全局入口、工厂、配置中心、上下文管理器。整个框架所有观测能力均由该类统一调度与管控是Micrometer可观测体系的基石组件。它的核心定位可总结为双重角色观测工厂根据全局配置动态创建真实观测实例或空操作NOOP观测实现监控动态启停。全局配置中心统一注册、管理所有观测扩展组件驱动观测全生命周期逻辑。2.2 ObservationConvention 约定规范解耦埋点业务代码与观测元数据名称、标签统一全局观测命名规则业务埋点只关注业务逻辑不硬编码指标名、标签通过Convention统一配置名称、高低基数标签全局统一修改无需改动埋点代码优先级自定义传入Convention 全局GlobalConvention 默认Convention。2.3 Observation.Context 上下文类似透传容器存储业务数据、异常、自定义标签所有Handler共享同一份上下文替代零散ThreadLocal传递数据。Observation.ContextcontextnewObservation.Context().put(String.class,自定义业务数据).addLowCardinalityKeyValue(region,shanghai).addHighCardinalityKeyValue(traceId,xxx);2.4 ObservationHandler 自定义处理器扩展观测能力的核心扩展点一套埋点可挂载多个Handler指标、链路、自定义日志。自定义打印Handler示例staticclassSimpleHandlerimplementsObservationHandlerObservation.Context{OverridepublicvoidonStart(Observation.Contextcontext){System.out.println(观测开始context.get(String.class));}OverridepublicvoidonError(Observation.Contextcontext){System.out.println(观测异常context.getError().getMessage());}OverridepublicvoidonEvent(Observation.Eventevent,Observation.Contextcontext){System.out.println(自定义事件event.getName());}OverridepublicvoidonStop(Observation.Contextcontext){System.out.println(观测结束);}// 控制当前Handler是否处理该上下文OverridepublicbooleansupportsContext(Observation.ContexthandlerContext){returntrue;}}注册到RegistryObservationRegistryregistryObservationRegistry.create();registry.observationConfig().observationHandler(newSimpleHandler());团队里不同人创建Observation时命名不一致// 张三Observation.createNotStarted(order.placeOrder,registry)// 李四Observation.createNotStarted(order-place-order,registry)// 王五Observation.createNotStarted(order/placeOrder,registry)// 混用分隔符ObservationConvention强制统一命名和tagpublicinterfaceObservationConventionTextendsObservation.Context{// ★ 默认 namedefaultStringgetName(){return;}// ★ 默认 contextualNamedefaultStringgetContextualName(){return;}// ★ 默认 lowCardinality key-valuedefaultKeyValuesgetLowCardinalityKeyValues(Tcontext){returnKeyValues.empty();}// ★ 默认 highCardinality key-valuedefaultKeyValuesgetHighCardinalityKeyValues(Tcontext){returnKeyValues.empty();}}2.5 ObservationPredicate 全局观测开关动态过滤不需要采集的观测返回false生成NoOp空观测无性能损耗registry.observationConfig().observationPredicate((name,ctx)-{// 过滤指定名称观测if(health.check.equals(name))returnfalse;// 过滤指定用户上下文if(ctxinstanceofMyContexttest_user.equals(((MyContext)ctx).getUsername())){returnfalse;}returntrue;});2.6 ObservationFilter 统一修改上下文观测停止前统一追加、删除、修改标签全局统一元数据registry.observationConfig().observationFilter(context-{// 全局追加低基数机房标签context.addLowCardinalityKeyValue(cloud.zone,hz);// 移除高基数大流量标签context.removeHighCardinalityKeyValue(raw_url);returncontext;});3. 注解支持3.1 Observed若项目中已开启面向切面编程AOP例如引入org.aspectj:aspectjweaver依赖即可通过Observed注解快速生成观测链路。该注解可直接标注在方法上仅观测当前方法或类上观测类内所有方法。以下示例展示了在方法上添加Observed注解的业务服务类staticclassObservedService{Observed(nametest.call,contextualNametest#call,lowCardinalityKeyValues{abc,123,test,42})voidcall(){System.out.println(call);}}3.2 ObservationKeyValue除此之外可通过ObservationKeyValue注解基于方法入参动态添加观测键值对标签。以下示例展示了带方法参数、配置ObservationKeyValue注解的服务类staticclassObservedServiceWithParameter{Observed(nametest.call)ObservationKeyValue(keykey4,cardinalityCardinality.LOW)Stringcall(ObservationKeyValues({ObservationKeyValue(keykey0,cardinalityCardinality.HIGH),ObservationKeyValue(keykey1),ObservationKeyValue(keykey2,expressionkey2: toUpperCase),ObservationKeyValue(keykey3,resolverValueResolver.class)})Stringparam){returnparam;}}核心注解参数释义lowCardinalityKeyValues低基数键值对适用于取值固定、枚举类、数量有限的业务标签用于指标聚合、分组统计cardinality Cardinality.HIGH高基数键值对适用于取值不固定、唯一、动态变化的参数如请求ID、自定义入参仅用于链路明细排查不适合聚合expression支持SpEL表达式可对入参进行格式化、运算、转换后生成标签值resolver自定义值解析器通过实现ValueResolver接口自定义标签值的生成逻辑