> 文章列表 > Feign扩展 - 进程内调用

Feign扩展 - 进程内调用

Feign扩展 - 进程内调用

0. 目录

      • 1. 背景
      • 2. 实现
        • 2.1 思路
        • 2.2 关键代码
      • 3. 优化
      • 4. 参考

1. 背景

当下微服务有多火就不用描述了,导致的最直接结果就是一些主要应用场景下巨石架构能够表现更好的软件产品,追时髦选了微服务架构。于是一系列噩梦随之而来。

这里我们不再讨论这些噩梦有哪些,感兴趣的读者可以参见底部的引用链接。本文的主要意图以一种比较平滑的方式缓解问题,将feign调用实现由默认的"采用http请求实现进程间的交互",通过扩展提供一种额外的实现——进程内的交互。
在这里插入图片描述

如此操作可以收获下列好处:

  1. 保持之前的微服务版本不变。适用于高并发,高流量等微服务适用场景。
  2. 同时提供单体部署版本的制品包,适用于大部分的小型应用场景。
  3. 整个过程中,上层无感知,并且最大化保证了向后兼容性。

2. 实现

2.1 思路

得益于过往阅读过feign相关源码-1,feign相关源码-2,整个过程相对于比较顺利,以下给出一些关键思路。

  1. 借鉴自hystrix或sentinel(SentinelFeign)对于Feign.Builder的实现,在覆盖实现build()方法过程中, 提供自定义InvocationHandler实现。
  2. 在自定义的InvocationHandler实现中,基于SpringMVC的处理逻辑和feign对于feign定义接口的解析结果容器,进行相应的查询匹配之后,找出对应被调用的springmvc controller方法,用反射的方式对齐进行调用,之后则进入正常的原本的处理逻辑。

2.2 关键代码

  1. 首先是对于Feign.Builder的实现,以将自定义实现的InvocationHandler嵌入到feign的主体执行逻辑中。
