Micrometer + Prometheus 监控 Feign 调用实战

背景

可观测性是系统架构的基石,准确详细的度量是工程师的重要决策来源。对于微服务系统,除了传统意义上系统边界层的监控指标,服务内部调用的情况也需引起重视,这回就来分享下笔者在实现Feign调用监控的实战经验。

实现

先看看我们的监控对象:调用次数,附带标签有:服务名、uri、计数、状态码,这些信息或跟请求有关,或跟响应有关。不难想出埋点的模式无非是拦截器、过滤器链和装饰器之类,需要对请求过程前后插入环绕代码的模式。

笔者曾写过善用RequestInterceptor的文章:Feign Interceptor 拦截器实现全局请求参数 。RequestInterceptor是Feign暴露给使用者的拦截器,但只作用到请求之前,没法统计请求结果。除此之外还有改造Decoder、为@FeignClient类添加AOP环绕等手段,但最终笔者还是采用了装饰Client的方式来解决。

Client在feign中是请求的实际发送者,通过控制Client实现,就能拿到完整的请求过程。

    @Slf4j
    @AllArgsConstructor
    public static class MetricsFeignClient implements Client {

        private final Client delegate;
        private final MeterRegistry meterRegistry;

        @Override
        public Response execute(Request request, Request.Options options) throws IOException {

            Response response = null;
            try {
                response = delegate.execute(request, options);
            } finally {
                try {
                    meterRegistry.counter("feign_execution",
                            "target", request.requestTemplate().feignTarget().name(),
                            "uri", URLUtil.getPath(request.url()),
                            "status", Optional.ofNullable(response).map(Response::status).orElse(-1).toString()
                    ).increment();
                } catch (Exception e) {
                        log.error("error counting rpc invocation", e);
                }
            }
            return response;
        }
    }

直接实现Client,通过@Bean注入,当然也能实现目的,但万万不可这样实操。编写系统组件的重要原则,就是不能影响业务,如果接入了监控代码的业务服务中自行实现了Client用作己用(也可能是引入了带自定义Client的框架,如ribbon),要么Client之间相互覆盖导致失去重要功能,要么因Bean冲突而启动失败。所以笔者认为最好的方式就是对原有Client实例进行装饰,如上文代码的delegate字段。那么我们就需要监听到Client类创建完毕,然后用新类装饰,替换掉老的Client对象,笔者选择通过BeanPostProcessor实现。

@Slf4j
public class RpcMetricsExecutionProcessor implements BeanPostProcessor {

    private volatile boolean injected = false;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (!injected && bean instanceof Client) {
            injected = true;
            log.info("rpc execution metrics decorator injected , bean name : {} , original bean type : {} ", beanName, bean.getClass().getSimpleName());
            return new MetricsFeignClient((Client) bean, registry);
        }
        return bean;
    }

定义了BeanPostProcessor后,Bean创建完成就会回调上文中的方法,因为所有Bean创建都会回调,我们只需寻找我们需要的Client类即可。

另外笔者推荐用配置类的方式加载监控逻辑,避免业务工程没有引入Feign依赖而报错。

@Slf4j
@ConditionalOnClass(name = "feign.Client")
public class RpcMetricsExecutionConfiguration {

    @Bean
    public RpcMetricsExecutionProcessor rpcMetricsExecutionProcessor() {
        return new RpcMetricsExecutionProcessor();
    }

}

至此基本的Feign调用监控就完成了,因为掌控了请求过程,后续可以加上耗时、异常种类的统计等等。

posted @ 2022-01-09 14:51  d1zzyboy  阅读(1827)  评论(0编辑  收藏  举报