背景
可观测性是系统架构的基石,准确详细的度量是工程师的重要决策来源。对于微服务系统,除了传统意义上系统边界层的监控指标,服务内部调用的情况也需引起重视,这回就来分享下笔者在实现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调用监控就完成了,因为掌控了请求过程,后续可以加上耗时、异常种类的统计等等。