Feign
Feign 是一個聲明web服務客戶端,這便得編寫web服務客戶端更容易,使用Feign 創建一個接口並對它進行註解,它具有可插拔的註解支持包括Feign註解與JAX-RS註解,Feign還支持可插拔的編碼器與解碼器,Spring Cloud 增加了對 Spring MVC的註解,Spring Web 默認使用了HttpMessageConverters, Spring Cloud 集成 Ribbon 和 Eureka 提供的負載均衡的HTTP客戶端 Feign.
CAT
CAT是一個實時和接近全量的監控系統,它側重於對Java應用的監控,除了與點評RPC組件融合的很好之外,他將會能與Spring、MyBatis、Dubbo 等框架以及Log4j 等結合,不久將會支持PHP、C++、Go等多語言應用,基本接入了美團點評上海側所有核心應用。目前在中間件(MVC、RPC、數據庫、緩存等)框架中得到廣泛應用,爲美團點評各業務線提供系統的性能指標、健康狀況、監控告警等。
CAT消息鏈路的構建思路
A -> B -> C
CAT的鏈路樹的話,其實應該是將消息的編號串聯起來,然後可以在管理頁面上將這些消息編號統一展現。
編號模型:
- ROOTID : 根的編號
- PARENTID : 上級編號
- CHILD : 子級編號
消息樹就是上下級編號關聯
因爲Feign底層的話也是基於HTTP去調用的,所以參數之間傳遞的時候需要將消息編號進行傳遞,並且關聯起來。
也就是說 A 客戶端要生成編號模型,然後通過Feign調用B的時候帶過去。
B客戶端接收到這個編號模型的時候,在本地生成消息樹的時候,將編號模型植入進去完成綁定關聯
具體實現細節
A -> B
- Feign發起一個HTTP調用前需要通過CAT構建一個消息樹,這一部分通過AOP來做,AOP拿到消息模型之後,綁定到當前Request中
@Aspect
@EnableAspectJAutoProxy
@Configuration
public class CatMsgIdAspectBean {
private Logger logger = LoggerFactory.getLogger(CatMsgIdAspectBean.class);
public Object around(ProceedingJoinPoint pjp) throws Throwable {
createMessageTree();
Object proceed = pjp.proceed();
return proceed;
}
/**
* 統一設置消息編號的messageId
*/
private void createMessageTree() {
CatMsgContext context = new CatMsgContext();
Cat.logRemoteCallClient(context);
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
requestAttributes.setAttribute(Cat.Context.PARENT, context.getProperty(Cat.Context.PARENT), 0);
requestAttributes.setAttribute(Cat.Context.ROOT, context.getProperty(Cat.Context.ROOT), 0);
requestAttributes.setAttribute(Cat.Context.CHILD, context.getProperty(Cat.Context.CHILD), 0);
requestAttributes.setAttribute(CatMsgConstants.APPLICATION_KEY, Cat.getManager().getDomain(), 0);
}
}
- 生成好了消息樹之後,Feign在攔截請求中將消息模型綁定到請求的Head中
@Component
public class FeignInterceptor implements RequestInterceptor {
private Logger logger = LoggerFactory.getLogger(FeignInterceptor.class);
@Override
public void apply(RequestTemplate requestTemplate) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
String rootId = requestAttributes.getAttribute(Cat.Context.ROOT, 0).toString();
String childId = requestAttributes.getAttribute(Cat.Context.CHILD, 0).toString();
String parentId = requestAttributes.getAttribute(Cat.Context.PARENT, 0).toString();
requestTemplate.header(Cat.Context.ROOT, rootId);
requestTemplate.header(Cat.Context.CHILD, childId);
requestTemplate.header(Cat.Context.PARENT, parentId);
requestTemplate.header(CatMsgConstants.APPLICATION_KEY, Cat.getManager().getDomain());
logger.info(" 開始Feign遠程調用 : " + requestTemplate.method() + " 消息模型 : rootId = " + rootId + " parentId = " + parentId + " childId = " + childId);
}
}
- 請求發送之後,B客戶端怎麼去接受?
public class HttpCatCrossFliter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(HttpCatCrossFliter.class);
private static final String DEFAULT_APPLICATION_NAME = "default";
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String requestURI = request.getRequestURI();
Transaction t = Cat.newTransaction(CatMsgConstants.CROSS_SERVER, requestURI);
try {
Cat.Context context = new CatMsgContext();
context.addProperty(Cat.Context.ROOT, request.getHeader(Cat.Context.ROOT));
context.addProperty(Cat.Context.PARENT, request.getHeader(Cat.Context.PARENT));
context.addProperty(Cat.Context.CHILD, request.getHeader(Cat.Context.CHILD));
Cat.logRemoteCallServer(context);
this.createProviderCross(request, t);
filterChain.doFilter(req, resp);
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
logger.error("------ Get cat msgtree error : ", e);
Event event = Cat.newEvent("HTTP_REST_CAT_ERROR", requestURI);
event.setStatus(e);
completeEvent(event);
t.addChild(event);
t.setStatus(e.getClass().getSimpleName());
} finally {
t.complete();
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void destroy() {
}
/**
* 串聯provider端消息樹
*
* @param request
* @param t
*/
private void createProviderCross(HttpServletRequest request, Transaction t) {
Event crossAppEvent = Cat.newEvent(CatMsgConstants.PROVIDER_CALL_APP, request.getHeader(CatMsgConstants.APPLICATION_KEY)); //clientName
Event crossServerEvent = Cat.newEvent(CatMsgConstants.PROVIDER_CALL_SERVER, request.getRemoteAddr()); //clientIp
crossAppEvent.setStatus(Event.SUCCESS);
crossServerEvent.setStatus(Event.SUCCESS);
completeEvent(crossAppEvent);
completeEvent(crossServerEvent);
t.addChild(crossAppEvent);
t.addChild(crossServerEvent);
}
private void completeEvent(Event event) {
if (event != NullMessage.EVENT) {
AbstractMessage message = (AbstractMessage) event;
message.setCompleted(true);
}
}
}
- 將攔截器註冊到容器中
@Configuration
public class CatFilterConfigure {
@Bean
public FilterRegistrationBean catFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
HttpCatCrossFliter filter = new HttpCatCrossFliter();
registration.setFilter(filter);
registration.addUrlPatterns("/*");
registration.setName("cat-filter");
registration.setOrder(1);
return registration;
}
}
接收好了之後,基本流程已經完畢了。
Cross報表
其他代碼:
public class CatMsgConstants {
public static final String CROSS_CONSUMER = "PigeonCall";
/**
* Cross報表中的數據標識
*/
public static final String CROSS_SERVER = "PigeonService";
public static final String PROVIDER_APPLICATION_NAME = "serverApplicationName";
public static final String CONSUMER_CALL_SERVER = "PigeonCall.server";
public static final String CONSUMER_CALL_APP = "PigeonCall.app";
public static final String CONSUMER_CALL_PORT = "PigeonCall.port";
public static final String PROVIDER_CALL_SERVER = "PigeonService.client";
/**
* 客戶端調用標識
*/
public static final String PROVIDER_CALL_APP = "PigeonService.app";
public static final String FORK_MESSAGE_ID = "m_forkedMessageId";
public static final String FORK_ROOT_MESSAGE_ID = "m_rootMessageId";
public static final String FORK_PARENT_MESSAGE_ID = "m_parentMessageId";
public static final String INTERFACE_NAME = "interfaceName";
/**
* 客戶端調用的服務名稱 -> 最好是Cat.getManager().getDomain()獲取
*/
public static final String APPLICATION_KEY = "application.name";
}
public class CatMsgContext implements Cat.Context {
private Map<String, String> properties = new HashMap<>();
@Override
public void addProperty(String key, String value) {
properties.put(key, value);
}
@Override
public String getProperty(String key) {
return properties.get(key);
}
}
注意事項
- 如果Feign集成了Hystrix,會出現上下文參數找不到的情況
原因是Hystrix會開啓一個子線程去執行Feign請求,但是子線程卻獲取不到主線程的上下文,這時候需要把主線程上下文帶到子線程中去!
解決方法:
@Component
public class FeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategy.class);
private HystrixConcurrencyStrategy delegate;
public FeignHystrixConcurrencyStrategy() {
try {
this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
if (this.delegate instanceof FeignHystrixConcurrencyStrategy) {
// Welcome to singleton hell...
return;
}
HystrixCommandExecutionHook commandExecutionHook =
HystrixPlugins.getInstance().getCommandExecutionHook();
HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy =
HystrixPlugins.getInstance().getPropertiesStrategy();
this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
HystrixPlugins.reset();
HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
} catch (Exception e) {
log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
}
}
private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) {
if (log.isDebugEnabled()) {
log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy ["
+ this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["
+ metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
}
}
/**
* 將當前線程參數傳遞到要調用的方法中去
*
* @param callable
* @param <T>
* @return
*/
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
return new WrappedCallable<>(callable, requestAttributes);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize,
HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
unit, workQueue);
}
// @Override
// public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
// HystrixThreadPoolProperties threadPoolProperties) {
// return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
// }
@Override
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
return this.delegate.getBlockingQueue(maxQueueSize);
}
@Override
public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
return this.delegate.getRequestVariable(rv);
}
static class WrappedCallable<T> implements Callable<T> {
private final Callable<T> target;
private final RequestAttributes requestAttributes;
public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
this.target = target;
this.requestAttributes = requestAttributes;
}
@Override
public T call() throws Exception {
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
return target.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
}
其實如果不用Hystrix的話,可以省去AOP那部分,只是因爲要把消息對象往下傳遞。
本人也是剛剛研究這一塊,也是給自己做個筆記,以上都是關鍵代碼和思路,有不對的地方請指正,希望能幫助到更多的人。