
文章目录1. 整体介绍1.1 基础概念1.2 计数器类型1.2.1 CumulativeCounter 累加计数器1.2.2 StepCounter 步进区间计数器1.2.3 DropwizardCounter 第三方适配器1.2.4 CompositeCounter 复合多路分发计数器1.2.5 NoopCounter 空操作无实现计数器1.3 核心规范1.4 使用方式3. 手动计数器3.1 创建方式3.1.1 静态工厂创建3.1.2 流式构建器3.2 埋点使用主动打点3.3 查看计数器数值3.3.1 方式 1直接调用 counter.count()3.3.2 方式 2通过 registry 遍历所有 Meter调用 measure() 采集指标3.3.3 方式 3重新获取 Counter 再取值4. Counted AOP 自动计数器4.1 核心概念4.2 使用示例4.3 MeterTag 从方法入参动态提取标签4.4 优势和劣势5. 外部函数计数器5.1 核心定义5.2 使用示例5.3 优势和劣势1. 整体介绍1.1 基础概念Counter计数器是Micrometer八大计量器中最常用、最基础的埋点类型用于统计单调递增事件总量例如请求次数、异常次数、消息生产/消费次数。统一顶层接口Counter核心两个方法increment(double amount)计数累加埋点打点入口count()读取当前计数值注意事项所有计量器统一顶层接口为Meter全部由MeterRegistry注册表创建、托管Counter是Meter的子接口分为普通手动计数器、CountedAOP自动计数器、FunctionCounter外部函数计数器三类计数器仅输出一类指标【累计计数值】Counter接口仅支持正向累加increment()传入的增量必须为正数不支持负数递减。1.2 计数器类型横向对比总表类名计数模型核心用途典型绑定 RegistryCumulativeCounter全量累计标准累加指标时序库后台算ratePrometheusMeterRegistryStepCounter步进窗口增量直接输出周期增量云端SaaS监控DatadogMeterRegistryDropwizardCounter第三方桥接兼容Codahale/Dropwizard旧metrics体系DropwizardMeterRegistryCompositeCounter多路分发聚合一套埋点同时上报多个监控后端CompositeMeterRegistryNoopCounter空操作降级监控关闭时兜底避免空指针、减少分支判断所有Registry禁用时自动切换1.2.1 CumulativeCounter 累加计数器核心字段DoubleAdder高并发线程安全累加器比AtomicDouble吞吐更高。privatefinalDoubleAddervalue;处理逻辑increment()不断往DoubleAdder叠加数值count()返回程序启动以来累计总和不会重置。特点累计型cumulative数值只涨不跌Prometheus、InfluxDB等绝大多数时序库默认使用适合统计总请求数、总错误数、总下单量等永久累加指标。优缺点✅ 简单、并发性能好❌ 无法直接看区间增量需后端计算速率rate 当前 - 上一次1.2.2 StepCounter 步进区间计数器该计数器向监控系统上报单个采集步长内的增量count()返回的是上一完整周期的事件增量而非服务全生命周期总和。核心字段StepDouble内置双缓冲区当前窗口缓冲区 上一完整窗口缓冲区依赖Clock和采集步长stepMillis如60s。privatefinalStepDoublevalue;处理逻辑increment()只写入当前活跃窗口count()返回上一个完整采集周期的增量值_closingRollover()周期到切换缓冲区旧窗口数据冻结供采集新窗口清零。特点数值会周期性重置天然输出区间增量直接可算QPS内存存双窗口少量额外开销。适配按周期推送、不做差值计算的监控系统Datadog、New Relic、Wavefront等SaaS监控系统需要直接拿到“这一分钟产生了多少次”不用后台再算差值。1.2.3 DropwizardCounter 第三方适配器核心字段是桥接适配层把MicrometerCounterAPI转发给DropwizardCodahale原生Meter。privatefinalcom.codahale.metrics.Meterimpl;逻辑映射increment(amount)→impl.mark((long) amount)Dropwizard Meter仅支持long类型传入double会强制强转long小数会丢失精度count()→impl.getCount()返回Dropwizard底层累计总数。定位兼容老项目原有Dropwizard Metrics迁移到Micrometer时无缝兼容封装第三方metrics库屏蔽底层API差异统一Micrometer埋点入口。1.2.4 CompositeCounter 复合多路分发计数器继承AbstractCompositeMeter内部持有多个子 Counter对应多个Registry例如同时输出PrometheusDatadog。核心方法解读increment(double amount)一次埋点打点遍历所有注册的子计数器同步累加实现多监控系统同时上报。count()只取第一个子计数器的值复合场景下读数值仅做兜底展示无业务计算意义。registerNewMeter() 当新增Registry时自动用Meter.Id重建对应类型的子Counter注册到新注册表newNoopMeter()无注册器时返回空实现NoopCounter。使用场景多监控共存业务同时对接PrometheusLokiDatadog埋点只写一次多路输出。1.2.5 NoopCounter 空操作无实现计数器处理逻辑Overridepublicvoidincrement(doubleamount){}// 空方法不做任何累加Overridepublicdoublecount(){return0;}触发时机MeterRegistry被关闭/禁用Composite无任何子注册器全局metrics开关关闭作用空对象模式避免埋点代码频繁判空if(counter ! null)埋点代码无感知降级关闭监控不影响业务主流程零性能损耗。1.3 核心规范1. 重复埋点冗余禁止若业务逻辑需要同时记录耗时使用Timer即可Timer内置事件总次数统计无需额外新增Counter同理DistributionSummary自带count统计不要重复创建计数器。2.观测指标优先看速率而非原始绝对值原始累计总值会受实例运行时长干扰服务重启后数值直接归零图表断崖下跌。生产告警、大盘分析务必基于单位时间速率QPS/TPS。示例消息队列监控用速率区分消息入队/消费快慢不受实例重启、运行天数影响。1.4 使用方式分类埋点方式数值维护方是否调用 increment 埋点指标含义依赖框架普通手动 Counter开发者主动埋点Counter 内部累加必须手动调用累计事件次数 / 区间增量无纯 MicrometerCounted AOP CounterAOP 自动埋点Counter 内部累加框架自动调用无需手写方法调用总次数、异常次数Spring AOPFunctionCounter无主动埋点外部业务对象维护调用无效不做计数当前瞬时存量快照无3. 手动计数器开发者主动在业务代码获取Counter对象手动调用increment()/increment(n)完成计数埋点是最基础、灵活度最高的埋点方式。3.1 创建方式3.1.1 静态工厂创建通过MeterRegistry.counter()静态工厂创建全局缓存同名同标签实例重复创建不会新建对象。创建注册表MeterRegistryregistrynewSimpleMeterRegistry();支持标签维度拆分// 创建指标名api.request携带标签envtestCountercounterregistry.counter(api.request,Tags.of(env,test));3.1.2 流式构建器低频元数据指标说明、基础单位通过Counter.builder()链式配置最终调用register()注册到注册表CountercounterCounter.builder(api.request).baseUnit(requests)// 基础计量单位.description(HTTP接口总请求次数)// 指标描述同步推送到监控平台.tags(region,test,server,gateway)// 多维度标签.register(registry);3.2 埋点使用主动打点业务分支、异常、事件发生处手动累加支持整数 / 浮点数增量// 请求 1counter.increment();// 请求 10counter.increment(10);3.3 查看计数器数值3.3.1 方式 1直接调用 counter.count()Counter接口自带count()方法直接读出当前计数值// 直接读取计数器当前值doubletotalcounter.count();System.out.println(接口总请求数total);// 输出 11.03.3.2 方式 2通过 registry 遍历所有 Meter调用 measure() 采集指标measure()是Meter统一对外暴露指标数据的标准方法监控组件底层都是靠这个读取数据返回Measurement对象// 遍历注册表内所有计量器for(Metermeter:registry.getMeters()){System.out.println(指标名称meter.getId().getName());System.out.println(标签meter.getId().getTags());// 读取指标观测值for(Measurementmeasurement:meter.measure()){System.out.println(统计类型measurement.getStatistic());System.out.println(指标数值measurement.getValue());}System.out.println(------------------------);}输出示例指标名称api.request 标签[tag(regiontest), tag(servergateway)] 统计类型COUNT 指标数值11.03.3.3 方式 3重新获取 Counter 再取值代码其他位置没有持有Counter对象时可通过registry.counter(名称, 标签) 重新拿到实例再调用count()// 根据指标名、标签匹配获取已注册的CounterCounterfindCounterregistry.counter(api.request,Tags.of(region,test,server,gateway));System.out.println(查询到的请求总数findCounter.count());4. Counted AOP 自动计数器4.1 核心概念基于Spring AOP动态代理实现的全自动埋点仅需在类 / 方法上加Counted注解无需手写counter.increment()支持同步、异步Async方法框架自动拦截方法执行完成计数逻辑。两种织入方式编译/加载期AspectJ织入Spring AOP动态代理日常Spring项目主流方案。AOP内部执行逻辑示例调用方法前通过注解元数据构建Meter.Id从registry获取 / 创建Counter方法正常返回执行counter.increment()标签resultsuccess方法抛出异常可配置单独累加异常计数携带异常类名标签方法执行结束切面释放上下文对业务无感知。底层实际计量对象仍是标准CumulativeCounter/StepCounter等只是埋点动作由AOP环绕通知自动执行。4.2 使用示例SpringBoot自动配置CountedAspect若自动配置失效需手动注册切面BeanConfigurationpublicclassCountedConfiguration{// 注册切面全局生效BeanpublicCountedAspectcountedAspect(MeterRegistryregistry){returnnewCountedAspect(registry);}}使用示例// 基础用法方法调用成功自动1Counted(valueuser.login.count,extraTags{device,mobile})publicvoidlogin(Stringusername){// 纯业务代码无任何埋点逻辑}// 进阶区分成功/失败计数、记录异常类型、自定义描述Counted(valueapi.order.create,description订单创建接口调用统计,recordFailuresAsExceptionstrue,// 异常单独打异常标签extraTags{source,h5},failTagerror.type// 异常类型存入tag)publicOrdercreateOrder(OrderDTOdto){returnorderService.save(dto);}异步方法同样生效AsyncCountedpublicCompletableFuture?asyncPay(){returnCompletableFuture.supplyAsync(()-{// 支付逻辑});}4.3 MeterTag 从方法入参动态提取标签常规Counted仅能写死静态标签若需要把接口入参、业务参数作为指标维度搭配MeterTag注解使用需要手动配置标签解析处理器。配置标签解析器// 自定义参数值解析器ValueResolvervalueResolverparameter-custom_tag_value[parameter];// Spring EL表达式解析器ValueExpressionResolverelResolvernewSpelValueExpressionResolver();// 给切面绑定处理器countedAspect.setMeterTagAnnotationHandler(newCountedMeterTagAnnotationHandler(clazz-valueResolver,clazz-elResolver));支持四种标签提取模式interfaceOrderApi{// 模式1自定义ValueResolver解析参数CountedvoidresolverTagDemo(MeterTag(keypay_type,resolverValueResolver.class)StringpayParam);// 模式2SpEL表达式生成标签值CountedvoidelTagDemo(MeterTag(keydesc,expressionpay_ type)Stringtype);// 模式3直接调用参数toString()作为标签值CountedvoidsimpleTagDemo(MeterTag(order_id)LongorderId);// 模式4单个参数挂载多个标签CountedvoidmultiTagDemo(MeterTag(keychannel,expressionch: data.channel)MeterTag(keysource,expressiondata.source)DataHolderdata);}调用后指标校验示例// 直接toStringapi.simpleTagDemo(15L);assertThat(registry.get(method.counted).tag(order_id,15).counter().count()).isEqualTo(1);// 自定义解析器api.resolverTagDemo(alipay);assertThat(registry.get(method.counted).tag(pay_type,custom_tag_value[alipay]).counter().count()).isEqualTo(1);限制说明CountedAspect不支持元注解嵌套Counted必须直接标注在目标方法上。4.4 优势和劣势✅优势零埋点代码无业务侵入接口统一规范全局统一管控所有接口 / 服务方法调用量避免漏埋天然区分成功、失败调用快速统计接口错误率统一添加固定标签服务名、环境、模块。❌ 劣势依赖Spring AOP仅能作用于Spring Bean的public方法无法细粒度区分方法内部多分支逻辑只能统计整个方法调用次数不能自定义增量数值每次调用固定1内部调用this.xxx ()AOP拦截失效无法计数。5. 外部函数计数器5.1 核心定义和前两类完全相反没有任何increment主动埋点操作Counter自身不存储、不累加数值指标采集时自动回调传入的外部函数读取外部已存在的数值作为当前计数值。不属于Cumulative/Step实现体系是独立实现的特殊Counter:典型业务场景连接池状态数据库活跃连接数、Redis客户端连接数在线存量当前在线用户、活跃会话数量队列堆积MQ待消费消息、线程池排队任务数内存缓存本地缓存key数量、限流当前排队请求数。约束开发者必须保证读取的函数返回值只增不减Micrometer不会校验单调性。调用functionCounter.increment()不会报错但内部是空实现不会改变任何数值无实际作用所有数值维护交给外部业务对象。5.2 使用示例通过registry.more().counter()函数式构造器注册绑定外部对象与取值函数CacheguavaCache...;// 开启内置统计的Guava缓存// 自动抓取 cache.stats().evictionCount() 单调递增数值registry.more().counter(cache.evict.total,Tags.of(cache,user),guavaCache,c-c.stats().evictionCount());流式构建器// 自定义状态对象提供单调递增count()方法CacheStatestatenewCacheState();FunctionCountercounterFunctionCounter.builder(cache.hit.total,state,state-state.count()).baseUnit(hits).description(缓存命中总次数).tags(region,prod).register(registry);数据更新逻辑业务代码自主修改外部变量FunctionCounter完全不参与数值修改没有increment()埋点逻辑监控组件定时拉取指标时自动执行取值函数拿到实时最新值上报。5.3 优势和劣势✅ 优势复用业务已有状态变量无需额外维护一套计数适合瞬时存量指标在线人数、连接数、队列堆积长度无埋点侵入不需要在事件处写计数代码。❌ 劣势不支持增量打点仅能读取外部现成数值取值函数执行时机在指标采集阶段高频采集会带来额外性能消耗无法统计事件发生次数只适合存量快照类指标函数执行异常会直接导致指标无数据。