文章目录
1. 什么是?
定制设计负载均衡是从多个服务定制设计中根据某个策略选择一定制设计定制设计个进行访问,定制设计常见的负载均衡分为两种
- 定制设计客户端负载均衡:定制设计即在客户端就进行负载定制设计均衡算法分配。例如spring cloud中的
ribbon
,定制设计客户端会有定制设计一个服务器地址列表,定制设计在发送请求前通过负载定制设计均衡算法选择 一个服务器,定制设计然后进行访问 - 定制设计服务端负载均衡:定制设计在消费者和服务提供方定制设计中间使用独立的代理方定制设计式进行负载。例如
Nginx
,定制设计先发送请求,然后通过Nginx
定制设计的负载均衡算法,定制设计在多个服务器之间选择一 个进行访问!
定制设计常见的负载均衡算法:
随机
:定制设计通过随机选择服务进行执行,定制设计一般这种方式使用较少;轮询
:定制设计请求来之后排队处理,轮着来加权轮询
:定制设计通过对服务器性能的分型,给高配置,定制设计低负载的服务器分配更高的权重,均衡各个 定制设计服务器的压力;一致性hash
:定制设计通过客户端请求的地址的HASH定制设计值取模映射进行服务器调度。最少并发
:定制设计将请求分配到当前压力定制设计最小的服务器上
2. 的使用
Ribbon属于netflix的产品,依赖如下
<!--添加ribbon的依赖--><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>
- 1
- 2
- 3
- 4
- 5
但 spring-cloud 定制设计体系下的大多数产品都整合和ribbon,定制设计如服务发现nacos-discovery
,RPC调用feign
组件等等,所以,定制设计使用时可以不用再引入ribbon
依赖
使用Ribbon定制设计时只需添加@LoadBalanced
注解即可,定制设计代表当前请求拥有了负定制设计载均衡的能力
①:为RestTemplate
添加@LoadBalanced
注解
@Configurationpublic class RestConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
②:使用RestTemplate
定制设计进行远程调用,定制设计此次调用有负载均衡效果!
@Autowiredprivate RestTemplate restTemplate;@RequestMapping(value = "/findOrderByUserId/{id}")public R findOrderByUserId(@PathVariable("id") Integer id) { String url = "http://order/findOrderByUserId/"+id; R result = restTemplate.getForObject(url,R.class); return result;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
①:定制设计自定义负载均衡策略
定制设计自定义负载均衡策略方式有多种
- 实现
IRule
接口 - 或者继承
AbstractLoadBalancerRule
类
实现基于Nacos
定制设计权重的负载均衡策略:nacos
中权重越大的实例请求频次越高!
//继承 AbstractLoadBalancerRule 类@Slf4jpublic class NacosRandomWithWeightRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public Server choose(Object key) { //获取负载均衡器 DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer(); String serviceName = loadBalancer.getName(); NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); try { //nacos基于权重的算法 Instance instance = namingService.selectOneHealthyInstance(serviceName); return new NacosServer(instance); } catch (NacosException e) { log.error("获取服务实例异常:{}", e.getMessage()); e.printStackTrace(); } return null; } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
自定义负载均衡策略的配置也有两种
-
全局配置
:当前服务调用其他微服务时,一律使用指定的负载均衡算法@Configurationpublic class RibbonConfig { /** * 全局配置 * 指定负载均衡策略 * @return */ @Bean public IRule ribbonRule() { // 指定使用基于`Nacos`权重的负载均衡策略:`nacos`中权重越大的实例请求频次越高! return new NacosRandomWithWeightRule(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
-
局部配置
:当前服务调用指定的微服务时,使用对应的负载均衡算法,比如调用order
服务使用该算法,调用其他的不使用!# 被调用的微服务名mall-order: ribbon: # 自定义的负载均衡策略(基于随机&权重) NFLoadBalancerRuleClassName: com.china.test.ribbondemo.rule.NacosRandomWithWeightRule
- 1
- 2
- 3
- 4
- 5
②:Ribbon的饥饿加载
Ribbon
默认懒加载,意味着只有在发起调用的时候才会创建客户端。在第一次进行服务调用时会做一些初始化工作,比如:创建负载均衡器 ,如果网络情况不好,这次调用可能会超时。
可以开启饥饿加载,在项目启动时就完成初始化工作,解决第一次调用慢的问题
ribbon: eager-load: # 开启ribbon饥饿加载,源码对应属性配置类:RibbonEagerLoadProperties enabled: true # 配置mall-user使用ribbon饥饿加载,多个使用逗号分隔 clients: mall-order
- 1
- 2
- 3
- 4
- 5
- 6
开启之后,可以看到,第一次调用日志已经没有初始化工作了
3. Ribbon的负载均衡原理
①:收集带有@LoadBalanced注解的,并为其添加一个负载均衡拦截器
上面的使用案例中,如果不加@LoadBalanced
注解的话,RestTemplate
没有负载均衡功能的,为什么一个@LoadBalanced
注解就使RestTemplate
具有负载均衡功能了呢?下面来看一下Ribbon的负载均衡原理
Ribbon既然在springboot中使用,自然会想到springboot对Ribbon的自动配置类RibbonAutoConfiguration
!这个自动配置类被加载的前置条件是:需要加载LoadBalancerAutoConfiguration
类,如下所示
@Configuration@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)@RibbonClients@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")//加载的前置条件:先加载 LoadBalancerAutoConfiguration类@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class })@EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class })public class RibbonAutoConfiguration {
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
而 LoadBalancerAutoConfiguration
类是属于spring-cloud-common包下的,该包是spring cloud的基础包,肯定会被加载的
进入LoadBalancerAutoConfiguration
类中,该类中注册了几个bean,主要做了以下几件事
- 收集到所有带有
@LoadBalanced
注解的RestTemplate
,并放入restTemplates
集合 - 创建一个带有负载均衡功能的拦截器
LoadBalancerInterceptor
- 在容器类初始化完毕后,把所有的
RestTemplate
内部都添加上拦截器LoadBalancerInterceptor
,当有请求经过ribbon
,通过restTemplate
发起调用时,会先走此拦截器,实现负载均衡
@Configuration@ConditionalOnClass(RestTemplate.class)@ConditionalOnBean(LoadBalancerClient.class)@EnableConfigurationProperties(LoadBalancerRetryProperties.class)public class LoadBalancerAutoConfiguration { //Springboot会将所有带有@LoadBalanced注解的RestTemplate,都放进restTemplates这个集合中去 @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); //SmartInitializingSingleton :该方法会等待所有类都初始化完毕后执行 // 拿到上面收集到的所有带有@LoadBalanced注解的RestTemplate // 执行下面的函数式接口方法customize,把拦截器 放入每一个restTemplate中, //当有请求经过ribbon,通过 restTemplate 发起调用时,会先走此拦截器,实现负载均衡 @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); } } }); } @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } @Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { //向容器中放入一个带有负载均衡功能的拦截器 @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } //这是一个函数式接口,此处实现的是接口的customize方法,定义了一个操作,操作内容如下: // 传入一个restTemplate,并把上面的拦截器 放入restTemplate中 //当有请求经过ribbon,通过 restTemplate 发起调用时 //会先走此拦截器,实现负载均衡 @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
LoadBalancerAutoConfiguration
是如何精准的收集到所有的带有@LoadBalanced
注解的RestTemplate
呢?点开@LoadBalanced
注解,发现他是带有@Qualifier
限定符的!
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifier //spring的限定符public @interface LoadBalanced {}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
@Qualifier
限定符的作用:
- 一个接口有两个实现类,当我们通过
@AutoWired
注解进行注入时,spring
不知道应该绑定哪个实现类,从而导致报错。 - 这时就可以通过
@Qualifier
注解来解决。通过它可以标识我们需要的实现类。而@LoadBalanced
的元注解是@Qualifier
,所以 源码中就可以通过@LoadBalanced
注解来限定收集所有带有@LoadBalanced
注解的RestTemplate
实现
②:选择负载均衡器,执行负载均衡算法(默认轮询)
上面说到请求经过ribbon的RestTemplate
调用时,会先走其内部的LoadBalancerInterceptor
的负载均衡逻辑。既然是走拦截器,那么就可以去看LoadBalancerInterceptor
的intercept()
方法,一般该方法就有负载均衡逻辑!
@Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); //负载均衡器的 execute 方法 return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException { //获取负载均衡器 ILoadBalancer ILoadBalancer loadBalancer = getLoadBalancer(serviceId); // 通过负载均衡选择一个服务器 Server server = getServer(loadBalancer, hint); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); //向某一台服务器 发起HTTP请求 return execute(serviceId, ribbonServer, request); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
如上,Ribbon
通过负载均衡 选择一台机器 发起http
请求已经执行完毕!
Ribbon负载均衡器的默认实现:ZoneAwareLoadBalancer
上面说到getLoadBalancer(serviceId)
方法可以获取一个负载均衡器,用于执行负载均衡算法,这个负载均衡器已经在RibbonClientConfiguration
配置类中初始化好了,获取时直接从容器中取即可
@Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { // 从配置类中找一个负载均衡器 ILoadBalancer if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) { return this.propertiesFactory.get(ILoadBalancer.class, config, name); } // 如果没有就创建一个负载均衡器的实现 ZoneAwareLoadBalancer return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
可以看到RibbonClientConfiguration
配置类中,默认初始化的是ZoneAwareLoadBalancer
,它具备区域感知的能力。
在创建默认负载均衡器时(new ZoneAwareLoadBalancer
)做了什么呢?
- 从
nacos
注册中心上立即获取最新的服务信息,保存在ribbon
的本地服务列表中 - 使用延时定时线程池,定时从nacos上拉取最新服务地址,更新
ribbon
的本地服务列表中
进入new ZoneAwareLoadBalancer
中:
void restOfInit(IClientConfig clientConfig) { boolean primeConnection = this.isEnablePrimingConnections(); // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList() this.setEnablePrimingConnections(false); //开启定时延时任务(会延后执行),定时从nacos上拉取最新服务地址,更新`ribbon`的本地服务列表中 enableAndInitLearnNewServersFeature(); //进入后立即执行,从`nacos`注册中心上立即获取最新的服务信息,保存在`ribbon`的本地服务列表中 updateListOfServers(); if (primeConnection && this.getPrimeConnections() != null) { this.getPrimeConnections() .primeConnections(getReachableServers()); } this.setEnablePrimingConnections(primeConnection); LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString()); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
开启定时任务方法enableAndInitLearnNewServersFeature();
如下:
@Override public synchronized void start(final UpdateAction updateAction) { if (isActive.compareAndSet(false, true)) { //开启一个线程,执行更新任务 final Runnable wrapperRunnable = new Runnable() { @Override public void run() { if (!isActive.get()) { if (scheduledFuture != null) { scheduledFuture.cancel(true); } return; } try { //UpdateAction 又是一个函数式接口, //doUpdate方法需要看一下传进来的方法内容,下文展示 updateAction.doUpdate(); lastUpdated = System.currentTimeMillis(); } catch (Exception e) { logger.warn("Failed one update cycle", e); } } }; //开启定时延时任务,定时执行上面的线程 scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay( wrapperRunnable, initialDelayMs, refreshIntervalMs, TimeUnit.MILLISECONDS ); } else { logger.info("Already active, no-op"); } }================== 函数式接口的 updateAction.doUpdate()方法内容如下============= protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { updateListOfServers(); } }; // updateListOfServers方法如下: @VisibleForTesting public void updateListOfServers() { List<T> servers = new ArrayList<T>(); if (serverListImpl != null) { // 该方法会从对应的配置中心中取最新数据 servers = serverListImpl.getUpdatedListOfServers(); LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers); if (filter != null) { servers = filter.getFilteredListOfServers(servers); LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers); } } // 更新本地服务列表! updateAllServerList(servers); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
负载均衡器初始化时,立即从注册中心获取最新服务的方法updateListOfServers()
,如下:
@VisibleForTesting public void updateListOfServers() { List<T> servers = new ArrayList<T>(); if (serverListImpl != null) { //从注册中心上获取服务地址 servers = serverListImpl.getUpdatedListOfServers(); LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers); if (filter != null) { servers = filter.getFilteredListOfServers(servers); LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers); } } //更新本地服务列表! updateAllServerList(servers); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
Ribbon负载均衡算法的默认实现:ZoneAvoidanceRule
有了负载均衡器ZoneAwareLoadBalancer
,接下来执行负载均衡算法即可getServer(loadBalancer, hint);
,回顾上边第②条的代码:
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException { //获取负载均衡器 ILoadBalancer ILoadBalancer loadBalancer = getLoadBalancer(serviceId); // 通过负载均衡算法 选择一个服务器地址 Server server = getServer(loadBalancer, hint); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); //向某一台服务器 发起HTTP请求 return execute(serviceId, ribbonServer, request); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
进入负载均衡算法选择服务器的方法getServer(loadBalancer, hint)
中
public Server chooseServer(Object key) { if (counter == null) { counter = createCounter(); } counter.increment(); if (rule == null) { return null; } else { try { //这个rule就是ribbon的负载均衡算法! //默认是 ZoneAvoidanceRule ,在没有区域的环境下,类似于轮询(RandomRule) return rule.choose(key); } catch (Exception e) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e); return null; } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
ZoneAvoidanceRule
的核心逻辑如下:使用cas
+ 死循环
轮询服务器地址
private int incrementAndGetModulo(int modulo) { for (;;) { int current = nextIndex.get(); //取模得到其中一个 int next = (current + 1) % modulo; //cas赋值 ,返回nextIndex的机器 if (nextIndex.compareAndSet(current, next) && current < modulo) return current; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
其中,ZoneAvoidanceRule
的初始化和负载均衡器ZoneAwareLoadBalancer
的初始化一样,也在RibbonClientConfiguration
配置类中完成!
@Bean @ConditionalOnMissingBean public IRule ribbonRule(IClientConfig config) { if (this.propertiesFactory.isSet(IRule.class, name)) { return this.propertiesFactory.get(IRule.class, config, name); } // 负载均衡策略的 默认实现ZoneAvoidanceRule ZoneAvoidanceRule rule = new ZoneAvoidanceRule(); rule.initWithNiwsConfig(config); return rule; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Ribbon的负载均衡策略有如下几种
RandomRule
:随机策略, 随机选择一个Server。RetryRule
: 重试策略 。对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server。RoundRobinRule
: 轮询策略 。 轮询index,选择index对应位置的Server。AvailabilityFilteringRule
:可用性过滤策略 。 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里记录的各个Server的运行状态。BestAvailableRule
:最低并发策略 。 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。WeightedResponseTimeRule
:响应时间加权重策略。根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。ZoneAvoidanceRule
:区域权重策略。默认的负载均衡策略,综合判断server所在区域的性能和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server。在没有区域的环境下,类似于轮询(RandomRule
)NacosRule
: 同集群优先调用
4. 的原理
Feign和OpenFeign的区别?
Feign
:Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTP API。可以单独使用OpenFeign
:Spring Cloud openfeign对Feign进行了 增强,使其支持Spring MVC
注解,另外还整合了Ribbon
和Eureka
,从而使得Feign的使用更加方便
Feign的调用原理图(可在每一层做扩展)
OpenFeign的常用配置项:(对应上图,可以在配置中做扩展)
日志配置
:有时候我们遇到 Bug,比如接口调用失败、参数没收到等问题,或者想看看调用性能,就需要配置 Feign 的 日志了,以此让 Feign 把请求信息输出来。日志配置分为局部配置和全局配置!拦截器配置
:每次 feign 发起http调用之前,会去执行拦截器中的逻辑,就类似mvc中的拦截器。比如:做权限认证。超时时间配置
:通过 Options 可以配置连接超时时间(默认2秒)和读取超时时间(默认5秒),注意:Feign的底层用的是Ribbon
,但超时时间以Feign配置为准客户端组件配置
:Feign 中默认使用 JDK 原生的URLConnection
发送 HTTP 请求,我们可以集成别的组件来替换掉 URLConnection,比如 Apache HttpClient,OkHttp。GZIP 压缩配置
:再配置文件中开启压缩可以有效节约网络资源,提升接口性能编码器解码器配置
:Feign 中提供了自定义的编码解码器设置,同时也提供了多种编码器的实现,比如 Gson、Jaxb、Jackson。 我们可以用不同的编码解码器来处理数据的传输。如果你想传输 XML 格式的数据,可以自定义 XML 编码解 码器来实现获取使用官方提供的 Jaxb
5. OpenFeign是如何整合Ribbon的?
通过上边,我们已经知道Ribbon
可以把微服务的 服务名 通过负载均衡策略替换成某一台机器的IP
地址,然后通过http
请求进行访问!如下所示:
http://
mall-order
/order/findOrderByUserId ====> http://192.168.100.15
/order/findOrderByUserId
而Feign
则是把参数组装到url中去,实现一个完整的RPC调用
http://mall-order/order/findOrderByUserId/
5(参数)
====> http://192.168.100.15/order/findOrderByUserId/5(参数)
①:扫描所有@FeignClient注解,以FactoryBean的形式注册到容器中
Feign的使用需要用到@EnableFeignClients
、@FeignClient("gulimall-ware")
这两个注解,其中,@EnableFeignClients
通过@Import
向容器中添加了一个bean定义注册器,用于扫描@FeignClient("gulimall-ware")
注解,注册bean定义
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented//通过`@Import`向容器中添加了一个bean定义注册器 FeignClientsRegistrar@Import(FeignClientsRegistrar.class)public @interface EnableFeignClients {}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
进入FeignClientsRegistrar
的registerBeanDefinitions
方法,查看具体注册了什么
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //注册一下默认配置 registerDefaultConfiguration(metadata, registry); //注册所有@FeignClient注解 标注的类 registerFeignClients(metadata, registry); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
注册逻辑中,最主要的就是把所有@FeignClient
注解 标注的类以FactoryBean
的形式注册到容器中
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 1. 获取一个扫描器 ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; //2. 拿到所有@FeignClient注解标注的类 Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); } 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))); } 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")); // 3. 把这些类注入容器 registerFeignClient(registry, annotationMetadata, attributes); } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
修改bean定义为FactoryBean
的子类FeignClientFactoryBean
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); //把bean定义构建成一个FactoryBean 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); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
②:RPC调用时,通过LoadBalancerFeignClient整合Ribbon,实现负载均衡调用
既然是把@FeignClient("gulimall-ware")
注解标注的类 以FactoryBean
的子类FeignClientFactoryBean
的形式注入到容器,那么RPC调用时肯定是通过调用FeignClientFactoryBean
的getObject
方法来使用的!
@Override public Object getObject() throws Exception { return getTarget(); }
- 1
- 2
- 3
- 4
getTarget()
:
<T> T getTarget() { FeignContext context = this.applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith("http")) { this.url = "http://" + this.name; } else { this.url = this.name; } this.url += cleanPath(); return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url)); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
loadBalance()
:
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) { //获取的其实是LoadBalanceFeignClient,用于整合Ribbon的负载均衡 Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); 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?"); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
Client client = getOptional(context, Client.class)
;获取的是Feign
的客户端实现:
整合Ribbon逻辑:进入LoadBalancerFeignClient
的execute
方法中,使用Feign
的负载均衡器向Ribbon
发请求,已达到整合Ribbon
的目的!
org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient # execute
@Override public Response execute(Request request, Request.Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); //使用Feign的负载均衡器向Ribbon发请求,已达到整合Ribbon的目的! FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); return lbClient(clientName) .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { IOException io = findIOException(e); if (io != null) { throw io; } throw new RuntimeException(e); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
综上所述,负载均衡的逻辑还是在Ribbon
中,而Feign
通过整合Ribbon
实现了带有负载均衡的RPC
调用!