深入分析Spring Cloud 负载均衡器架构选型
我们这次项目主要从RestTemplate 和 Feign 进行选型分析。
一、Spring Cloud Feign分析
Feign是另外一种客户端负载均衡实现。
我在该模块写了Feign Client的示例代码。
【1】spring-cloud-web-demo-api为服务的sdk模块
【2】spring-cloud-web-demo-service为提供接口服务的模块
【3】spring-cloud-web-demo-client为模拟调用服务的模块
首先在spring-cloud-web-demo-api模块,定义Feign API。spring-cloud-web-demo为spring-cloud-web-demo-service暴露的服务名。
@FeignClient(value = "spring-cloud-web-demo") public interface UserFeign { @GetMapping(value = "/user/getUserById", produces = "application/json;charset=utf-8") Object getUserById(@RequestParam(value = "id", required = false) Long id); //省略 }
然后通过ClientAutoConfiguration自动装配。(client直接引入api包就可以使用,不需要再EnableFeignClients)
@Configuration @EnableFeignClients("net.teaho.demo.spring.cloud.web.api") public class ClientAutoConfiguration { } org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ net.teaho.demo.spring.cloud.web.api.config.ClientAutoConfiguration
在service模块如以往Spring MVC般实现api模块接口即可。
@RestController public class UserController implements UserFeign { private static final Logger logger = LoggerFactory.getLogger(UserController.class); @Override public Object getUserById(Long id) { return "{\"id\":1, \"name\": \"test\"}"; } //省略 }
在Client模块,注入bean后直接调用。
@Component @Slf4j public class TestService { @Autowired private RestTemplate restTemplate; public Object getOneUser(){ return userController.getUserById(1L); } }
二、RestTemplate分析
写了具有客户端负载均衡能力的RestTemplate的请求代码。
类似这样定义:
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
RestTemplate究竟是如何利用注册中心实现客户端负载均衡的呢?
实现方式: 就是将上面所说的LoadBalancerInterceptor负载均衡拦截器加到标注了@LoadBalanced的RestTemplate实例中。 LoadBalancerInterceptor拦截器会在执行过程中获取并设置适合的目标请求实例,重新构造请求URI。
// 将配置中标注了@LoadBalanced的RestTemplate注入到这里 @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); //将注册的RestTemplateCustomizer(RestTemplate自定义器)集合处理上面的restTemplates集合 @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); }
三、技术选型
最终选择使用OpenFeign,下面说说原因。
和RestTemplate比起来,OpenFeign显得更适合Spring Boot微服务。
Open Feign相当于(HTTP)RPC,相比起RestTemplate,它直接显式将API声明以JAVA接口形式标识出来。 并且因为底层用的动态代理,它还可以(无感知地)替换底层实现。比如,github上就有替换底层逻辑的repo – Open Feign+Dubbo的RPC实现。
通过sdk包的形式,方便了调用,不需要像RestTemplate一样,客户端自行拼接上一串请求参数。在代码编写上也清晰。
要使用就必须知道OpenFeign是怎么实现的呢?
四、OpenFeign 初始化分析
流程图如下:
看看前面例子里我们引入的OpenFeign的东西
【1】@EnableFeignClients(“net.teaho.demo.spring.cloud.web.api”)
【2】@FeignClient(value = “spring-cloud-web-demo”) 还有自动装配引入的
【3】FeignRibbonClientAutoConfiguration
【4】FeignClientsConfiguration
我们就从这两个注解开始分析源码。
【1】首先看@FeignClient注解。
//给接口标注成一个REST调用方 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FeignClient { //服务名,可以带协议前缀,也可以用${property.name}关联一个配置值。 @AliasFor("name") String value() default ""; @Deprecated String serviceId() default ""; //bean name String contextId() default ""; @AliasFor("value") String name() default ""; /** * Sets the <code>@Qualifier</code> value for the feign client. */ String qualifier() default ""; //直接指定一个地址,比如http://localhost:12345,一般用于调试 String url() default ""; boolean decode404() default false; /** * A custom <code>@Configuration</code> for the feign client. Can contain override * <code>@Bean</code> definition for the pieces that make up the client, for instance * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}. * * @see FeignClientsConfiguration for the defaults */ //可用于覆盖FeignClient默认设置 Class<?>[] configuration() default {}; //回滚类,像我的例子中定义的回滚类必须实现UserFeign接口,看https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/single/spring-cloud.html#spring-cloud-feign-hystrix-fallback Class<?> fallback() default void.class; //如果需要对异常做诊断可用此属性,https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/single/spring-cloud.html#spring-cloud-feign-hystrix-fallback Class<?> fallbackFactory() default void.class; //路径前缀 String path() default ""; //标记bean是否为primary boolean primary() default true; }
【2】接下来重点关注@EnableFeignClients注解是如何扫描FeignClient接口的。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { //省略 }
嗯,发现没有,就是FeignClientsRegistrar做处理的。来分析下重点方法registerFeignClients和registerFeignClient
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //classPath扫描器 ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); //ClassPathScanningCandidateComponentProvider扫描的basePackage集合 Set<String> basePackages; Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); //扫描器用于扫描标注了@FeignClient类的拦截器 AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); //clients属性为空,以@EnableFeignClients的value、basePackage等为根包扫描 if (clients == null || clients.length == 0) { scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); } //@EnableFeignClients的clients属性不为空,解析clients的类和根包 else { final Set<String> clientClasses = new HashSet<>(); basePackages = new HashSet<>(); for (Class<?> clazz : clients) { basePackages.add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { @Override protected boolean match(ClassMetadata metadata) { String cleaned = metadata.getClassName().replaceAll("\\$", "."); return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter( new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter))); } //1.根据basePackage找到目标@FeignClient接口 //2.检查是否为接口 //3.将找到的接口注册为FeignClientFactoryBean for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); registerClientConfiguration(registry, name, attributes.get("configuration")); registerFeignClient(registry, annotationMetadata, attributes); } } } } private String getClientName(Map<String, Object> client) { if (client == null) { return null; } String value = (String) client.get("contextId"); if (!StringUtils.hasText(value)) { value = (String) client.get("value"); } if (!StringUtils.hasText(value)) { value = (String) client.get("name"); } if (!StringUtils.hasText(value)) { value = (String) client.get("serviceId"); } if (StringUtils.hasText(value)) { return value; } throw new IllegalStateException("Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName()); } private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); definition.addPropertyValue("url", getUrl(attributes)); definition.addPropertyValue("path", getPath(attributes)); String name = getName(attributes); definition.addPropertyValue("name", name); String contextId = getContextId(attributes); definition.addPropertyValue("contextId", contextId); definition.addPropertyValue("type", className); definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("fallback", attributes.get("fallback")); definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); String alias = contextId + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); } }
可以看到最后注册beanDefinition时,我们看到注册了FeignClientFactoryBean这一FactoryBean。 我们看看工厂bean FeignClientFactoryBean是如何构造对象的。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { //省略 @Override public Object getObject() throws Exception { return getTarget(); } <T> T getTarget() { //1.获取FeignContext,在FeignAutoConfiguration声明 FeignContext context = applicationContext.getBean(FeignContext.class); //2.构造Feign builder Feign.Builder builder = feign(context); //3.如果没有设置url参数 if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } //4.设置path url += cleanPath(); //5.获取Client(用于执行最终HTTP/HTTPS请求,比如LoadBalancerFeignClient), //构造反射实例 return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } //存在url参数,构造非loadBalance的请求实例(target) if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); } //在FeignContext中获取一些在FeignClientsConfiguration中声明,Feign需要用到的组件 protected Feign.Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(this.type); // @formatter:off Feign.Builder builder = get(context, Feign.Builder.class) // required values .logger(logger) .encoder(get(context, Encoder.class)) .decoder(get(context, Decoder.class)) .contract(get(context, Contract.class)); // @formatter:on configureFeign(context, builder); return builder; } protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) { //获取Client Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); //从Context获取Targeter,Targeter用于生成最终target实例(对应我的例子是被调用的通过反射生成的UserFeign实例) Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, target); } throw new IllegalStateException( "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?"); } //省略 }
在非调试情况下(即我们没设置url参数), 我们来看看targeter.target(this, builder, context, target)做了什么。
Targeter接口是构造被请求的代理bean的类。有两个实现类HystrixTargeter、DefaultTargeter。
HystrixTargeter会比默认的多设置一些回滚措施,用到Feign的Contract属性, 我会先从DefaultTargeter说起。
DefaultTargeter会通过Feign.Builder#target(Target target)生成实例。我们来看看代码。
public abstract class Feign { //省略 public static class Builder { private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>(); private Logger.Level logLevel = Logger.Level.NONE; private Contract contract = new Contract.Default(); private Client client = new Client.Default(null, null); private Retryer retryer = new Retryer.Default(); private Logger logger = new NoOpLogger(); private Encoder encoder = new Encoder.Default(); private Decoder decoder = new Decoder.Default(); private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default(); private ErrorDecoder errorDecoder = new ErrorDecoder.Default(); private Options options = new Options(); private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default(); private boolean decode404; private boolean closeAfterDecode = true; private ExceptionPropagationPolicy propagationPolicy = NONE; //省略 public <T> T target(Class<T> apiType, String url) { return target(new HardCodedTarget<T>(apiType, url)); } public <T> T target(Target<T> target) { return build().newInstance(target); } //默认实现就是创建一个ReflectiveFeign实例 public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); } } //省略 }
在解读ReflectiveFeign前介绍几个概念:
1、InvocationHandlerFactory 是控制反射方法分发的接口,create方法返回InvocationHandler。
2、InvocationHandlerFactory.MethodHandler 最终将对代理类方法调用转换成HTTP请求的地方,请看实现类SynchronousMethodHandler
3、InvocationHandlerFactory.Default 默认实现,作为构造参数传入ReflectiveFeign,create方法创建的是new ReflectiveFeign.FeignInvocationHandler(target, dispatch)。
4、ReflectiveFeign.ParseHandlersByName 作为构造参数传入ReflectiveFeign,核心方法apply(Target key)先将标注了@FeignClient的接口的方法解析出待处理的元数据List, 然后创建出方法名和方法处理器的map映射Map<String, MethodHandler>String是方法名,方法处理器通过SynchronousMethodHandler.Factory#create创建。
5、FeignInvocationHandler 为处理一般方法的处理器
6、DefaultMethodHandler 为处理接口默认方法的处理器
有了以上介绍,接下来简单分析ReflectiveFeign的newInstance方法。
public class ReflectiveFeign extends Feign { private final ParseHandlersByName targetToHandlersByName; private final InvocationHandlerFactory factory; private final QueryMapEncoder queryMapEncoder; ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory, QueryMapEncoder queryMapEncoder) { this.targetToHandlersByName = targetToHandlersByName; this.factory = factory; this.queryMapEncoder = queryMapEncoder; } .. /** * creates an api binding to the {@code target}. As this invokes reflection, care should be taken * to cache the result. */ @SuppressWarnings("unchecked") @Override public <T> T newInstance(Target<T> target) { //创建方法名和方法处理器的map映射 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; //判断是否为接口的默认方法,DefaultMethodHandler的处理逻辑是直接调用会原接口的default方法 } else if (Util.isDefault(method)) { DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { //方法处理map methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } InvocationHandler handler = factory.create(target, methodToHandler); //jdk动态代理创建对象 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); //将默认方法处理器也绑定到代理对象上 for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; } static class FeignInvocationHandler implements InvocationHandler { private final Target target; private final Map<Method, MethodHandler> dispatch; //省略 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //自定义的equals、hashCode和toString的处理 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 return dispatch.get(method).invoke(args); } //省略 }
初始化完成。
五、OpenFeign 执行分析
上图是OpenFeign构造的代理对象被调用时的时序图。
1、代理对象被执行
2、找到对应SynchronousMethodHandler进行方法调用。
3、构造RequestTemplate
4、LoadBalancerFeignClient执行负载请求
5、FeignLoadBalancer通过ILoadBalancer选择合适Server,通过Server重组URI,通过RibbonRequest持有的Client执行实际HTTP请求包装成Response。
6、SynchronousMethodHandler通过Decoder将请求响应用Decoder解码成最终结果。
下面介绍执行过程中涉及到源码中的部分组件。
1、RequestTemplate 是一个HTTP请求内容的抽象。
2、RequestTemplate.Factory 将方法参数解析成RequestTemplate。
3、Retryer 我在上面的时序图没有标注出来,实际上它在SynchronousMethodHandler的执行中控制重试逻辑。
4、RequestInterceptor 在SynchronousMethodHandler发起执行中,会使用该拦截器对RequestTemplate进行处理。这是一个拓展点。
5、Logger 执行请求时打日志(在debug时打)。默认为Logger.Level.NONE即不打日志,可以增加bean覆盖。
- Logger.Level.NONE 不打印信息
- Logger.Level.BASIC 打印请求url和响应码。
- Logger.Level.HEADERS 打印BASIC信息外加header信息
- Logger.Level.FULL 打印所有
6、LoadBalancerFeignClient Client接口的实现类,是具有负载均衡能力的Client。Client接口为执行HTTP的接口,Client.Default是最终发出HTTP请求的类。
7、FeignLoadBalancer FeignLoadBalancer通过ILoadBalancer选择合适Server,通过Server重组URI,通过RibbonRequest持有的Client执行实际HTTP请求包装成Response。
8、LoadBalancerCommand ribbon的rxJava实现,执行负载流程逻辑的组件。
9、ILoadBalancer ribbon的负载均衡器抽象。
熔断: 在FeignClientsConfiguration中, 当配置了feign.hystrix.enabled,Feign Builder使用HystrixFeign.builder()。
所以build的时候新建HystrixInvocationHandler和HystrixDelegatingContract实例。
Feign build(final FallbackFactory<?> nullableFallbackFactory) { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); } }); super.contract(new HystrixDelegatingContract(contract)); return super.build(); }
来看看HystrixInvocationHandler的hystrix调用代码
final class HystrixInvocationHandler implements InvocationHandler { //省略 @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { //省略 HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) { //实际执行 @Override protected Object run() throws Exception { try { return HystrixInvocationHandler.this.dispatch.get(method).invoke(args); } catch (Exception e) { throw e; } catch (Throwable t) { throw (Error) t; } } @Override protected Object getFallback() { if (fallbackFactory == null) { return super.getFallback(); } try { //用配置的fallbackFactory创建fallback实例 Object fallback = fallbackFactory.create(getExecutionException()); Object result = fallbackMethodMap.get(method).invoke(fallback, args); //根据fallback对象的returntype解析包装内的结果返回 if (isReturnsHystrixCommand(method)) { return ((HystrixCommand) result).execute(); } else if (isReturnsObservable(method)) { // Create a cold Observable return ((Observable) result).toBlocking().first(); } else if (isReturnsSingle(method)) { // Create a cold Observable as a Single return ((Single) result).toObservable().toBlocking().first(); } else if (isReturnsCompletable(method)) { ((Completable) result).await(); return null; } else { return result; } } catch (IllegalAccessException e) { // shouldn't happen as method is public due to being an interface throw new AssertionError(e); } catch (InvocationTargetException e) { // Exceptions on fallback are tossed by Hystrix throw new AssertionError(e.getCause()); } } }; //根据方法的return去返回结果 if (Util.isDefault(method)) { return hystrixCommand.execute(); } else if (isReturnsHystrixCommand(method)) { return hystrixCommand; } else if (isReturnsObservable(method)) { // Create a cold Observable return hystrixCommand.toObservable(); } else if (isReturnsSingle(method)) { // Create a cold Observable as a Single return hystrixCommand.toObservable().toSingle(); } else if (isReturnsCompletable(method)) { return hystrixCommand.toObservable().toCompletable(); } return hystrixCommand.execute(); } //省略 }
到此这篇关于Spring Cloud 负载均衡器架构选型的文章就介绍到这了,更多相关Spring Cloud 负载均衡内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
一次"java:程序包org.aspectj.lang不存在"问题解决实战记录
这篇文章主要给大家介绍了一次"java:程序包org.aspectj.lang不存在"问题解决的实战过程,这个错误提示意味着你的Java程序中引用了org.aspectj.lang这个包,但是该包并不存在,文章通过图文介绍的非常详细,需要的朋友可以参考下2023-06-06
最新评论