请先阅读之前的内容:
上一步我们创建了服务提供者 eureka-client
来提供加法服务,并且启动了两个实例 Zones。
下一步,我们去消费服务提供者的接口。在这里我们建立一个项目:
-
eureka-consumer
:服务消费者
在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于 HTTP Restful 的。Spring Cloud 有两种服务调用方式,一种是 Ribbon + RestTemplate,另一种是 Feign。
eureka-consumer 服务消费者
可以通过如下的 Spring Assistant 插件来创建项目,添加 Eureka Discovery
和 Web
,Actuator
作为依赖。
从 pom.xml
中可以看出,导入了如下的依赖:
- 2.0.3.RELEASE 版本的 Spring Boot
- Finchley.RELEASE 版本的 Spring Cloud
spring-cloud-starter-netflix-eureka-client
spring-boot-starter-web
spring-boot-starter-actuator
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
随后在启动程序中通过 @EnableDiscoveryClient
来激活 Eureka 中的 DiscoveryClient
实现:
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaConsumerApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(EurekaConsumerApplication.class, args);
}
}
随后在 application.properties
:指定 Eureka 注册中心的地址。消费者自己的端口是 3001。
spring.application.name=eureka-consumer
server.port=3001
eureka.client.serviceUrl.defaultZone=http://localhost:1234/eureka/
随后,我们创建一个 ConsumerController
类来消费服务:
@RestController
public class ConsumerController {
private final static Logger logger = LoggerFactory.getLogger(ConsumerController.class);
@Autowired
LoadBalancerClient loadBalancerClient;
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@GetMapping("/consumer")
public Integer consumer() {
// 获取到的所有服务清单
String services = "Services: " + discoveryClient.getServices();
logger.info(services);
// 调用加法服务
ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client");
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/add";
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(url)
// Add query parameter
.queryParam("operand1", 1)
.queryParam("operand2", 2);
logger.info(builder.toUriString());
return restTemplate.getForObject(builder.toUriString(), Integer.class);
}
}
org.springframework.cloud.client.discovery.DiscoveryClient
接口是 Spring Cloud 对服务治理做的一层抽象,所以可以屏蔽 Eureka 和 Consul 服务治理的实现细节,我们的程序不需要做任何改变,只需要引入不同的服务治理依赖,并配置相关的配置属性就能轻松的将微服务纳入 Spring Cloud 的各个服务治理框架中。
可以看到这里,我们注入了 LoadBalancerClient
和 RestTemplate
,先通过 loadBalancerClient
的 choose
函数来 负载均衡 的选出一个 eureka-client
的服务实例,这个服务实例的基本信息存储在 ServiceInstance
中,然后通过这些对象中的信息拼接出访问 /add
接口的详细地址,最后再利用 RestTemplate
对象实现对服务提供者接口的调用。
最后通过 mvn spring-boot:run
命令启动项目,启动完成后,可以通过 http://127.0.0.1:3001/consumer 消费服务。
刷新页面多次,从日志中可以看出,依次调用 2001 和 2002 两个端口对应的服务提供者,这是通过 LoadBalancerClient
实现的负载均衡。
通过 Ribbon 来消费服务
Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具。它是一个基于 HTTP 和 TCP 的客户端负载均衡器。它可以通过在客户端中配置 ribbonServerList
来设置服务端列表去轮询访问以达到均衡负载的作用。
- 当 Ribbon 与 Eureka 联合使用时,
ribbonServerList
会被DiscoveryEnabledNIWSServerList
重写,扩展成从 Eureka 服务注册中心中获取服务实例列表。同时它也会用NIWSDiscoveryPing
来取代IPing
,它将职责委托给 Eureka 来确定服务端是否已经启动。 - 而当 Ribbon 与 Consul 联合使用时,
ribbonServerList
会被ConsulServerList
来扩展成从 Consul 获取服务实例列表。同时由ConsulPing
来作为IPing
接口的实现。
首先在 pom.xml
中添加 Ribbon
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
随后在启动程序中通过 @LoadBalanced
来注解 RestTemplate
:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
由于 RestTemplate
被 @LoadBalanced
修饰,所以它具备客户端负载均衡的能力,当请求真正发起的时候,URL 中的服务名会根据负载均衡策略从服务清单中挑选出一个实例来进行访问。
随后,我们修改 ConsumerController
类来消费服务:
@GetMapping("/consumer")
public Integer consumer() {
// 获取到的所有服务清单
String services = "Services: " + discoveryClient.getServices();
logger.info(services);
// 调用加法服务
String url = "http://eureka-client/add";
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(url)
// Add query parameter
.queryParam("operand1", 1)
.queryParam("operand2", 2);
logger.info(builder.toUriString());
return restTemplate.getForObject(builder.toUriString(), Integer.class);
}
这里请求的 host 位置并没有使用一个具体的 IP 地址和端口的形式,而是采用了服务名的方式组成。
Spring Cloud Ribbon 有一个拦截器,它能够在这里进行实际调用的时候,自动的去选取服务实例,并将实际要请求的 IP 地址和端口替换这里的服务名,从而完成服务接口的调用。
通过 Feign 来消费服务
Spring Cloud Feign 是一套基于 Netflix Feign 实现的声明式服务调用客户端。它使得编写 Web 服务客户端变得更加简单。
我们只需要通过创建接口并用注解来配置它既可完成对 Web 服务接口的绑定。它具备可插拔的注解支持,包括 Feign 注解、JAX-RS 注解。它也支持可插拔的编码器和解码器。
Spring Cloud Feign 还扩展了对 Spring MVC 注解的支持,同时还整合了 Ribbon 和 Eureka 来提供均衡负载的 HTTP 客户端实现。
首先在 pom.xml
中添加 Ribbon
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
随后在启动程序中通过 @EnableFeignClients
注解开启扫描 Spring Cloud Feign 客户端的功能。
创建一个 Feign 的客户端接口定义。使用 @FeignClient
注解来指定这个接口所要调用的服务名称,接口中定义的各个函数使用 Spring MVC 的注解就可以来绑定服务提供方的 REST 接口:
@FeignClient("eureka-client")
public interface CalculatorClient {
@GetMapping("/add")
Integer add(@RequestParam Integer operand1, @RequestParam Integer operand2);
}
随后,我们修改 ConsumerController
类来消费服务:
@GetMapping("/consumer")
public Integer consumer() {
// 获取到的所有服务清单
String services = "Services: " + discoveryClient.getServices();
logger.info(services);
// 调用加法服务
return calculatorClient.add(1, 2);
}
Ribbon的饥饿加载(eager-load)模式
引用:http://blog.didispace.com/spring-cloud-tips-ribbon-eager/
有时候会发现这样一个问题:我们服务消费方调用服务提供方接口的时候,第一次请求经常会超时,而之后的调用就没有问题了。
造成第一次服务调用出现失败的原因主要是 Ribbon 进行客户端负载均衡的 Client 并不是在服务启动的时候就初始化好的,而是在调用的时候才会去创建相应的 Client,所以第一次调用的耗时不仅仅包含发送 HTTP 请求的时间,还包含了创建 RibbonClient 的时间,这样一来如果创建时间速度较慢,同时设置的超时时间又比较短的话,很容易就会出现上面所描述的显现。
而 Feign 的实现基于 Ribbon,所以它也有一样的问题。
我们可以通过下面的配置来开启 Ribbon 的饥饿加载模式,这样 RibbonClient 会提前创建,而不是在第一次调用的时候创建。
ribbon.eager-load.enabled=true
ribbon.eager-load.clients=eureka-client
引用:
程序猿DD Spring Cloud基础教程
Spring Cloud构建微服务架构:服务消费(基础)【Dalston版】
Spring Cloud构建微服务架构:服务消费(Ribbon)【Dalston版】
Spring Cloud构建微服务架构:服务消费(Feign)【Dalston版】】
Spring Cloud Dalston中文文档