服务的调用,主要是服务调用方请求注册中心,注册中心返回服务提供方实例。 服务调用方通过服务提供方实例获取服务的URL地址,再发送HTTP调用服务。调用方发现服务有很多方式。比如:
- 使用
DiscoveryClient
。(服务发现) - 使用
EurekaClient
。(服务发现) - 使用
LoadBalancerClient
。(服务发现) ,这个是Ribbon
的底层API。Ribbon
包含了LoadBalancerClient
。 - 使用
Ribbon
。Ribbon
是client(调用方)端的负载均衡。官方文档,中文文档 - 使用
openfeign
。Feign
包含了Ribbon
(或者说依赖Ribbon
)。(推荐使用这个)官方文档
下面通过示例说明:
1.1. 一、服务提供方代码。
1.1.1. 1、依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
1.1.2. 2、服务提供方application.yml配置如下:
server:
port: 8000
spring:
application:
name: user-provider
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/user-provider?serverTimezone=GMT%2b8
username: root
password: 123456
eureka:
client:
serviceUrl:
# 注册中心的地址
defaultZone: http://localhost:9999/eureka/
instance:
preferIpAddress: true
instanceId: ${spring.cloud.client.ip-address}:${server.port}
简单配置了像注册中心注册服务。
1.1.3. 3、服务提供方就只提供一个/test/add接口。
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/add")
public int add(int a,int b) {
return a + b;
}
}
1.2. 二、服务调用方
1.2.1. 2.1、使用 DiscoveryClient。
排除掉jersey.
官方文档
默认情况下,EurekaClient 使用 Jersey 进行 HTTP 通信。如果您希望避免来自 Jersey 的依赖项,则可以将其从依赖项中排除。
Spring Cloud 默认会使用 Spring RestTemplate请求工具。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
</exclusion>
</exclusions>
</dependency>
配置RestTemplate
。
//之前的版本需要 @EnableDiscoveryClient
@SpringBootApplication
public class UserConsumerApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(UserConsumerApplication.class, args);
}
}
调用服务。
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
public String serviceUrl() {
//user-provider 是注册到eureka的服务提供端的名字。
//获取服务的列表。
List<ServiceInstance> list = discoveryClient.getInstances("user-provider");
if (list != null && list.size() > 0 ) {
//获取服务。返回 http://192.168.32.236:8000
return String.valueOf(list.get(0).getUri());
}
return null;
}
@GetMapping("/callAdd")
public int callAdd() {
String url = serviceUrl() +"/test/add?a=3&b=6";
return restTemplate.getForObject(url, Integer.class);
}
}
访问 http://localhost:9000/test/callAdd
就可以看到结果了。
调用方使用DiscoveryClient
获取到所有的user-provider
服务的实例,然后选择第1个调用。
1.2.2. 2.2、使用 EurekaClient。
使用EurekaClient
和前面的配置一样。
调用服务。
调用方可以使用EurekaClient
作为发现服务的工具。
详细可以查看官方文档
@RestController
@RequestMapping("/test")
public class TestEurekaClientController {
@Qualifier("eurekaClient")
@Autowired
private EurekaClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
public String serviceUrl() {
InstanceInfo instance = discoveryClient.getNextServerFromEureka("user-provider", false);
return instance.getHomePageUrl();
}
@GetMapping("/callAdd2")
public int callAdd() {
String url = serviceUrl() +"/test/add?a=3&b=6";
return restTemplate.getForObject(url, Integer.class);
}
}
访问http://localhost:9000/test/callAdd2
就可以看到结果了。
1.2.3. 2.3、使用 LoadBalancerClient。
这是一个负载均衡客户端的抽象定义。官方文档
调用服务。
@RestController
@RequestMapping("/test")
public class TestLoadBalancerClientController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
public String serviceUrl() {
ServiceInstance instance = loadBalancerClient.choose("user-provider");
return String.valueOf(instance.getUri());
}
@GetMapping("/callAdd3")
public int callAdd() {
String url = serviceUrl() +"/test/add?a=3&b=6";
return restTemplate.getForObject(url, Integer.class);
}
}
通过LoadBalancerClient
的choose函数来负载均衡的选出一个user-provider
服务实例,
再拼接请求地址,最后使用RestTemplate
发送请求。
1.2.4. 2.4、使用 Ribbon。
官方文档
中文文档Ribbon
是一个client端的负载均衡器,可让您对HTTP和TCP客户端的行为进行大量控制。Feign已经使用了Ribbon,因此,如果使用@FeignClient
,也就包含了Ribbon。Ribbon
是一个client端的负载均衡。client端维护并且缓存了一个服务列表,并定期从Eureka注册中心同步最新的服务列表。
添加依赖。
要使用Ribbon,需要添加依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
给RestTemplate
加@LoadBalanced
。
客户端的负载均衡。底层就是把LoadBalancerClient
配置给RestTemplate
。LoadBalancerClient
做负载均衡选出调用哪个服务,RestTemplate
发送请求。
//之前的版本需要 @EnableDiscoveryClient
@SpringBootApplication
public class UserConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(UserConsumerApplication.class, args);
}
}
注意:加上@LoadBalanced
之后,前面请求路径/test/callAdd
、/test/callAdd2
、/test/callAdd3
不会被初始化了。请求会报错(路径不存在)。
调用。
@RestController
@RequestMapping("/test")
public class TestRibbonController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/callAdd4")
public int callAdd() {
String url = "http://user-provider/test/add?a=3&b=6";
return restTemplate.getForObject(url, Integer.class);
}
}
调用的时候直接使用user-provider
服务名。不需要组装ip和端口了。
因为Spring Cloud Ribbon有一个拦截器,它能够在进行实际调用的时候,自动的去选取服务实例,并将实际要请求的IP地址和端口替换这里的服务名,从而完成服务接口的调用。
Ribbon
自定义负载均衡策略。(一般不配置)
默认情况下, Ribbon
使用轮询的方式选择服务器,给client端调用。但是这个负载均衡策略是可以自定义调整的。
官方文档的16.4 Customizing the Ribbon Client by Setting Properties
就说明了这个情况。配置如下:
user-provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
上面的配置表示,在选择user-provider
服务器的时候,以响应时间作为权重,选择服务器。NFLoadBalancerRuleClassName
配置的是选择服务器的规则,要设置为com.netflix.loadbalancer.IRule
的实现类。
spring-cloud内置了很多实现类。对应不同的策略。一般不去配置,使用默认的策略就行了。
1.2.5. 2.5、使用 OpenFeign。
OpenFeign
底层用Ribbon
(或者说依赖Ribbon
)。Ribbon
的配置对OpenFeign
都生效。
OpenFeign是在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。官方文档
添加依赖。
<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-openfeign</artifactId>
</dependency>
配置application.yml
。
server:
port: 9001
spring:
application:
name: user-consumer-feign
# datasource:
# driverClassName: com.mysql.jdbc.Driver
# url: jdbc:mysql://localhost:3306/user-consumer?serverTimezone=GMT%2b8
# username: root
# password: 123456
eureka:
client:
serviceUrl:
# 注册中心的地址
defaultZone: http://localhost:9999/eureka/
instance:
preferIpAddress: true
instanceId: ${spring.cloud.client.ip-address}:${server.port}
#user-provider:
# ribbon:
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
feign:
client:
config:
default:
# 远程调用超时时间。默认是1秒。我改为15秒
readTimeout: 15000
需要注意配置readTimeout
,这个远程调用超时时间默认是1秒,如果服务提供方响应时间超过1秒,client方就会报错。所以这里设置为15秒给服务提供方更多时间响应。
启用OpenFeign
使用@EnableFeignClients
启用OpenFeign
。
@EnableFeignClients
@SpringBootApplication
public class UserConsumerFeignApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerFeignApplication.class, args);
}
}
声明服务端的接口。
/**
*调用user-provider 服务。
*/
@FeignClient("user-provider")
public interface UserProviderClient {
// 请求/test/add 接口
@GetMapping("/test/add")
int add(@RequestParam(name = "a") int a, @RequestParam(name = "b") int b);
}
使用@FeignClient
声明接口类是要调用外部服务user-provider
的。使用@GetMapping("/test/add")
来表示要调用的接口路径。
调用。
@RestController
@RequestMapping("/test")
public class TestOpenFeignController {
@Autowired
private UserProviderClient userProviderClient;
@GetMapping("/openfeign/callAdd")
public int callAdd() {
return userProviderClient.add(3,8);
}
}
直接注入连接端UserProviderClient
调用服务即可。访问http://localhost:9001/test/openfeign/callAdd
可以看到结果。OpenFeign
有点像RPC调用,实际上底层是HTTP调用。