SpringCloud(四)客户端负载均衡Ribbon
Ribbon是一个Netflix的客户端负载均衡组件,可以方便的定义负载均衡策略。
- SpringCloud(四)客户端负载均衡Ribbon
- 部署多实例的服务端
- 配置Ribbon负载均衡器
- 使用配置文件
- 直接使用注解和API配置
- 在SpringCloud中和Eureka集成
- 通过负载均衡器调用服务
- 使用LoadBalancerClient调用服务
- 使用负载均衡的RestTemplate调用服务
- 使用Feign调用服务
- 测试负载均衡的作用
部署多实例的服务端
在使用Ribbon之前,我们首先部署一个多实例的服务作为客户端负载均衡的测试对象。
我们继续使用之前的sms-service
服务,通过不同环境的配置分别在本机启动8001端口和9001端口的服务。
/*8001端口的服务*/
java -jar springCloudDemo-sms.jar --spring.profiles.active=dev
/*9001端口的服务*/
java -jar springCloudDemo-sms.jar --spring.profiles.active=prod
为了更好的演示Ribbon的负载均衡,我们在8001的服务中新增一条数据,这样当我们查询短信列表时就可以通过返回信息知道我们调用了哪个实例。
配置Ribbon负载均衡器
Ribbon的配置和使用在服务的调用方web-app-service
服务里面进行设置。我们使用配置文件,Ribbon API和集成Eureka三种方式进行Ribbon的配置。
首先我们引入Ribbon的jar包依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
使用配置文件
SpringCloud支持使用配置文件自定义一个Ribbon负载均衡器。Robbin配置的属性名称必须以<clientName>.ribbon.
开头。Ribbon配置中有以下几个类:
- NFLoadBalancerClassName
: should implement ILoadBalancer
- NFLoadBalancerRuleClassName
: should implement IRule
- NFLoadBalancerPingClassName
: should implement IPing
- NIWSServerListClassName
: should implement ServerList
- NIWSServerListFilterClassName
: should implement ServerListFilter
我们定义一个properties-sms-service
的负载均衡器
# 定义一个properties-sms-service服务的负载均衡器。服务实例信息来自配置文件
# 服务名
properties-sms-service:
ribbon:
# 服务实例列表
listOfServers: http://localhost:8001,http://localhost:9001
# 这个负载均衡器不做重试
MaxAutoRetriesNextServer: 0
MaxAutoRetries: 0
# 负载策略-轮询
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
# 设置它的服务实例信息来自配置文件, 如果不设置NIWSServerListClassName就会去Euereka里面找
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
直接使用注解和API配置
首先使用@RibbonClient
声明一个客户端。
@Configuration
@RibbonClient(name = "annotation-sms-service", configuration = AnnotationRibbonClientCofiguration.class)
public class AnnotationRibbonClient {
}
然后在AnnotationRibbonClientCofiguration.java
中进行客户端配置。
public class AnnotationRibbonClientCofiguration {
@Bean
public ServerList<Server> ribbonServerList() {
Server[] servers = new Server[2];
servers[0] = new Server("http://localhost:8001");
servers[1] = new Server("http://localhost:9001");
ServerList<Server> serverList = new StaticServerList<>(servers);
return serverList;
}
@Bean
public IRule initRule() {
// 轮询
//new RoundRobinRule();
// 选择一个最小的并发请求的server
//new BestAvailableRule();
// 随机
return new RandomRule();
}
}
在这个配置中我们指定了服务列表为localhost:8001
和localhost:9001
两个短信服务的实例,并且指定了负载策略为随机。与Feign的配置类似,配置类AnnotationRibbonClientCofiguration.java
最好不要使用@Configuration
注解,防止这个负载均衡器配置应用到全局中。
在SpringCloud中和Eureka集成
如果需要Ribbon和Eureka集成,只需要在application.yml
配置文件中设置ribbon.eureka.enabled=true
ribbon:
# 开启eureka与ribbon的集成
eureka:
enabled: true
# 暂不开启熔断机制
hystrix:
enabled: false
# 配置ribbon默认的超时时间
ConnectTimeout: 2000
ReadTimeout: 2000
# 是否开启重试
OkToRetryOnAllOperations: true
# 重试的时候实例切换次数
MaxAutoRetriesNextServer: 3
# 每个实例重试次数
MaxAutoRetries: 2
Ribbon客户端会自动从Eureka注册中心读取服务列表,这样一个名字为sms-service
(我们短信服务在Eureka上的注册名)就自动创建好了。
SpringCloud提供了以下默认的配置
BeanType | beanName | ClassName |
IClientConfig | ribbonClientConfig | DefaultClientConfigImpl |
IRule | ribbonRule | ZoneAvoidanceRule |
IPing | ribbonPing | NoOpPing |
ServerList | ribbonServerList | ConfigurationBasedServerList |
ServerListFilter | ribbonServerListFilter | ZonePreferenceServerListFilter |
ILoadBalancer | ribbonLoadBalancer | ZoneAwareLoadBalancer |
ServerListUpdater | ribbonServerListUpdater | PollingServerListUpdater |
我们可以对每个负载均衡器进行个性化设置,覆盖默认的配置。
@Configuration
@RibbonClient(name = "sms-service", configuration = SMSRobbinConfiguration.class)
public class SMSRobbinClient {
}
public class SMSRobbinConfiguration {
@Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl();
}
}
通过负载均衡器调用服务
使用LoadBalancerClient调用服务
LoadBalancerClient
的choose方法可根据负载均衡名称自动选择调用的实例。我们通过获取到的实例uri,使用RestTemplate发起Http请求调用接口。
@RestController
@RequestMapping("/loadBalancer/lbClient")
public class LoadBalancerClientController {
@Autowired
LoadBalancerClient client;
@Autowired
RestTemplate restTemplate;
private static final Logger LOGGER = LoggerFactory.getLogger(LoadBalancerClientController.class);
@RequestMapping("/eureka/querySMS")
public String eurekas() {
ServiceInstance serviceInstance = client.choose("sms-service");
URI uri = serviceInstance.getUri();
LOGGER.info("load balancer with eureka choose uri : {}", uri);
final URI resolve = uri.resolve("/sms");
return restTemplate.getForObject(resolve, String.class);
}
@RequestMapping("/annotation/querySMS")
public String annotation() {
ServiceInstance serviceInstance = client.choose("annotation-sms-service");
URI uri = serviceInstance.getUri();
LOGGER.info("load balancer with annotation choose uri : {}", uri);
final URI resolve = uri.resolve("/sms");
return restTemplate.getForObject(resolve, String.class);
}
@RequestMapping("/properties/querySMS")
public String properties() {
ServiceInstance serviceInstance = client.choose("properties-sms-service");
URI uri = serviceInstance.getUri();
LOGGER.info("load balancer with properties choose uri : {}", uri);
final URI resolve = uri.resolve("/sms");
return restTemplate.getForObject(resolve, String.class);
}
}
使用负载均衡的RestTemplate调用服务
在RestTemplate上使用@LoadBalanced
注解我们可以直接将RestTemplate和Robbin集成起来。这样我们调用服务的时候使用服务名称,Robbin将自动选择合适的实例,而RestTemplate帮我们发起请求。
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);//单位为ms
factory.setConnectTimeout(5000);//单位为ms
return factory;
}
@Bean(name="lbRestTemplate")
@LoadBalanced
RestTemplate lbRestTemplate() {
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setReadTimeout(2000);
simpleClientHttpRequestFactory.setConnectTimeout(2000);
return new RestTemplate(simpleClientHttpRequestFactory);
}
}
@RestController
@RequestMapping("/loadBalancer/restTemplate")
public class RestTemplateController {
@Autowired
@Qualifier("lbRestTemplate")
RestTemplate lbRestTemplate;
@RequestMapping("/eureka/querySMS")
public String eurekas() {
return lbRestTemplate.getForObject("http://sms-service/sms", String.class);
}
@RequestMapping("/annotation/querySMS")
public String annotation() {
return lbRestTemplate.getForObject("http://annotation-sms-service/sms", String.class);
}
@RequestMapping("/properties/querySMS")
public String properties() {
return lbRestTemplate.getForObject("http://properties-sms-service/sms", String.class);
}
}
声明RestTemplate时我们为了区分普通的RestTemplate和负载均衡的RestTemplate,负载均衡的RestTemplate命名为lbRestTemplate
。这样在使用的时候我们就能灵活的选择不同的RestTemplate。
使用Feign调用服务
SpringCloud中Feign默认使用了Ribbon所以不需要进行额外的配置,所以Ribbon配置好后,我们直接使用Feign就可以实现客户端负载均衡了。
@RestController
@RequestMapping("/loadBalancer/feign")
public class FeignClientController {
@Autowired
EurekaFeignClent eurekaFeignClent;
@Autowired
AnnotationFeignClient annotationFeignClient;
@Autowired
PropertiesFeignClient propertiesFeignClient;
@RequestMapping("/eureka/querySMS")
public String eurekas() {
return eurekaFeignClent.queryAll();
}
@RequestMapping("/annotation/querySMS")
public String annotation() {
return annotationFeignClient.queryAll();
}
@RequestMapping("/properties/querySMS")
public String properties() {
return propertiesFeignClient.queryAll();
}
}
@FeignClient(name = "sms-service")
interface EurekaFeignClent {
@RequestMapping(value="/sms", method = RequestMethod.GET)
public String queryAll();
}
@FeignClient(name = "annotation-sms-service")
interface AnnotationFeignClient {
@RequestMapping(value="/sms", method = RequestMethod.GET)
public String queryAll();
}
@FeignClient(name = "properties-sms-service")
interface PropertiesFeignClient {
@RequestMapping(value="/sms", method = RequestMethod.GET)
public String queryAll();
}
测试负载均衡的作用
在三种配置方式和三种使用方式的实现下,web-app-service
服务一共有9个测试接口
http://localhost:8101/loadBalancer/lbClient/eureka/querySMS
http://localhost:8101/loadBalancer/lbClient/annotation/querySMS
http://localhost:8101/loadBalancer/lbClient/properties/querySMS
http://localhost:8101/loadBalancer/restTemplate/eureka/querySMS
http://localhost:8101/loadBalancer/restTemplate/annotation/querySMS
http://localhost:8101/loadBalancer/restTemplate/properties/querySMS
http://localhost:8101/loadBalancer/feign/eureka/querySMS
http://localhost:8101/loadBalancer/feign/annotation/querySMS
http://localhost:8101/loadBalancer/feign/properties/querySMS
测试可知9个接口都可以成功返回短信。且注解和API配置的方式会随机负载到8001或者9001端口的服务。配置文件和集成Eureka方式配置的负载均衡会轮询这两个实例。