SpringCloud(五)负载均衡之Ribbon
关于负载均衡
负载均衡一般分为服务器端负载均衡和客户端负载均衡
所谓服务器端负载均衡,比如Nginx、F5这些,请求到达服务器之后由这些负载均衡器根据一定的算法将请求路由到目标服务器处理。
所谓客户端负载均衡,比如我们要说的Ribbon,服务消费者客户端会有一个服务器地址列表,调用方在请求前通过一定的负载均衡算法选择一个服务器进行访问,负载均衡算法的执行是在请求客户端进行。
Ribbon是Netflix发布的负载均衡器。Eureka一般配合Ribbon进行使用,Ribbon利用从Eureka中读取到服务信息,在调用服务提供者提供的服务时,会根据一定的算法进行负载。
Ribbon高级应用
需求:
复制商品微服务9001,在9000和9001编写Controller,返回服务实例端口。
Page微服务中通过负载均衡策略调用szx-service-product的controller
在微服务中使用Ribbon不需要额外导入依赖坐标,微服务中引入过eureka-client相关依赖,会自动引入Ribbon相关依赖坐标。
代码中使用如下,在RestTemplate上添加对应注解即可
@SpringBootApplication
//@EnableEurekaClient //Eureka客户端,只能在Eureka环境中使用
@EnableDiscoveryClient //也是将当前项目表示为注册中心的客户端,像注册中心进行注册,可以在所有的服务注册中心环境中使用
public class PageApplication {
public static void main(String[] args) {
SpringApplication.run(PageApplication.class,args);
}
/**
* 向容器中注入一个RestTemplate,封装了HttpClient
* @return
*/
@Bean
@LoadBalanced //Ribbon负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
创建szx-serivce-product-9001微服务,创建ServerConfigController,定义方法返回当前微服务所使用的容器端口号
修改服务提供者api返回值,返回当前实例的端口号,便于观察负载情况
@RestController
@RequestMapping("/service")
public class ServiceInfoController {
//获取配置文件中端口号
@Value("${server.port}")
private String port;
@RequestMapping("/getPort")
public String getPort(){
return port;
}
}
**在页面静态微服务中调用szx-serivce-product下在资源路径:**http://localhost:9100/page/getProductServerPort
/**
* 测试负载均衡调用端口
* @return
*/
@GetMapping("/getProductServerPort")
public String getProductServerPort(){
//因为配置了负载均衡,所以不需要获取getInstances,直接使用微服务名称即可调用
String url = "http://szx-service-product/service/getPort";
System.out.println("******************************************************************************");
System.out.println("url = " + url);
System.out.println("******************************************************************************");
String result = restTemplate.getForObject(url, String.class);
return result;
}
Ribbon负载均衡策略
Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为 com.netflix.loadbalancer.IRule ,接口简介:
/**
* Interface that defines a "Rule" for a LoadBalancer. A Rule can be thought of
* as a Strategy for loadbalacing. Well known loadbalancing strategies include
* Round Robin, Response Time based etc.
*
* @author stonse
*
*/
public interface IRule {
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
负载均衡策略 | 描述 |
---|---|
RoundRobinRule:轮询策略 | 默认超过10次获取到的server都不可用,会返回一个空的server |
RandomRule:随机策略 | 如果随机到的server为null或者不可用的话,会while不停的循环选取 |
RetryRule:重试策略 | 一定时限内循环重试。默认继承RoundRobinRule,也支持自定义注入,RetryRule会在每次选取之后,对选举的server进行判断,是否为null,是否alive,并且在500ms内会不停的选取判断。而RoundRobinRule失效的策略是超过10次,RandomRule是没有失效时间的概念,只要serverList没都挂。 |
BestAvailableRule:最小连接数策略 | 遍历serverList,选取出可用的且连接数最小的一个server。该算法里面有一个LoadBalancerStats的成员变量,会存储所有server的运行状况和连接数。如果选取到的server为null,那么会调用RoundRobinRule重新选取。 |
AvailabilityFilteringRule:可用过滤策略 | 扩展了轮询策略,会先通过默认的轮询选取一个server,再去判断该server是否超时可用,当前连接数是否超限,都成功再返回。 |
ZoneAvoidanceRule:区域权衡策略**(默认策略)** | 扩展了轮询策略,继承了2个过滤器:ZoneAvoidancePredicate和AvailabilityPredicate,除了过滤超时和链接数过多的server,还会过滤掉不符合要求的zone区域里面的所有节点, 在一个区域/机房内的服务实例中轮询。先过滤再轮询 |
修改负载均衡策略:
#针对的被调用方微服务名称(szx-service-product),不加就是全局生效
szx-service-product.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule #随机
szx-service-product.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule #轮询
Ribbon核心源码剖析
Ribbon工作原理:
在之前直接调用服务的基础上,增加了拦截器,拦截器的作用是拦截请求,进行负载均衡算法,选择符合目标条件的服务进行调用
- SpringCloud充分利用了SpringBoot的自动装配特点,找spring.factories配置文件
-
LoadBalancerAutoConfiguration 类中配置
装配验证:
@Configuration @ConditionalOnClass({RestTemplate.class}) //只有存在RestTemplate类时,或者子类对象,该配置类才会装配生效 @ConditionalOnBean({LoadBalancerClient.class}) //在容器中必须存在负载均衡的Client //@ConditionalOnClass和@ConditionalOnBean都是进行验证使用,在自动配置前进行验证的条件 @EnableConfigurationProperties({LoadBalancerRetryProperties.class}) //启用自动配置 public class LoadBalancerAutoConfiguration { }
自动注入:
@Configuration @ConditionalOnClass({RestTemplate.class}) //只有存在RestTemplate类时,或者子类对象,该配置类才会装配生效 @ConditionalOnBean({LoadBalancerClient.class}) //在容器中必须存在负载均衡的Client //@ConditionalOnClass和@ConditionalOnBean都是进行验证使用,在自动配置前进行验证的条件 @EnableConfigurationProperties({LoadBalancerRetryProperties.class}) //启用自动配置 public class LoadBalancerAutoConfiguration { @LoadBalanced //该RestTemplate集合会自动注入添加了@LoadBalanced注解的RestTemplate对象 @Autowired( required = false ) private List<RestTemplate> restTemplates = Collections.emptyList(); //创建一个空的RestTemplate集合 }
初始化:
@Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { return () -> { //启用restTemplateCustomizers定制化工具 restTemplateCustomizers.ifAvailable((customizers) -> { Iterator var2 = this.restTemplates.iterator(); while(var2.hasNext()) { RestTemplate restTemplate = (RestTemplate)var2.next(); Iterator var4 = customizers.iterator(); while(var4.hasNext()) { RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next(); customizer.customize(restTemplate); } } }); }; }
注入restTemplate定制器:
为retTemplate对象设置loadBalancerInterceptor
@Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) { return (restTemplate) -> { List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors()); //向list中增加负载均衡拦截器 list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; }
到这里,我们明白,添加了注解的RestTemplate对象会被添加一个拦截器LoadBalancerInterceptor,该拦截器就是后续拦截请求进行负载处理的。