RestTemplate简介
RestTemplate是Spring Resources中⼀个访问第三⽅RESTful API接⼝的⽹络请求框架。
RestTemplate的设计原则和其他的Spring Template(例如JdbcTemplate)类似,都是为了执⾏复杂
任务提供了⼀个具有默认⾏为的简单⽅法。
RestTemplate是⽤来消费REST服务的,所以RestTemplate的主要⽅法都与REST的HTTP协议的
⼀些⽅法紧密相连,例如HEAD、GET、POST、PUT、DELETE、OPTIONS等⽅法,这些⽅法在
RestTemplate类对应的⽅法为headForHeaders(),getForObject()、postForObject()、put()、delete()
等。
举例说明,在订单服务通过RestTemplate的getForObject⽅法调⽤⽀付服务,并且将调⽤结果反
序列化成Payment对象,代码如下。
@GetMapping("/payment/{id}")
public ResponseEntity<Payment> getPaymentById(@PathVariable("id") Integer
id) {
String url = "http://localhost:9001/payment/" + id;
List<ServiceInstance> serviceInstances =
discoveryClient.getInstances("cloud-payment-service");
ServiceInstance serviceInstance = serviceInstances.get(0);
url = "http://" + serviceInstance.getHost() + ":" +
serviceInstance.getPort() + "/payment/" + id;
Payment payment = restTemplate.getForObject(url, Payment.class);
return ResponseEntity.ok(payment);
}
RestTemplate⽀持常⻅的Http协议请求⽅法,例如post, get, delete等,所以⽤RestTemplate很
容易构建RESTfule API。上述案例结果返回json对象,使⽤jackson框架完成。
LoadBalancer负载均衡
负载均衡是指将负载分摊到多个执⾏单元上,常⻅的负载均衡有两种⽅式。⼀种独⽴进程单元,通
过负载均衡策略,将请求转发到不同的执⾏单元上,例如Nginx。另⼀种是将负载均衡逻辑以代码的形
式封装到服务消费者的客户端上,服务消费者客户端维护了⼀份服务提供者的信息列表,有了信息表,
通过负载均衡策略将请求分摊给多个服务提供者,从⽽达到负载均衡的⽬的。
SpringCloud原有的客户端负载均衡⽅案Ribbon已经被废弃,取⽽代之的是SpringCloud
LoadBalancer,LoadBalancer是Spring Cloud Commons的⼀个⼦项⽬,他属于上述的第⼆种
⽅式,是将负载均衡逻辑封装到客户端中,并且运⾏在客户端的进程⾥。
在Spring Cloud构件微服务系统中,LoadBalancer作为服务消费者的负载均衡器,有两种使⽤⽅
式,⼀种是和RestTemplate相结合,另⼀种是和Feign相结合,Feign已经默认集成了LoadBalancer,
关于Feign下⼀章讲解。
LoadBalancer整合RestTemplate
在⽀付微服务⼯程中进⾏如下更改。
- 配置⽂件
在application.yml配置⽂件中,使⽤spel指定端⼝,表示存在port参数使⽤port参数,不存在使⽤
默认9001端⼝, 启动⽀付服务时,可以通过指定-Dport=9000,指定⽀付服务使⽤不同端⼝启动,具体参考后⾯内容。
server:
port: ${port:9001}
- PaymentController
在提供⽀付服务时,把端⼝打印出来,⽅便查看测试效果。代码如下。
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/{id}")
public ResponseEntity<Payment> payment(@PathVariable("id") Integer id) {
Payment payment = new Payment(id, "⽀付成功,服务端⼝=" + serverPort);
return ResponseEntity.ok(payment);
}
}
- OrderApplication
在产⽣RestTemplate实例时,使⽤@LoadBalanced注解,开启负载均衡。
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
- OrderController
在OrderController中,使⽤serviceId调⽤⽀付服务,此时LoadBalancer负载均衡⽣效,从多个服
务提供者节点轮询选择⼀个使⽤。
@GetMapping("/payment/{id}")
public ResponseEntity<Payment> getPaymentById(@PathVariable("id") Integer
id) {
String url = "http://cloud-payment-service/payment/" + id;
Payment payment = restTemplate.getForObject(url, Payment.class);
return ResponseEntity.ok(payment);
}
- 启动并测试
启动两个⽀付微服务⼯程,端⼝分别是9000和9001,因为application.yml配置⽂件中使⽤
${port:9001}配置端⼝,其中9000节点启动配置如图3-1所示。
另⼀个⽀付节点,不指定-Dport,使⽤默认9001端⼝启动,这时准备了2个⽀付微服务节点。
Eureka注册效果如图3-2所示。
接下来在浏览器多次访问http://localhost:9002/order/payment/123456时,负载均衡器起了作
⽤,结果9000,9001轮流出现。
⽀付成功,服务端⼝=9000
⽀付成功,服务端⼝=9001
LoadBlancerClient简介
负载均衡的核⼼类为LoadBalancerClient,LoadBalancerClient可以获取负载均衡的服务提供者实
例信息。在OrderController增加演示代码如下。
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/test-load-balancer")
public String testLoadBalancer() {
ServiceInstance instance = loadBalancerClient.choose("cloud-paymentservice");
return instance.getHost() + ":" + instance.getPort();
}
重启⼯程,浏览器访问http://localhost:9002/order/test-load-balancer,发现浏览器轮流显示如下内
容
localhost:9000
localhost:9001
LoadBalancer源码解析
类的调⽤顺序
当时使⽤有@LoadBalanced注解的RestTemplate时,设计的类的调⽤关系如图3-3所示
关键类解析
●LoadBalancerRequestFactory: ⼀个⼯⼚, 包装⼀个为HttpRequest对象,回调对象
LoadBalancerRequest。
●LoadBalancerClient: ⽤于根据 serviceId 选取⼀个 ServiceInstance, 执⾏从
LoadBalancerRequestFactory 获得的那个回调。
●LoadBalancerInterceptor:RestTemplate 的拦截器, 拦截后调⽤ LoadBalancerClient 修改
HttpRequest 对象(主要是 url), 且传⼊调⽤ LoadBalancerRequestFactory ⽣成的回调给
LoadBalancerClient。
● RestTemplateCustomizer:为 restTemplate 加上⼀个拦截器(也可以⼲点别的, 默认就这⼀个⽤
处) 。
● SmartInitializingSingleton:容器初始化是,调⽤ RestTemplateCustomizer 为容器中所有加了。
@LoadBalanced 的 RestTemplate 加上⼀个拦截器。
●ReactiveLoadBalancer:定义了 choose ⽅法, 即如何选取⼀个 ServiceInstance, 如轮播, 随机。
源码部分
- LoadBalancerInterceptor
RestTemplate 的拦截器, 拦截后调⽤ LoadBalancerClient 修改 HttpRequest 对象(主要是 url),
且传⼊调⽤ LoadBalancerRequestFactory ⽣成的回调给 LoadBalancerClient。
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[]
body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid
hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
- BlockingLoadBalancerClient
⽤于根据 serviceId 选取⼀个 ServiceInstance, 执⾏从 LoadBalancerRequestFactory 获得的那
个回调,发送HTTP请求,远程调⽤REST ful API。代码如下所示。
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
String hint = getHint(serviceId);
LoadBalancerRequestAdapter<T, DefaultRequestContext> lbRequest = new
LoadBalancerRequestAdapter<>(request,
new DefaultRequestContext(request, hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors =
getSupportedLifecycleProcessors(serviceId);
supportedLifecycleProcessors.forEach(lifecycle ->
lifecycle.onStart(lbRequest));
ServiceInstance serviceInstance = choose(serviceId, lbRequest);
if (serviceInstance == null) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.DISCARD,
lbRequest, new EmptyResponse())));
throw new IllegalStateException("No instances available for " +
serviceId);
}
return execute(serviceId, serviceInstance, lbRequest);
}
choose⽅法,调⽤RoundRobinLoadBalancer的choose⽅法,以轮播⽅式获取⼀个
serviceInstance。代码如下所示。
@Override
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer =
loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
Response<ServiceInstance> loadBalancerResponse =
Mono.from(loadBalancer.choose(request)).block();
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
- RoundRobinLoadBalancer
RoundRobinLoadBalancer的choose⽅法,以轮播⽅式获取⼀个serviceInstance。代码如下所
示。
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier =
serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier,
serviceInstances));
}
Spirng Cloud OpenFeign
Feign是⼀个声明式的HTTP客户端组件,它旨在是编写Http客户端变得更加容易。OpenFeign添加
了对于Spring MVC注解的⽀持,同时集成了Spring Cloud LoadBalancer和Spring Cloud
CircuitBreaker,在使⽤Feign时,提供负载均衡和熔断降级的功能。
配置步骤
在订单⼯程⼯程的pom.xml中添加如下依赖
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 开启Feign功能
使⽤@EnableFeignClients开启Feign功能
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- 创建Feign客户端
在注解@FeignClient注解中,“cloud-payment-service”是服务名,使⽤这个名字来从Eureka服务
列表中得到相应的服务,来创建LoadBalancer客户端,也可以使⽤url属性,指定服务的URL。
@FeignClient(value = "cloud-payment-service")
public interface PaymentClient {
@GetMapping("/payment/{id}")
public Payment payment(@PathVariable("id") Integer id);
}
- OrderController
在OrderController中使⽤FeignClient访问⽀付服务,代码如下。
@Autowired
private PaymentClient paymentClient;
@GetMapping("/feign/payment/{id}")
public ResponseEntity<Payment> getPaymentByFeign(@PathVariable("id")
Integer id) {
Payment payment = paymentClient.payment(id);
return ResponseEntity.ok(payment);
}
- 启动并测试
分别启动⽀付服务9000端⼝,9001端⼝,订单服务,访问
http://localhost:9002/order/feign/payment/123,执⾏效果如图3-4所示。