当前位置: 首页 > news >正文

Micrometer 系列【55】统一观测:ObservationConvention | 观测约定

文章目录

    • 1. 概述
    • 1.1 基础定义
    • 1.2 核心特征
      • 1.3 工作原理
        • (1)创建阶段
        • (2)启动观测
        • (3)执行业务逻辑
        • (4)观测结束
    • 2. 源码分析
      • 2.1 KeyValuesConvention:标记接口
      • 2.2 ObservationConvention:基础契约接口
      • 2.3 ChatModelObservationConvention:上层领域扩展接口
  • 3. 完整示例
    • 3.1 自定义上下文容器
    • 3.2 自定义观测 Convention 规范
    • 3.3 自定艺观测处理器
    • 3.4 业务服务类 & 测试入口
  • 4. 两种开发模式对比
    • 4.1 方式一:业务代码直接添加标签
    • 4.2 方式二:ObservationConvention 标准模式
    • 4.3 选型建议

1. 概述

Micrometer Observation体系提供统一可观测抽象,实现MetricsTracing数据同源采集。ObservationConvention是标签标准化核心契约,用来统一从业务上下文提取高低基数标签、定义观测名称。

早期开发方式直接在Observation.Context硬编码KeyValue,标签逻辑散落在业务代码;生产级框架(Spring AISpring Cloud)统一采用Convention模式,实现业务数据与标签抽取逻辑解耦。

本文基于原生API,讲解Convention定义、运行机制、源码细节以及完整可运行示例。

1.1 基础定义

一套标准化契约接口,负责从自定义Observation.Context中提取观测元信息。

核心方法:

  • getLowCardinalityKeyValues:提取低基数标签,同时用于监控指标Tag、链路追踪Span
  • getHighCardinalityKeyValues:提取高基数标签,仅允许放入链路 Span,禁止作为指标标签,防止时序爆炸;
  • getName():定义观测静态名称(指标名称);
  • getContextualName():动态上下文名称,用于链路界面展示;
  • supportsContext():判断当前规范实现是否匹配目标上下文类型。

1.2 核心特征

  1. 职责分离Context承载原始业务数据;Convention专注标签组装、命名规则;业务埋点不再关心标签规则。
  2. 强制区分高低基数:接口天然拆分两套标签方法,规范开发行为,规避高基数标签滥用风险。
  3. 分层扩展范式:顶层标记接口 → 泛型基础接口 → 领域专用子接口 → 默认通用实现 → 厂商/业务自定义实现,支持局部覆写。
  4. 无侵入扩展:新增业务维度、第三方适配时,仅新增Convention实现,无需改动埋点业务代码。
  5. 统一消费入口:所有ObservationHandler(指标处理器、追踪处理器)统一读取Convention产出的KeyValues,逻辑收敛。

1.3 工作原理

完整生命周期跟随Observation创建、启动、执行、停止流程。

(1)创建阶段

业务构建自定义Observation.Context,填充原始业务字段,不手动添加任何KeyValue;创建Observation实例并绑定对应ObservationConvention

此阶段仅完成对象绑定,不会触发标签提取。

(2)启动观测

调用observation.start()

  1. 框架内部检测绑定的Convention
  2. 主动调用getLowCardinalityKeyValues()getHighCardinalityKeyValues()提取标签;
  3. 调用getName()getContextualName()确定观测名称;
  4. ContextView+Convention产出标签传递给所有ObservationHandler#onStart

onStart阶段业务尚未执行,无法获取响应结果、异常信息。

(3)执行业务逻辑

observe()模板方法执行内部业务代码;业务可继续修改可变Context中的业务字段,但不建议动态追加标签,标签规则统一收敛在Convention

(4)观测结束

业务执行完成触发observation.stop()

  1. 再次通过Convention刷新高低基数标签;
  2. MetricsHandler使用低基数标签构建监控指标;TracingHandler将高低基数标签写入Span
  3. 观测生命周期结束,Convention实例可复用,Context上下文不可复用。

2. 源码分析

2.1 KeyValuesConvention:标记接口

标记型顶层接口,无任何方法。作用:语义归类,统一标识所有标签规范类,方便框架内部类型识别,区分普通业务类与观测规范实现。

publicinterfaceKeyValuesConvention{}

2.2 ObservationConvention:基础契约接口

  1. 使用泛型约束适配的上下文类型;
  2. 标签方法提供默认空实现,按需覆写;
  3. supportsContext用于匹配上下文类型;
  4. getName静态指标名,getContextualName链路展示动态名称。
publicinterfaceObservationConvention<TextendsObservation.Context>extendsKeyValuesConvention{ObservationConvention<Observation.Context>EMPTY=context->false;defaultKeyValuesgetLowCardinalityKeyValues(Tcontext){returnKeyValues.empty();}defaultKeyValuesgetHighCardinalityKeyValues(Tcontext){returnKeyValues.empty();}booleansupportsContext(Observation.Contextcontext);@NullabledefaultStringgetName(){returnnull;}@NullabledefaultStringgetContextualName(Tcontext){returnnull;}}

