目录
Java版本:1.8
Spring版本:5.1.8.RELEASE
Spring Boot版本:2.1.6.RELEASE
Spring Cloud版本:Greenwich.SR1
代码示例
通过服务发现,客户端可以获取某个服务的所有可用服务地址,此时我们需要客户端负载均衡,以便将客户端请求按照特定方式分摊到各个服务地址上
稍微改造一下之前的Test-Client(代码参考https://blog.csdn.net/a19881029/article/details/100585120),由于spring-cloud-starter-netflix-eureka-client中包含Ribbon相关依赖(见如下pom依赖)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-eureka</artifactId>
</dependency>
因此不需要修改pom文件
修改RestTemplateConfig在普通RestTemplate的基础上增加负载均衡RestTemplate
package com.sean;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* Created by seanzou on 2019/9/10.
*/
@Configuration
public class RestTemplateConfig {
@Bean(name = "normalTemplate")
public RestTemplate normalTemplate(ClientHttpRequestFactory clientHttpRequestFactory){
return new RestTemplate(clientHttpRequestFactory);
}
@Bean(name = "loadBalanceTemplate")
@LoadBalanced
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory){
return new RestTemplate(clientHttpRequestFactory);
}
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);//ms
factory.setConnectTimeout(5000);//ms
return factory;
}
}
修改TestService并使用LoadBalancerClient替换之前的DiscoveryClient,通过LoadBalancerClient+普通RestTemplate或者负载均衡RestTemplate两种方式请求服务
package com.sean;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
/**
* Created by seanzou on 2019/9/24.
*/
public class TestLoadBalancerRequest implements LoadBalancerRequest<String> {
private String path;
private RestTemplate restTemplate;
@Override
public String apply(ServiceInstance instance) throws Exception {
String host = instance.getHost();
Integer port = instance.getPort();
ResponseEntity responseEntity = restTemplate.getForEntity("http://" + host + ":" + port + path, String.class);
if(responseEntity != null && responseEntity.getStatusCode() != null &&
responseEntity.getStatusCode().is2xxSuccessful()){
String resp = (String)responseEntity.getBody();
return resp;
}
return null;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public RestTemplate getRestTemplate() {
return restTemplate;
}
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
}
package com.sean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
/**
* Created by seanzou on 2019/7/10.
*/
@Component
public class TestService {
@Autowired
private RestTemplate normalTemplate;
@Autowired
private RestTemplate loadBalanceTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
public void test() {
System.out.println("start loadBalancerClient querying");
TestLoadBalancerRequest req = new TestLoadBalancerRequest();
req.setPath("/name");
req.setRestTemplate(normalTemplate);
for(int i = 0 ; i < 10 ; i++){
try {
String resp = loadBalancerClient.execute("test-service", req);
System.out.println(resp);
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
System.out.println("start restTemplate querying");
for(int i = 0 ; i < 10 ; i++){
ResponseEntity responseEntity = loadBalanceTemplate.getForEntity("http://test-service/name", String.class);
if(responseEntity != null && responseEntity.getStatusCode() != null &&
responseEntity.getStatusCode().is2xxSuccessful()){
String resp = (String)responseEntity.getBody();
System.out.println(resp);
}
}
}
}
启动Test-Client后访问http://localhost:8090/test以调用测试代码,后端日志输出如下
start loadBalancerClient querying
sean
sean two
sean
sean two
sean
sean two
sean
sean two
sean
sean two
start restTemplate querying
sean
sean two
sean
sean two
sean
sean two
sean
sean two
sean
sean two
LB实现原理
我们从pom文件开始
spring-cloud-starter-netflix-eureka-client依赖spring-cloud-starter-netflix-ribbon
spring-cloud-starter-netflix-ribbon依赖spring-cloud-netflix-ribbon
spring-cloud-netflix-ribbon的代码结构如下
spring.factories配置中指定了AutoConfiguration类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
RibbonAutoConfiguration类十分关键,Ribbon自动创建对象以及自动加载配置的逻辑都在这个类中
在上面的示例中我们使用了两种调用方式,分别是LoadBalancerClient+普通RestTemplate或者负载均衡RestTemplate两种方式请求服务,下面我们兵分两路
负载均衡RestTemplate实现原理
RibbonAutoConfiguration类中有如下注解
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConf