一个成熟的微服务集群,内部调用必然依赖一个好的RPC框架,好比:基于http协议的feign,基于私有tcp协议的dubbo。本文内容介绍feign。java
若是不使用rpc框架,那么调用服务须要走http的话,配置请求head、body,而后才能发起请求。得到响应体后,还需解析等操做,十分繁琐。git
Feign是一个http请求调用的轻量级框架,能够以Java接口注解的方式调用Http请求。Feign经过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,封装了http调用流程。github
feign底层基于http协议,适应绝大部份内外部API调用的应用场景,而且SpringCloud对feign已经有了比较好的封装。使用上能够依赖于SpringCloud封装过的feign:spring
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
Feign在默认状况下使用的是JDK原生的URLConnection发送HTTP请求,没有链接池,可是对每一个地址会保持一个长链接,即利用HTTP的
persistence connection。建议替换为Apache HttpClient,做为底层的http client包,从而获取链接池、超时时间等与性能息息相关的控制能力:服务器
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
在配置文件中启用ApacheHttpClient:app
feign.httpclient.enabled=true
FeignClient参数:负载均衡
public @interface FeignClient { @AliasFor("name") String value() default ""; /** @deprecated */ @Deprecated String serviceId() default ""; String contextId() default ""; // 指定FeignClient的名称 @AliasFor("value") String name() default ""; String qualifier() default ""; // 全路径地址或hostname,http或https可选 String url() default ""; // 当发生http 404错误时,若是该字段位true,会调用decoder进行解码,不然抛出FeignException boolean decode404() default false; // Feign配置类,能够自定义Feign的LogLevel Class<?>[] configuration() default {}; // 容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑 Class<?> fallback() default void.class; // 工厂类,用于生成fallback类实例,经过这个属性咱们能够实现每一个接口通用的容错逻辑,减小重复的代码 Class<?> fallbackFactory() default void.class; // 定义当前FeignClient的统一前缀,相似于controller类上的requestMapping String path() default ""; boolean primary() default true; }
使用Feign涉及两个注解:@EnableFeignClients,用来开启Feign;@FeignClient,标记要用Feign来拦截的请求接口。框架
启动配置上检查是否有@EnableFeignClients注解,若是有该注解,则开启包扫描,扫描被@FeignClient注解的接口。扫描出该注解后,
经过beanDefinition注入到IOC容器中,方便后续被调用使用。tcp
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<?>[] defaultConfiguration() default {}; Class<?>[] clients() default {}; }
@EnableFeignClients 是关于注解扫描的配置,使用了@Import(FeignClientsRegistrar.class)。在spring context处理过程当中,这个Import会在解析Configuration的时候当作提供了其余的bean definition的扩展,Spring经过调用其registerBeanDefinitions方法来获取其提供的bean definition。ide
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); } }
FeignClientsRegistrar里重写了spring里ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法。也就是在启动时,处理了EnableFeignClients注解后,registry里面会多出一些关于Feign的BeanDefinition。
ReflectiveFeign内部使用了jdk的动态代理为目标接口生成了一个动态代理类,这里会生成一个InvocationHandler统一的方法拦截器,同时为接口的每一个方法生成一个SynchronousMethodHandler拦截器,并解析方法上的元数据,生成一个http请求模板RequestTemplate。
public class ReflectiveFeign extends Feign { @Override public <T> T newInstance(Target<T> target) { Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); for (Method method : target.type().getMethods()) { if (method.getDeclaringClass() == Object.class) { continue; } else if (Util.isDefault(method)) { DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; } }
Feign内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求:
final class SynchronousMethodHandler implements MethodHandler { @Override public Object invoke(Object[] argv) throws Throwable { // 根据输入参数,构造Http请求 RequestTemplate template = buildTemplateFromArgs.create(argv); // 克隆出一份重试器 Retryer retryer = this.retryer.clone(); // 尝试最大次数,若是中间有结果,直接返回 while (true) { try { return executeAndDecode(template); } catch (RetryableException e) { try { retryer.continueOrPropagate(e); } catch (RetryableException th) { Throwable cause = th.getCause(); if (propagationPolicy == UNWRAP && cause != null) { throw cause; } else { throw th; } } if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } } }
Feign真正发送HTTP请求是委托给feign.Client来作的:
public interface Client { Response execute(Request request, Options options) throws IOException; class Default implements Client { @Override public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection, request); } } }
默认底层经过JDK的java.net.HttpURLConnection实现了feign.Client接口类。在每次发送请求的时候,都会建立新的HttpURLConnection连接,这样的话默认状况下Feign的性能不好,通常扩展该接口,好比使用Apache的HttpClient或者OkHttp3等基于链接池的高性能Http客户端。
注意:SynchronousMethodHandler并非直接完成远程URL的请求,而是经过负载均衡机制,定位到合适的远程server服务器,而后再完成真正的远程URL请求。即:SynchronousMethodHandler实例的client成员,其实际不是feign.Client.Default类型,而是LoadBalancerFeignClient客户端负载均衡类型。
Feign框架比较小巧,在处理请求转换和消息解析的过程当中,基本上没什么时间消耗。真正影响性能的,是处理Http请求的环节。能够从这个方面着手分析系统的性能提高点。