2.3 ChatModelObservationConvention:上层领域扩展接口

  1. 泛型锁定专属上下文ChatModelObservationContext
  2. 默认实现类型匹配方法,子类无需重复编写类型判断;
  3. 作为领域规范接口,统一约束该领域下全部规范实现。

参考Spring AI设计范式,基于基础接口做领域锁定:

publicinterfaceChatModelObservationConventionextendsObservationConvention<ChatModelObservationContext>{@OverridedefaultbooleansupportsContext(Observation.Contextcontext){returncontextinstanceofChatModelObservationContext;}}

3. 完整示例

场景:

  • 纯原生Java,无Spring
  • 根观测:创建订单;
  • 子观测:发起支付;
  • 采用标准Convention模式,Context不再手动添加KeyValue

3.1 自定义上下文容器

订单观测上下文容器,仅存放原始业务字段,标签提取逻辑交给Convention

publicclassOrderObservationContextextendsObservation.Context{privateStringorderNo;privateLonguserId;privateStringorderType;publicStringgetOrderNo(){returnorderNo;}publicvoidsetOrderNo(StringorderNo){this.orderNo=orderNo;}publicLonggetUserId(){returnuserId;}publicvoidsetUserId(LonguserId){this.userId=userId;}publicStringgetOrderType(){returnorderType;}publicvoidsetOrderType(StringorderType){this.orderType=orderType;}}

支付观测上下文容器:

importio.micrometer.observation.Observation;/** * */publicclassPaymentObservationContextextendsObservation.Context{privateStringorderNo;privateStringpayChannel;publicStringgetOrderNo(){returnorderNo;}publicvoidsetOrderNo(StringorderNo){this.orderNo=orderNo;}publicStringgetPayChannel(){returnpayChannel;}publicvoidsetPayChannel(StringpayChannel){this.payChannel=payChannel;}}

3.2 自定义观测 Convention 规范

订单观测规范接口:

publicinterfaceOrderObservationConventionextendsObservationConvention<OrderObservationContext>{@OverridedefaultbooleansupportsContext(Observation.Contextcontext){returncontextinstanceofOrderObservationContext;}}

订单观测规范默认实现,统一提取高低基数标签:

publicclassDefaultOrderObservationConventionimplementsOrderObservationConvention{publicstaticfinalStringOBSERVATION_NAME="order.create";@OverridepublicStringgetName(){returnOBSERVATION_NAME;}@OverridepublicStringgetContextualName(OrderObservationContextcontext){return"order "+context.getOrderType();}@OverridepublicKeyValuesgetLowCardinalityKeyValues(OrderObservationContextcontext){returnKeyValues.of(KeyValue.of("order.type",context.getOrderType()));}@OverridepublicKeyValuesgetHighCardinalityKeyValues(OrderObservationContextcontext){returnKeyValues.of(KeyValue.of("order.no",context.getOrderNo()),KeyValue.of("user.id",String.valueOf(context.getUserId())));}}

支付观测规范接口:

publicinterfacePaymentObservationConventionextendsObservationConvention<PaymentObservationContext>{@OverridedefaultbooleansupportsContext(Observation.Contextcontext){returncontextinstanceofPaymentObservationContext;}}

支付观测规范默认实现,统一提取高低基数标签:

publicclassDefaultPaymentObservationConventionimplementsPaymentObservationConvention{publicstaticfinalStringOBSERVATION_NAME="payment.create";@OverridepublicStringgetName(){returnOBSERVATION_NAME;}@OverridepublicStringgetContextualName(PaymentObservationContextcontext){return"payment "+context.getPayChannel();}@OverridepublicKeyValuesgetLowCardinalityKeyValues(PaymentObservationContextcontext){returnKeyValues.of(KeyValue.of("pay.channel",context.getPayChannel()));}@OverridepublicKeyValuesgetHighCardinalityKeyValues(PaymentObservationContextcontext){returnKeyValues.of(KeyValue.of("pay.order.no",context.getOrderNo()));}}

3.3 自定艺观测处理器

自定义观测处理器,读取Convention生成的标签进行打印

publicclassBizLogObservationHandlerimplementsObservationHandler<Observation.ContextView>{@OverridepublicvoidonStart(Observation.ContextViewcontextView){System.out.println("==== onStart ====");System.out.println("观测名称:"+contextView.getName());System.out.println("动态名称:"+contextView.getContextualName());System.out.println("低基数标签:"+contextView.getLowCardinalityKeyValues());System.out.println("高基数标签:"+contextView.getHighCardinalityKeyValues());System.out.println("================\n");}@OverridepublicvoidonStop(Observation.ContextViewcontextView){System.out.println("==== onStop ====");System.out.println("观测名称:"+contextView.getName());Throwableerror=contextView.getError();if(error!=null){System.out.println("异常信息:"+error.getMessage());}System.out.println("================\n");}@OverridepublicbooleansupportsContext(Observation.ContextViewcontext){returntrue;}}

3.4 业务服务类 & 测试入口

同上一篇,不再赘述!!!

4. 两种开发模式对比

4.1 方式一:业务代码直接添加标签

