Micrometer + Prometheus monitoring Feign call practice

background

Observability is the cornerstone of system architecture, and accurate and detailed measurement is an important decision source for engineers. For the microservice system, in addition to the monitoring indicators of the system boundary layer in the traditional sense, attention should also be paid to the internal invocation of services. This time, let's share the writer's practical experience in realizing Feign invocation monitoring.

realization

Let's take a look at our monitoring object: the number of calls, with Tags: service name, uri, count and status code. These information are related to the request or response. It is not difficult to think of the buried point mode, which is nothing more than interceptors, filter chains and decorators. It is necessary to insert surrounding code before and after the request process.

The author has written articles on making good use of RequestInterceptor: Feign Interceptor interceptor implements global request parameters . RequestInterceptor is an interceptor that Feign exposes to users, but it only works before the request and cannot count the request results. In addition, there are also means to transform the Decoder and add AOP surround for @ FeignClient class, but the author finally adopts the way of decorating the Client.

The Client is the actual sender of the request in feign. By controlling the Client implementation, you can get the complete request process.

    @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;
        }
    }

The Client can be implemented directly. Of course, the purpose can be achieved through @ Bean injection, but it must not be implemented in this way. The important principle of writing system components is not to affect the business. If the Client is used for its own use in the business service connected to the monitoring code (or the framework with custom Client, such as ribbon, may be introduced), either the clients cover each other, resulting in the loss of important functions, or the startup fails due to Bean conflict. Therefore, the author believes that the best way is to decorate the original Client instance, such as the delegate field in the above code. Then we need to listen to the creation of the Client class, and then decorate it with a new class to replace the old Client object. The author chooses to implement it through 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;
    }

After the BeanPostProcessor is defined, the methods above will be called back when the Bean is created, because all Bean creation will be called back. We just need to find the Client class we need.

In addition, the author recommends loading the monitoring logic in the way of configuration class to avoid error reporting without introducing Feign dependency in business engineering.

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

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

}

So far, the basic Feign call monitoring is completed, because the request process is controlled, and the statistics of time-consuming and exception types can be added later.

Keywords: Spring Cloud Prometheus feign

Added by iversonm on Sun, 09 Jan 2022 09:22:35 +0200