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);
@Around(value = "execution(* com.elab.marketing.auth.service..*(..))")
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那部分,只是因为要把消息对象往下传递。
本人也是刚刚研究这一块,也是给自己做个笔记,以上都是关键代码和思路,有不对的地方请指正,希望能帮助到更多的人。