技术背景
上一篇教程中,我们利用Consul注册中心,实现了服务的注册和发现功能,这一篇我们来聊聊服务的调用。单体应用中,代码可以直接依赖,在代码中直接调用即可,但在微服务架构是分布式架构,服务都运行在各自的进程之中,甚至部署在不同的主机和不同的地区。这个时候就需要相关的远程调用技术了。
Spring Cloud体系里应用比较广泛的服务调用方式有两种:
1. 使用 RestTemplate 进行服务调用,可以通过 Ribbon 注解 RestTemplate 模板,使其拥有负载均衡的功能。
2. 使用 Feign 进行声明式服务调用,声明之后就像调用本地方法一样,Feign 默认使用 Ribbon实现负载均衡。
两种方式都可以实现服务之间的调用,可根据情况选择使用,下面我们分别用实现案例来进行讲解。
服务提供者
新建项目
新建一个项目 kitty-producer,添加以下依赖。
Swagger:API文档。
Consul :注册中心。
Spring Boot Admin:服务监控。
pom.xml
复制代码
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.0.4.RELEASE
com.louis
kitty-producer
${project.version}
jar
kitty-producer
kitty-producer
UTF-8
UTF-8
1.0.0
1.8
2.8.0
1.3.2
1.1.10
2.0.0
Finchley.RELEASE
org.springframework.boot
spring-boot-starter-web
io.springfox
springfox-swagger2
${swagger.version}
io.springfox
springfox-swagger-ui
${swagger.version}
de.codecentric
spring-boot-admin-starter-client
${spring.boot.admin.version}
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
复制代码
配置文件
在配置文件添加内容如下,将服务注册到注册中心并添加服务监控相关配置。
application.yml
复制代码
server:
port: 8003
spring:
application:
name: kitty-producer
cloud:
consul:
host: localhost
port: 8500
discovery:
serviceName: ${spring.application.name} # 注册到consul的服务名称
boot:
admin:
client:
url: "http://localhost:8000"
# 开放健康检查接口
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: ALWAYS
复制代码
启动类
修改启动器类,添加 @EnableDiscoveryClient 注解,开启服务发现支持。
KittyProducerApplication.java
复制代码
package com.louis.kitty.producer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class KittyProducerApplication {
public static void main(String[] args) {
SpringApplication.run(KittyProducerApplication.class, args);
}
}
复制代码
添加服务
新建一个 HelloController,提供一个 hello 接口, 返回字符串信息。
复制代码
package com.louis.kitty.producer.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "hello kitty !";
}
}
复制代码
为了模拟均衡负载,复制一份上面的项目,重命名为 kitty-producer2 ,修改对应的端口为 8004,修改 hello 方法的返回值为:"hello kitty 2!"。
依次启动注册中心、服务监控和两个服务提供者,启动成功之后刷新Consul管理界面,发现我们注册的kitty-producer服务,并有2个节点实例。
访问: http://localhost:8500, 查看两个服务提供者已经注册到注册中心。
访问: http://localhost:8000, 查看两个服务提供者已经成功显示在监控列表中。
访问 http://localhost:8003/hello,返回结果如下。
访问 http://localhost:8004/hello,返回结果如下。
服务消费者
新建项目
新建一个项目 kitty-producer,添加以下依赖。
Swagger:API文档。
Consul :注册中心。
Spring Boot Admin:服务监控。
pom.xml
复制代码
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.0.4.RELEASE
com.louis
kitty-consumer
${project.version}
jar
kitty-consumer
kitty-consumer
UTF-8
UTF-8
1.0.0
1.8
Finchley.RELEASE
org.springframework.boot
spring-boot-starter-web
io.springfox
springfox-swagger2
${swagger.version}
io.springfox
springfox-swagger-ui
${swagger.version}
de.codecentric
spring-boot-admin-starter-client
${spring.boot.admin.version}
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
复制代码
添加配置
修改配置文件如下。
application.yml
复制代码
server:
port: 8005
spring:
application:
name: kitty-consumer
cloud:
consul:
host: localhost
port: 8500
discovery:
serviceName: ${spring.application.name} # 注册到consul的服务名称
boot:
admin:
client:
url: "http://localhost:8000"
# 开放健康检查接口
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: ALWAYS
复制代码
启动类
修改启动器类,添加 @EnableDiscoveryClient 注解,开启服务发现支持。
KittyConsumerApplication.java
复制代码
package com.louis.kitty.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class KittyConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(KittyConsumerApplication.class, args);
}
}
复制代码
服务消费者
添加消费服务测试类,添加两个接口,一个查询所有我们注册的服务,另一个从我们注册的服务中选取一个服务,采用轮询的方式。
ServiceController.java
复制代码
package com.louis.kitty.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ServiceController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private DiscoveryClient discoveryClient;
/**
* 获取所有服务
*/
@RequestMapping("/services")
public Object services() {
return discoveryClient.getInstances("kitty-producer");
}
/**
* 从所有服务中选择一个服务(轮询)
*/
@RequestMapping("/discover")
public Object discover() {
return loadBalancerClient.choose("kitty-producer").getUri().toString();
}
}
复制代码
添加完成之后,启动项目,访问:http://localhost:8500,服务消费者已经成功注册到注册中心。
访问:http://localhost:8000,服务消费者已经成功显示在监控列表中。
访问 http://localhost:8005/services,返回两个服务,分别是我们注册的8003和8004。
复制代码
[{
"serviceId": "kitty-producer",
"host": "GG20J1G2E.logon.ds.ge.com",
"port": 8003,
"secure": false,
"metadata": {
"secure": "false"
},
"uri": "http://GG20J1G2E.logon.ds.ge.com:8003",
"scheme": null
}, {
"serviceId": "kitty-producer",
"host": "GG20J1G2E.logon.ds.ge.com",
"port": 8004,
"secure": false,
"metadata": {
"secure": "false"
},
"uri": "http://GG20J1G2E.logon.ds.ge.com:8004",
"scheme": null
}]
复制代码
反复访问 http://localhost:8005/discover,结果交替返回服务8003和8004,因为默认的负载均衡器是采用轮询的方式。
8003 和 8004 两个服务会交替出现,从而实现了获取服务端地址的均衡负载。
大多数情况下我们希望使用均衡负载的形式去获取服务端提供的服务,因此使用第二种方法来模拟调用服务端提供的 hello 方法。
创建 CallHelloController.java
复制代码
package com.louis.kitty.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class CallHelloController {
@Autowired
private LoadBalancerClient loadBalancer;
@RequestMapping("/call")
public String call() {
ServiceInstance serviceInstance = loadBalancer.choose("kitty-producer");
System.out.println("服务地址:" + serviceInstance.getUri());
System.out.println("服务名称:" + serviceInstance.getServiceId());
String callServiceResult = new RestTemplate().getForObject(serviceInstance.getUri().toString() + "/hello", String.class);
System.out.println(callServiceResult);
return callServiceResult;
}
}
复制代码
使用 RestTemplate 进行远程调用。添加完之后重启 kitty-consumer 项目。
在浏览器中访问地址: http://localhost:8005/call 依次往复返回结果如下:
负载均衡器(Ribbon)
在上面的教程中,我们是这样调用服务的,先通过 LoadBalancerClient 选取出对应的服务,然后使用 RestTemplate 进行远程调用。
LoadBalancerClient 就是负载均衡器,默认使用的是 Ribbon 的实现 RibbonLoadBalancerClient,采用的负载均衡策略是轮询。
1. 查找服务,通过 LoadBalancer 查询服务。
ServiceInstance serviceInstance = loadBalancer.choose("kitty-producer");
2.调用服务,通过 RestTemplate 远程调用服务。
String callServiceResult = new RestTemplate().getForObject(serviceInstance.getUri().toString() + "/hello", String.class);
这样就完成了一个简单的服务调用和负载均衡。接下来我们说说Ribbon。
Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为。为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多负载均衡算法,例如轮询、随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法。
ribbon内置负载均衡策略:
策略名 策略声明 策略描述 实现说明
BestAvailab