背景
可觀測性是系統架構的基石,准確詳細的度量是工程師的重要決策來源。對於微服務系統,除了傳統意義上系統邊界層的監控指標,服務內部調用的情況也需引起重視,這回就來分享下筆者在實現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調用監控就完成了,因為掌控了請求過程,后續可以加上耗時、異常種類的統計等等。