已Ribbon为例了解负载均衡
什么是负载均衡
负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
简单来说就是当一个服务部署在了多个节点时,客户端可以通过负载均衡来获取单个可以请求的节点。
为什么需要负载均衡
一台服务器所能承受的请求有限,并发场景下,为了能够承载更多请求,满足更多的用户,需要对服务器节点进行扩容,将用户流量分摊到多个节点,从而达到承接更大流量的目的。
常见的负载均衡算法
- 随机
- 轮询
- 权重 (响应时间、同机房策略、灰度策略)
怎么实现负载均衡
引入Spring Cloud Ribbon jar包,可以单独使用,需要在application.yml中配置服务节点。然后通过在RestTemplate实例上增加注解的方式,拦截RestTemplate发出的http请求,通过Ribbon内置的负载均衡算法,选择需要请求的服务节点,向选中的节点发送请求。
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
return restTemplateBuilder.build();
}
}
@RestController
public class LoadBlanceTest {
@Resource
private RestTemplate restTemplate;
@GetMapping("/getUser")
public String getUser(){
return restTemplate.getForObject("http://spring-cloud-test-service/users", String.class);
}
}
# 应用名称
spring.application.name=spring-cloud-ribbon
# 应用服务 WEB 访问端口
server.port=8080
spring-cloud-test-service.ribbon.listOfServers=\
http://localhost:8081,http://localhost:8082,http://localhost:8083
自定义负载均衡策略
可以通过实现IRule接口来自定义负载均衡算法实现。上图右侧是Spring Cloud目前以支持的实现算法。
spring-cloud-ribbon-service.ribbon.NFLoadBalancerRuleClassName=\
com.fishbone.example.springcloudprovidercosumer.FishBoneLoadBalance
public class FishBoneLoadBalance extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
return chooseServer(getLoadBalancer(),key);
}
public Server chooseServer(ILoadBalancer lb, Object key){
if (lb == null) {
return null;
}
Server server = null;
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
int index = chooseIpInt(serverCount);
server = upList.get(index);
return server;
}
private int chooseIpInt(int serverCount) {
//获取请求IP地址
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
String remoteAddre = requestAttributes.getRequest().getRemoteAddr();
int code = Math.abs(remoteAddre.hashCode());
return code%serverCount;
}
}
负载均衡原理
通过上方的内容,我们已经对负载均有了一个大概的理解。要实现负载均衡有以下几点需要考虑。
- 如何拦截客户端请求?
- 如何获取服务列表?
- 如何选择负载均衡规则?
- 如何请求服务端并拿到结果给到客户端?
以上只是一些基础的问题,肯定还会有很多细节需要考虑,比如:
http请求拦截到之后,只需要修改地址、端口么?
获取服务列表后,如果服务挂了怎么办?
如果调用不成功,比如超时要怎么解决?
如何根据业务选择最优的节点?
拦截请求
可以这么说,Spring Cloud 相关的starter 组件都用到了 Spring Boot 的自动装配机制。Ribbon 也不例外。所以我们需要找到 Ribbon 相关的自动装配入口。
在第一时间搜索 RibbonAutoConfiguration 后发现 一个类
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// 在看这里依赖 LoadBalancerAutoConfiguration 我们前边 就是通过 @LoadBalanced 注解实现负载
// 所以先去看一下 LoadBalancerAutoConfiguration
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
// ...
}
@Conditional 注解
看源码之前先简单介绍几个 @Conditional 注解。源码中大量使用,如果搞不清楚,会容易晕~
@ConditionalOnBean
// 当给定的在 bean 存在时,则实例化当前Bean,这个bean可能由于某种原因而没有注册到ioc里。
// 这时 @ConditionalOnBean 可以让当前bean也不进行注册,当前bean 指使用了这个注解的 bean
@ConditionalOnMissingBean
// 当给定的在bean不存在时,则实例化当前Bean,感觉这个是在多态环境下使用,
// 当一个接口有多个实现类时,如果只希望它有一个实现类,那就在各个实现类上加上这个注解
@ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean
这几个注解,包括其他类似的注解使用时后边一般会跟上条件类。比如:
@Configuration
public class ConditioanTeset {
// 对于这种 后边加了注释的 条件意思为 如果 CTest 不存在 则创建 ATest
@Bean
@ConditionalOnMissingBean(CTest.class)
public ATest getATest(){
return new ATest();
}
// 对于这种释没有跟条件值的看返回值类型,这里表示:如果BTest不存在则创建 BTest
@Bean
@ConditionalOnMissingBean
public BTest getBTest(){
System.out.println("getCTest");
return new BTest();
}
// 对于这种有参数条件的 条件意思为 如果 ATest 已存在 则用此方法 创建 ATest 但是 BTest 必须要存在
// 如果BTest 不存在 会查看是否有 BTest 实例化方法(本类或者其他 Spring 托管类),
// 如果没有则不满足条件
// 注意 如果方法名字一样 只会走一个,无参的优先级最低 (只有当有参的不满足条件时 才会走无参)
// 可能有点绕。可以理解一下,因为Spring 默认是单例, 所以 在初始化一个 bean 时 如果 一个bean 有多种初始化方式, 那就会选用其中的一种。
// 比如这里 getAtest 方法有两个,所以只会走有参数的(@ConditionalOnBean 满足条件,如果删除上边的无参,不满足条件就不会走)。
// 如果方法名字 不一样,则两个方法都会走。
@Bean
@ConditionalOnBean
public ATest getATest(BTest bTest) {
System.out.println(bTest);
return new ATest();
}
}
记住几点:
- 对于这种释没有跟条件值的看返回值类型,返回值类型就是条件类型。
- 如果方法名字一样 只会走一个,无参的优先级最低。
- 如果定义了参数,则参数必须要先实例化。会从 IOC容器中找参数对应的实例化类,先完成初始化,依赖注入。
- 在使用过程中,一定要谨慎,避免写一些不会触发的定义。比如:如果 A不存在,实例化A,并且需要一个参数A。
好了下面继续看 LoadBalancerAutoConfiguration 方法
LoadBalancerAutoConfiguration
这里需要先看类上的定义注解, LoadBalancerAutoConfiguration 依赖与 RestTemplate 和 LoadBlancerClient 两个类。
其实这里就是一个简单的 @Bean 对象的配置,可以按照顺序看一下,注意根据依赖关系决定加载的先后顺序,比如第一个方法,参数类型是 RestTemplateCustomizer 所以可以直接去看返回值为 RestTemplateCustomizer 的类。
这时会发现有两个方法 两个方法的区别就是参数不同,以及 方法所属的类加载条件不同 (RetryTemplate 是否存在)。 这里 我们并没有使用 RetryTemplate,因此看 LoadBalancerInterceptorConfig#restTemplateCustomizer 方法即可。
这时发现该方法又依赖 LoadBalancerInterceptor 参数, 接着找,又发现 LoadBalancerInterceptorConfig#loadBalancerInterceptor 方法,然后又有参数,继续找...
其实在 LoadBalancerInterceptor 这里就不难发现,这里注册了一个 LoadBalancerInterceptor 拦截器,同时用 SmartInitializingSingleton 做了一层封装。
简单来说,这个类就是用来 添加拦截器。 需要注意的就是 两个函数式 接口,在客户端初始化对象时(SmartInitializingSingleton )将会用到。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@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
// 当 bean 不存在时 则实例化当前 bean 对于这里来说这个 bean 就是 LoadBalancerRequestFactory
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
/**
* Auto configuration for retry mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
/**
* Auto configuration for retry intercepting mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor loadBalancerRetryInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactories.get(0));
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
来一波分析:
首先,起点就是 各个 AutoConfiguration ,需要关注的就是两个,第一个,LoadBalancerAutoConfiguration 这个的主要功能就是向 RestTemplate 注册拦截器。第二个,