public class FeignInvokeInProcess {public static Builder builder() {return new Builder();}public static final class Builder extends Feign.Builder implements ApplicationContextAware {private Contract contract = new Contract.Default();private ApplicationContext applicationContext;private FeignContext feignContext;@Overridepublic Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {throw new UnsupportedOperationException();}@Overridepublic Builder contract(Contract contract) {this.contract = contract;return this;}@Overridepublic Feign build() {// Refer To HystrixFeign.javasuper.invocationHandlerFactory(new InvocationHandlerFactory() {@SneakyThrows@Overridepublic InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {// 注解取值以避免循环依赖的问题FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);Class fallback = feignClient.fallback();Class fallbackFactory = feignClient.fallbackFactory();String contextId = feignClient.contextId();if (!StringUtils.hasText(contextId)) {contextId = feignClient.name();}RequestMappingHandlerMapping rmhm = feignContext.getInstance(contextId,RequestMappingHandlerMapping.class);ServletContext sc = feignContext.getInstance(contextId, ServletContext.class);DispatcherServlet dispatcherServlet = feignContext.getInstance(contextId, DispatcherServlet.class);RequestMappingHandlerAdapter handlerAdapter = feignContext.getInstance(contextId,RequestMappingHandlerAdapter.class);Object fallbackInstance;FallbackFactory fallbackFactoryInstance;// 判断fallback类型if (void.class != fallback) {fallbackInstance = getFromContext(contextId, "fallback", fallback, target.type());InvocationHandler SentinelInvocationHandler = null; //new SentinelInvocationHandler(//								target, dispatch, new FallbackFactory.Default(fallbackInstance));return new InvokeInProcessInwardInvocationHandler(target, dispatch, rmhm, sc,dispatcherServlet, handlerAdapter, SentinelInvocationHandler);}if (void.class != fallbackFactory) {fallbackFactoryInstance = (FallbackFactory) getFromContext(contextId, "fallbackFactory",fallbackFactory, FallbackFactory.class);InvocationHandler SentinelInvocationHandler = null;// new SentinelInvocationHandler(//target, dispatch, fallbackFactoryInstance);return new InvokeInProcessInwardInvocationHandler(target, dispatch, rmhm, sc,dispatcherServlet, handlerAdapter, SentinelInvocationHandler);}// 默认fallbackFactoryFallbackFactory FallbackFactory = new FallbackFactory(target);InvocationHandler SentinelInvocationHandler = null; //new SentinelInvocationHandler(//target, dispatch, FallbackFactory);return new InvokeInProcessInwardInvocationHandler(target, dispatch, rmhm, sc,dispatcherServlet, handlerAdapter, SentinelInvocationHandler);}private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {Object fallbackInstance = feignContext.getInstance(name, fallbackType);if (fallbackInstance == null) {throw new IllegalStateException(String.format("No %s instance of type %s found for feign client %s", type, fallbackType, name));}if (!targetType.isAssignableFrom(fallbackType)) {throw new IllegalStateException(String.format("Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",type, fallbackType, targetType, name));}return fallbackInstance;}});super.contract(contract);return super.build();}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;feignContext = this.applicationContext.getBean(FeignContext.class);}}}
  1. 然后就是客户端发起feign调用时,我们要把该请求调度在进程内完成。
public class InvokeInProcessInwardInvocationHandler implements InvocationHandler {private final Target<?> target;private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;private final RequestMappingHandlerMapping requestMappingHandlerMapping;private final ServletContext sc;private final DispatcherServlet ds;private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;@Nullableprivate InvocationHandler fallback;public InvokeInProcessInwardInvocationHandler(Target<?> target,Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,RequestMappingHandlerMapping requestMappingHandlerMapping, ServletContext sc, DispatcherServlet ds,RequestMappingHandlerAdapter requestMappingHandlerAdapter, InvocationHandler fallback) {this.target = checkNotNull(target, "target");this.dispatch = checkNotNull(dispatch, "dispatch");this.requestMappingHandlerMapping = checkNotNull(requestMappingHandlerMapping, "requestMappingHandlerMapping");this.sc = checkNotNull(sc, "sc");this.ds = checkNotNull(ds, "ds");this.requestMappingHandlerAdapter = checkNotNull(requestMappingHandlerAdapter, "requestMappingHandlerAdapter");this.fallback = checkNotNull(fallback, "fallback");		}@Overridepublic Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {if ("equals".equals(method.getName())) {try {Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;return equals(otherHandler);} catch (IllegalArgumentException e) {return false;}} else if ("hashCode".equals(method.getName())) {return hashCode();} else if ("toString".equals(method.getName())) {return toString();}InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);Object result = null;if (isNeedRemoteCall(methodHandler)) {result = methodHandler.invoke(args);} else {// 进程内调用Object buildTemplateFromArgs = ReflectUtil.getFieldValue(methodHandler, "buildTemplateFromArgs");Method createMethod = ReflectUtil.getMethod(buildTemplateFromArgs.getClass(), "create",new Object[] {}.getClass());RequestTemplate template = ReflectUtil.invoke(buildTemplateFromArgs, createMethod, args);Request request = ReflectUtil.invoke(methodHandler, "targetRequest", template);final HttpServletRequest httpServletRequest = transfer(request);HandlerExecutionChain handler = requestMappingHandlerMapping.getHandler(httpServletRequest);if (Objects.isNull(handler)) {return null;}final HandlerMethod handlerMethod = Convert.convert(HandlerMethod.class, handler.getHandler());if (Objects.isNull(handlerMethod)) {// 托底return fallback.invoke(proxy, method, args);}Console.log(handlerMethod);//Method method2 = handlerMethod.getMethod();Object bean = handlerMethod.getBean();Console.log(bean);// 以下两种选其一即可, 效果一样的result = invokeByReflect(httpServletRequest, handlerMethod);result = invokeByCompleteRequestFlow(httpServletRequest);Console.log(result);//Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();}return result;}private boolean isNeedRemoteCall(InvocationHandlerFactory.MethodHandler methodHandler) {return false;}private HttpServletRequest transfer(Request request) {HttpMethod httpMethodOfFeign = request.httpMethod();org.springframework.http.HttpMethod resolve = org.springframework.http.HttpMethod.resolve(httpMethodOfFeign.name());Map<String, Collection<String>> headers = request.headers();final HttpHeaders headersContianer = new HttpHeaders();headers.forEach((k, vals) -> {headersContianer.addAll(k, java.util.Arrays.asList(vals.toArray(new String[0])));});return MockMvcRequestBuilders.request(resolve, URLUtil.toURI(request.url()))//.headers(headersContianer)//.content(request.body()) //.buildRequest(sc);}private Object invokeByReflect(final HttpServletRequest httpServletRequest, final HandlerMethod handlerMethod)throws Exception {MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();// 最终调用: ServletInvocableHandlerMethod.javaModelAndView handle = requestMappingHandlerAdapter.handle(httpServletRequest, mockHttpServletResponse,handlerMethod);Assert.isNull(handle);return mockHttpServletResponse.getContentAsString();}private Object invokeByCompleteRequestFlow(final HttpServletRequest httpServletRequest)throws ServletException, IOException {MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();// 这里以触发SpringMVC的一系列扩展, 例如ResponseBodyAdvice<T>等ds.service(httpServletRequest, mockHttpServletResponse);return mockHttpServletResponse.getContentAsString();}....}
  1. 更多的细节,请参见底部给出的gitee仓库地址。

3. 优化

可以看到整个实现还是相当粗糙,应用于生产版本还得需要磨合和大量的残酷测试。除此之外,当下能够考虑到的优化点:

  1. 针对特例场景的兼容。例如模块是逐步合并的,所以有些请求调用还是得走默认的http请求模式。(这一点上面的代码中已经有了体现)
  2. 关于springmvc的处理流程中是存在一系列扩展的。在本文实现之下,这些应该如何兼容?上面的invokeByCompleteRequestFlow经过简单的测试是可以满足的,但更为详尽的兼容性还得全面的测试。
  3. 以上这类方式很明显要求所有的服务调用都采用feign的方式,但对于历史代码如果缺乏检查机制,这一点是不能保证的。所以这一块是需要检查和重构的。
  4. 模块源码级别合并也可能存在一些问题,比如引入依赖冲突,包命名重复等。
  5. 优化性能。

4. 参考

  1. Gitee - feignInvokeInProcess
  2. feign源码解析 - 初始化
  3. feign源码解析 - 运行时
  4. 微服务了个寂寞
  5. 微服务的开始
  6. 为何如此强调CICD