业务代码直接调用context.addLowCardinalityKeyValue()

优点

  1. 上手简单,代码直观,快速实现Demo、小型工具项目;
  2. 无需新增Convention接口与实现类,减少类数量;
  3. 支持运行时动态追加标签,灵活性高。

缺点

  1. 标签逻辑散落在各个业务埋点处;标签名称、规则修改,需要改动全部埋点代码;
  2. 无法统一管控高低基数规范,容易出现开发随意新增高基数指标标签,引发Prometheus时序爆炸;
  3. 业务代码与可观测规则强耦合,埋点代码臃肿;
  4. 缺少统一扩展入口;同类上下文想要差异化标签,只能修改业务代码;
  5. 不符合Spring AISpring Cloud等官方组件标准实现范式,无法对齐开源生态规范;
  6. ContextView只能读取已经存入的KeyValue;无法根据业务后置数据动态计算标签。

4.2 方式二:ObservationConvention 标准模式

优点

  1. 关注点分离Context只承载原始业务实体;标签抽取逻辑统一收敛到Convention;业务埋点干净纯粹;
  2. 统一管控标签规范,集中管理Tag名称、高低基数划分;标准化评审、统一修改;
  3. 天然支持分层扩展:通用默认实现 + 业务/厂商自定义子类,局部覆写标签逻辑,开闭原则;
  4. 框架自动在start()/stop()两个阶段重新执行标签提取;支持根据响应、异常等后置数据生成标签;
  5. 对齐MicrometerSpring AI官方最佳实践,便于后续对接各类中间件观测规范;
  6. 可统一设置观测静态名称、上下文展示名称,统一指标命名规范。

缺点

  1. 项目需要新增一组Convention接口、实现类,前期代码量更多;
  2. 学习成本更高,需要理解整套分层范式;
  3. 如果需要运行时动态标签,需要在Context中预先存入临时字段供Convention读取。

4.3 选型建议

  1. Demo、小型脚本、一次性工具:直接使用addLowCardinalityKeyValue,降低开发成本;
  2. 中大型业务项目、SDK、中间件组件、框架封装:强制使用ObservationConvention模式;
  3. 长期维护、多团队协作、需要统一监控规范的工程:优先Convention方案。
http://www.cnnetsun.cn/news/4005704.html

相关文章:

  • 揭秘张云网站建设背后的硬核逻辑与用户体验重塑之道:为何专业才是最好的流量密码
  • react-native-autolink常见问题解决:从链接失效到样式冲突的终极方案
  • 终极指南:用Godot的Metroidvania-System打造专业银河恶魔城游戏
  • 2024新手必看网站搭建全套指南与高速下载工具选择实录,手把手教你从0到1
  • 如何在嵌入式项目中快速实现自适应控制?fuzzy-pid 模糊PID控制器上手指南
  • Ethermint-archive架构深度剖析:Tendermint ABCI如何重塑区块链共识
  • asmcrypto.js:高性能WebCrypto API的Asm.js实现完全指南
  • 三步解锁微信聊天数据宝藏:WeChatMsg让数字记忆永久留存
  • 装饰公司网站建设方案:如何打造一款既能展示设计美学又能精准获客的线上门户
  • 优雅终止线程:Two-phase Termination模式在gh_mirrors/ja/javamtp中的实现与最佳实践
  • 太原网站建设地图怎么找?揭秘本土企业突围长尾策略实战指南
  • 5分钟快速上手:Hermes WebUI让你的AI助手拥有完美浏览器界面
  • 如何3步快速配置洛雪音乐音源:免费获取全网无损音乐的完整攻略
  • C盘扩容(C盘右键无法扩展卷解决)超详细步骤!!!
  • 唯美谷网站建设:如何打造不仅好看而且能带来真金白银转化的高质量企业官网
  • 如何快速创建专属AI语音:3分钟上手完整教程
  • Cursor AI IDE 安装与配置教程(Windows + Mac 双平台)
  • 5分钟拯救你的硬盘空间:用Unmanic打造智能媒体库优化系统
  • 如何用CRI工具高效管理Kubernetes容器运行时?架构解析与最佳实践
  • 哈尔滨网站建设工作室哪家靠谱?揭秘打造企业官网的底层逻辑与避坑指南
  • 3步实战:如何为你的爱车安装开源驾驶辅助系统openpilot
  • 快速掌握C#语言基础知识点(7.string类型)
  • 如何深度掌握CRI工具实现Kubernetes容器运行时高效管理?
  • 深度解析网站建设论文开题报告怎么写以及核心要素与常见误区
  • 专业视频编辑新选择:Hap QuickTime Codec硬件加速终极指南
  • 告别资源下载困境:这款免费神器让你轻松保存视频号、抖音、小红书内容
  • 北京网站建设上石榴汇:揭秘传统企业在数字时代的破局之道与价值重塑
  • 前端安全检查:XSS、CSRF、CSP 和依赖链分别处理
  • AgentRAG推理过程怎么可视化,Text2SQL让数据分析可追溯
  • 如何永久保存微信朋友圈?这款开源工具让你轻松备份所有珍贵回忆