使用Ribbon实现客户端侧负载均衡
Posted shi_zi_183
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Ribbon实现客户端侧负载均衡相关的知识,希望对你有一定的参考价值。
使用Ribbon实现客户端侧负载均衡
在生成环境中,各个微服务都会部署多个实例。那么服务消费者要如何将请求分摊到多个服务者实例上呢?
Ribbon简介
Ribbon是Netfix发布的负载均衡器,它有助于控制HTTP的TCP客户端的行为。为Ribbon配置服务提供者地址列表后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法。
在Spring Cloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。
为服务消费者整合Ribbon
1)复制项目microservice-consumer-movie,将ArtifactId修改为microservice-consumer-movie-ribbon。
2)为项目引入Ribbon的依赖,Ribbon的依赖如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
由于前文已为电影服务添加了spring-cloud-starter-netflix-eureka-client
,该依赖已包含spring-cloud-starter-netflix-eureka-ribbon
,因此无须再次引入。
3)为RestTemplate添加@LoadBalanced注解。
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
只需添加注解@LoadBalanced,就可以为RestTemplate整合Ribbon,使其具备负载均衡的能力。
4)对Controller代码进行修改
package com.example.controller;
import com.example.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class MovieController {
private static final Logger LOGGER = LoggerFactory.getLogger(MovieController.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Value("${user.userServiceUrl}")
private String userServiceUrl;
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return this.restTemplate.getForObject("http://microservice-provider-user/"+id,User.class);
}
@GetMapping("/log-user-instance")
public void logUserInstance(){
ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider-user");
//打印当前选择的是哪个节点
MovieController.LOGGER.info("{}:{}:{}",serviceInstance.getServiceId(),serviceInstance.getHost(),serviceInstance.getPort());
}
}
有代码可知,我们将请求的地址改为"http://microservice-provider-user/"。microservice-provider-user是用户微服务的虚拟主机名,当Ribbon和Eureka配合使用时,会自动将虚拟主机名映射成微服务的网络地址。在新增的logUserInstance()方法中可使用LoadBalancerClient的API更加直观地获取当前选择的用户微服务节点。
测试
1)启动microservice-discovery-eureka。
2)启动2个或更多microservice-provider-user实例。
3)启动microservice-consumer-movie-ribbon。
4)访问http://localhost:8761
5)多次访问http://localhost:8010/user/1
同时两个用户微服务实例都会打印类似如下的日志。
6)多次访问http://localhost:8010/log-user-instance
可以看到,此时请求会均匀分布到两个用户微服务节点上,说明已经实现类负载均衡。
- 虚拟主机名与虚拟IP非常类似,如果大家接触过HAProxy或Heartbeat,理解虚拟主机名就非常容易了,如果无法理解虚拟主机名,可将其简单理解成为提供者的服务名称,因为在默认情况下,虚拟主机名和服务名称是一致的。当然,也可以使用配置属性eureka.instance.virtual-host-name或者eureka.instance.secure-virtual-host-name指定虚拟主机名。
- 不能将restTemplate.getForObject(…)与loadBalancerClient.choose(…)写在同一个方法中,两者之间会有冲突——因为此时代码中的restTemplate实际上是一个Ribbon客户端,本身已经包含了"choose"的行为。
- 虚拟主机名中不能包含"_"之类的字符,否则Ribbon在调用时会报异常。
Ribbon配置自定义
很多场景下,可能根据需要自定义Ribbon的配置,例如修改Ribbon的负载均衡规则等Spring Cloud Edgware允许使用Java代码或属性自定义Ribbon的配置,两种方式是等价的。
使用Java代码自定义Ribbon配置
配置指定名称的Ribbon Client
在Spring Cloud中,Ribbon的默认配置如下(格式是BeanType beanName:ClassName):
- IClientConfig ribbonClientConfig:DefaultClientConfigImpl
- IRule ribbonRule:ZoneAvoidanceRule
- IPing ribbonPing:DummyPing
- ServerList<Server> ribbonServerList:ConfigurationBasedServerList
- ServerListFilter<Server> ribbonServerListFilter:ZonePreferenceServerListFilter
- ILoadBalancer ribbonLoadBalancer:ZoneAwareLoadBalancer
- ServerListUpdater ribbonServerListUpdater:PollingServerListUpdater
//来自org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, this.name)) {
return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
} else {
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
}
BeanType是IRule,beanName是ribbonRule,ClassName是ZoneAvoidanceRule,这是一种根据服务提供者所在Zone的性能以及服务提供者可用性综合计算,选择提供者节点的负载均衡规则。
在Spring Cloud中,Ribbon默认的配置类是RibbonClientConfiguration。也可使用一个POJO自定义Ribbon的配置(自定义配置会覆盖默认配置)。这种配置是细粒度的,不同名称的Ribbon客户端使用不同配置。
下面来为名为microservice-provider-user的Ribbon客户端自定义配置。
1)复制项目microservice-consumer-movie-ribbon,将ArtifactId修改为microservice-consumer-movie-ribbon-customizing。
2)创建Ribbon的配置类(该类不能放在ComponentScan扫描目录下,否则该服务访问所有的生产者都会使用该配置)
package com.example.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
// 负载均衡规则,改为随机
return new RandomRule();
}
}
3)创建一个空类,并在其上添加@Configuration注解和@RibbonClient注解
package com.example.config;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;
@Configuration
@RibbonClient(name = "microservice-provider-user",configuration = RibbonConfiguration.class)
public class TestConfiguration {
}
由代码可知,使用@RibbonClient注解的configuration属性,即可自定义指定名称Ribbon客户端的配置。
测试
1)启动microservice-discovery-eureka
2)启动2个或更多microservice-provider-user实例。
3)启动microservice-consumer-movie-ribbon-customizing。
4)多次访问http://localhost:8010/log-user-instance
注:必须注意的是,本例中的RibbonConfiguration类不能存放在主应用程序上下文的@ComponentScan所扫描的包中,否则该类中的配置信息将被所有的@RibbonClient共享。
因此,如果只想自定义某一个Ribbon客户端的配置,必须防止@Configuration注解的类所在的包与@ComponentScan扫描的包重叠,或应显示指定@ComponentScan不扫描@Configuration类所在的包。
全局配置
可使用@RibbonClients注解为所有Ribbon Client提供默认配置
package com.ribbon;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ConfigurationBasedServerList;
import com.ribbon.DefaultRibbonConfig;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
@Configuration@RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)
public class RibbonClientDefaultConfigurationTestsConfig {
public static class BazServiceList extends ConfigurationBasedServerList {
public BazServiceList(IClientConfig config) {
super.initWithNiwsConfig(config);
}
}
}
package com.example.config;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DefaultRibbonConfig {
@Bean
public IRule ribbonRule() {
return new BestAvailableRule();
}
@Bean
public IPing ribbonPing() {
return new PingUrl();
}
@Bean
public ServerList<Server> ribbonServerList(IClientConfig config) {
return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config);
}
@Bean
public ServerListSubsetFilter serverListSubsetFilter() {
ServerListSubsetFilter filter = new ServerListSubsetFilter();
return filter;
}
}
使用属性自定义Ribbon配置
从Spring Cloud Netfix 1.2.0开始(即从Spring Cloud Camden RELEASE开始),Ribbo支持使用属性自定义。这种方式比使用Java代码配置的方式更加方便。
支持的属性如下,配置的前缀是<clientName>.ribbon
。其中<clientName>
是RibbonClient的名称,如果省略,则表示全局配置。
- NFLoadBalancerClassName:配置IloadBalancer的实现类
- NFLoadBalancerRuleClassName:配置IRule的实现类
- NFLoadBalancerPingClassName:配置IPing的实现类
- NIWSServerListClassName:配置ServerList的实现类
- NIWSServerListFilterClassName:配置ServerListFilter的实现类
下面,我们来用属性来修改名为microservice-provider-user的Ribbon Client的负载均衡规则。
复制项目microservice-consumer-movie-ribbon,将ArtifactId修改为microservice-consumer-movie-ribbon-customizing-properties。在项目的application.yml中添加以下内容即可。
microservice-provider-user:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
这样,就可以将名为microservice-provider-user的Ribbon Client的负载均衡规则设为随机。
若配置成如下形式,则表示对所有Ribbon Client都是用RandomRule;
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
测试
1)启动microservice-discovery-eureka
2)启动2个或更多microservice-provider-user实例。
3)启动microservice-consumer-movie-ribbon-customizing-properties。
4)多次访问http://localhost:8010/log-user-instance
注:属性配置的方式比Java代码配置的方式优先级更高
脱离Eureka使用Ribbon
在前文的实例中,是将Ribbon与Eureka配合使用的,但现实中可能不具备这样的条件,例如一些遗留的微服务,它们可能并没有注册到Eureka Server上,甚至根本不是使用Spring Cloud开发的,此时要想使用Ribbon实现负载均衡,要怎么办?
Ribbon支持脱离Eureka使用。
1)复制项目microservice-consumer-movie-ribbon,将ArtifactId修改为microservice-consumer-movie-without-eureka。在项目的application.yml中添加以下内容即可
2)去掉项目中Eureka依赖,只使用spring-cloud-starter-netflix-ribbon
pom.xml,找到
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
修改为
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
3)去掉启动类上的@EnableDiscoveryClient(如果有的话)
4)将application.yml改为
server:
port: 8010
spring:
application:
name: microservice-consumer-movie
microservice-provider-user:
ribbon:
listOfServers: localhost:8000,localhost:8001,localhost:8002
测试
1)启动2个或更多microservice-provider-user实例。
2)启动microservice-consumer-movie-without-eureka。
3)多次访问http://localhost:8010/log-user-instance
注:当Eureka Client依赖在项目的classpath下时,如果想单独使用Ribbon,而不是使用Eureka的服务发现功能,需添加配置ribbon.eureka.enabled=false。某些场景下,我们可能只想让指定名称的Ribbon Client去使用指定的URL请求,其他Ribbon Client依旧与Eureka配合使用,那么可配置:
<clientName>:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
microservice-provider-user:
ribbon:
listOfServers: localhost:8000,localhost:8001,localhost:8002
这样,对于名为<clientName>
的Ribbon Client,即可从地址列表中选择地址去请求,而其他名称的Ribbon Client依旧可与Eureka配合使用——自动从Eureka Server获得目标服务的地址,并选择一个去请求。
饥饿加载
Spring Cloud会为每个名称的Ribbon Client维护一个子应用程序上下文,这个上下文默认是懒加载的。指定名称的RibbonClient第一次请求时,对应的上下文才会被加载,因此,首次请求往往会比较慢。从Spring Cloud Dalston开始,我们可配置饥饿加载。
ribbon:
eager-load:
enabled:true
client: client1,client2
这样,对于名为client1、client2的Ribbon Client,将在启动时就加载对应的子应用程序上下文,从而提高首先请求的访问速度。
以上是关于使用Ribbon实现客户端侧负载均衡的主要内容,如果未能解决你的问题,请参考以下文章
springCloud:Ribbon实现客户端侧负载均衡-消费者整合Ribbon