Ribbon
RestTemplate远程调用
springboot 提供的远程调用工具
类似于 HttpClient,可以发送 http 请求,并处理响应。RestTemplate简化了Rest API调用,只需要使用它的一个方法,就可以完成请求、响应、Json转换
方法:
getForObject(url, 转换的类型.class, 提交的参数)
postForObject(url, 协议体数据, 转换的类型.class)
RestTemplate 和 Dubbo 远程调用的区别:
RestTemplate:
- http调用
- 效率低
Dubbo:
- RPC调用,Java的序列化
- 效率高
之前的系统结构是浏览器直接访问后台服务
后面我们通过一个Demo项目演示 Spring Cloud 远程调用
下面我们先不使用ribbon, 单独使用RestTemplate来执行远程调用
- 新建 ribbon 项目
- pom.xml
- application.yml
- 主程序
- controller
- 启动,并访问测试
新建 sp06-ribbon 项目
pom.xml
- eureka-client 中已经包含 ribbon 依赖
- 需要添加 sp01-commons 依赖
application.yml
spring:
application:
name: ribbon
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
主程序
- 创建
RestTemplate
实例RestTemplate
是用来调用其他微服务的工具类,封装了远程调用代码,提供了一组用于远程调用的模板方法,例如:getForObject()
、postForObject()
等
package cn.tedu.sp06;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class Sp06RibbonApplication {
//创建 RestTemplate 实例,并存入 spring 容器
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Sp06RibbonApplication.class, args);
}
}
RibbonController
package cn.tedu.sp06.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
@RestController
public class RibbonController {
@Autowired
private RestTemplate rt;
@GetMapping("/item-service/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
//向指定微服务地址发送 get 请求,并获得该服务的返回结果
//{1} 占位符,用 orderId 填充
return rt.getForObject("http://localhost:8001/{1}", JsonResult.class, orderId);
}
@PostMapping("/item-service/decreaseNumber")
public JsonResult decreaseNumber(@RequestBody List<Item> items) {
//发送 post 请求
return rt.postForObject("http://localhost:8001/decreaseNumber", items, JsonResult.class);
}
Ribbon
Springcloud集成的工具,作用是负载均衡,和重试
负载均衡
Ribbon 对 RestTemplate 做了封装,增强了 RestTemplate 的功能
- 获得地址表
- 轮询一个服务的主机地址列表
- RestTemplate负责执行最终调用
添加负载均衡
- ribbon依赖
- @LoadBalanced 注解,对 RestTemplate 进行功能增强
- 修改调用地址,使用 服务id,而不是具体主机地址
添加 ribbon 起步依赖(可选)
- eureka 依赖中已经包含了 ribbon
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
RestTemplate 设置 @LoadBalanced
@LoadBalanced
负载均衡注解,会对 RestTemplate
实例进行封装,创建动态代理对象,并切入(AOP)负载均衡代码,把请求分发到集群中的服务器
package cn.tedu.sp06;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class Sp06RibbonApplication {
@LoadBalanced //负载均衡注解
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Sp06RibbonApplication.class, args);
}
}
访问路径设置为服务id
package cn.tedu.sp06.controller;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@Slf4j
public class RibbonController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/item-service/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId){
//远程调用商品服务
//http://localhost:8001/{orderId}
//{1} -- RestTemplate 定义的一种占位符格式,传递参数orderId
//return restTemplate.getForObject("http://localhost:8001/{1}",JsonResult.class,orderId);
return restTemplate.getForObject("http://item-service/{1}",JsonResult.class,orderId);//Ribbon的方式,将ip:port改为服务名称
}
@PostMapping("/item-service/decreaseNumber")
public JsonResult<?> decreaseNumber(@RequestBody List<Item> items){
return restTemplate.postForObject("http://item-service/decreaseNumber", items, JsonResult.class);
}
// -----------------------
@GetMapping("/user-service/{userId}")
public JsonResult<User> getUser(@PathVariable Integer userId){
return restTemplate.getForObject("http://user-service/{1}", JsonResult.class,userId);
}
@GetMapping("/user-service/{userId}/score")
public JsonResult<?> addScore(@PathVariable Integer userId,Integer score){
return restTemplate.getForObject("http://user-service/{1}/score?score={2}", JsonResult.class,userId,score);
}
@GetMapping("/order-service/{orderId}")
public JsonResult<Order> getOrder(@PathVariable String orderId){
return restTemplate.getForObject("http://order-service/{1}", JsonResult.class,orderId);
}
@GetMapping("/order-service/")
public JsonResult<?> addOrder(){
return restTemplate.getForObject("http://order-service/", JsonResult.class);
}
}
访问测试
- 访问测试,ribbon 会把请求分发到 8001 和 8002 两个服务端口上
http://localhost:3001/item-service/34
Ribbon 重试
一种容错机制,当调用远程服务失败,可以自动重试调用
添加重试:
1.添加 spring-retry 依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
2.配置重试参数
在yml中配置
MaxAutoRetries:单台服务器的
MaxAutoRetriesNextServer:更换服务器的次数
OkToRetryOnAllOperations=true:默认只对GET请求重试, 当设置为true时, 对POST等所有类型请求都重试
spring:
application:
name: ribbon
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
ribbon:
MaxAutoRetriesNextServer: 2
MaxAutoRetries: 1
OkToRetryOnAllOperations: true
在java代码中设置
ConnectTimeout:与远程服务建立网络连接的超时时间
ReadTimeout:连接已建立,请求已发送,等待响应的超时时间
package cn.tedu.sp06;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class Sp06RibbonApplication {
public static void main(String[] args) {
SpringApplication.run(Sp06RibbonApplication.class, args);
}
/*
创建 RestTemplate 实例,
放入spring容器
*/
@Bean
@LoadBalanced//对RestTemplate进行增强与封装,添加Ribbon负载均衡功能
public RestTemplate restTemplate(){
//设置调用超时时间,超时后认为调用失败
SimpleClientHttpRequestFactory f = new SimpleClientHttpRequestFactory();
f.setConnectTimeout(1000); //建立连接的等待时间
f.setReadTimeout(1000); //连接建立后,发送请求后。等待接收响应的时间
return new RestTemplate(f);
}
}
item-service 的 ItemController 添加延迟代码,以便测试 ribbon 的重试机制
package cn.tedu.sp02.item.controller;
import java.util.List;
import java.util.Random;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.service.ItemService;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
public class ItemController {
@Autowired
private ItemService itemService;
@Value("${server.port}")
private int port;
@GetMapping("/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId) throws Exception {
log.info("server.port="+port+", orderId="+orderId);
///--设置随机延迟
if(Math.random()<0.9) {
long t = new Random().nextInt(5000);
log.info("item-service-"+port+" - 暂停 "+t);
Thread.sleep(t);
}
///~~
List<Item> items = itemService.getItems(orderId);
return JsonResult.ok(items).msg("port="+port);
}
@PostMapping("/decreaseNumber")
public JsonResult decreaseNumber(@RequestBody List<Item> items) {
itemService.decreaseNumbers(items);
return JsonResult.ok();
}
}
访问,测试 ribbon 重试机制
- 通过 ribbon 访问 item-service,当超时,ribbon 会重试请求集群中其他服务器
http://localhost:3001/item-service/35
- ribbon的重试机制,在 feign 和 zuul 中进一步进行了封装,后续可以使用feign或zuul的重试机制
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。