RestTemplete请求模板类
微服务中消费者去调用服务提供者提供的服务的时候,使用了一个及其方便的对象即是RestTemplate,我们通常用RestTemplate中最简单的一个功能getForObject或者postForEntity发起了一个get请求去调用服务端的数据,同时还通过配置@LoadBalanced注解开启客户端负载均衡。我们就来的看一下RestTemplate中几种常见请求方法的使用,在日常操作中,基于Rest的方式通常是四种情况,它们分别是:
GET 请求 –查询数据
get请求有两种方式:
1、getForEntity 2、getForObject
该方法返回一个 ResponseEntity对象,ResponseEntity是 Spring 对 HTTP 请求响应的封装,包括了几个重要的元素,比如响应码、contentType、 contentLength、响应消息体等;
消费者:
@Autowired
private RestTemplate restTemplate;
@Value("${service.name}")
private String serviceName;
/**
* Get请求,无参数,返回User
* @return
*/
@RequestMapping("/web/user")
public User user(){
//业务逻辑判断处理省略
ResponseEntity<User> userResponseEntity = restTemplate.getForEntity(serviceName + "/service/user", User.class);
HttpStatus statusCode = userResponseEntity.getStatusCode();
int statusCodeValue = userResponseEntity.getStatusCodeValue();
HttpHeaders headers = userResponseEntity.getHeaders();
User body = userResponseEntity.getBody();
System.out.println(statusCode);
System.out.println(statusCodeValue);
System.out.println(headers);
System.out.println(body);
return restTemplate.getForEntity(serviceName + "/service/user", User.class).getBody();
}
/**
* Get请求,有参数,返回User
* @return
*/
@RequestMapping("/web/getUser")
public User getUser(){
//业务逻辑判断处理省略
//定义数组
String[] userArry = {"101","zhangsan","18821611462"};
//定义map
Map<String, Object> paramMap = new ConcurrentHashMap<>();
paramMap.put("id",102);
paramMap.put("name","lisi");
paramMap.put("phone","18992384582");
//调用SpringCloud服务提供者提供的服务
//参数为数组
ResponseEntity<User> userResponseEntity1 = restTemplate.getForEntity(serviceName + "/service/getUser?id={0}&name={1}&phone={2}", User.class, userArry);
//参数为map
ResponseEntity<User> userResponseEntity2 = restTemplate.getForEntity(serviceName + "/service/getUser?id={id}&name={name}&phone={phone}", User.class, paramMap);
//getForObject
User user1 = restTemplate.getForObject(serviceName + "/service/getUser?id={0}&name={1}&phone={2}", User.class, userArry);
System.out.println(user1.getId() + "------" + user1.getName() + "------" + user1.getPhone());
User user2 = restTemplate.getForObject(serviceName + "/service/getUser?id={id}&name={name}&phone={phone}", User.class, paramMap);
System.out.println(user2.getId() + "======" + user2.getName() + "======" + user2.getPhone());
return restTemplate.getForEntity(serviceName + "/service/getUser?id={id}&name={name}&phone={phone}", User.class, paramMap).getBody();
}
提供者:
@RequestMapping("/service/user")
public User user(){
//进行业务处理(省略)
System.out.println("服务提供者1-->/service/user");
User user = new User();
user.setId(10001);
user.setName("providernj");
user.setPhone("17791412622");
return user;
}
@RequestMapping("/service/getUser")
public User getUser(@RequestParam(value = "id") Integer id,
@RequestParam(value = "name") String name,
@RequestParam(value = "phone") String phone){
//进行业务处理(省略)
System.out.println("服务提供者1-->/service/getUser");
User user = new User();
user.setId(id);
user.setName(name);
user.setPhone(phone);
return user;
}
POST 请求 –添加数据
四种传参方式:定义数组、LinkedMultiValueMap、传递对象、传递JSON格式
消费者:
@Autowired
private RestTemplate restTemplate;
@Value("${service.name}")
private String serviceName;
/**
* POST请求,有参数,返回User -addUser方法
* @return
*/
@RequestMapping("/web/addUser")
public User addUser(){
//业务逻辑判断处理省略
//定义数组
String[] userArry = {"2001","nj","200120012001"};
//要传的表单信息,参数数据(很坑人的),用普通HashMap包装参数,传不过去
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("id",2002);
paramMap.add("name","wx");
paramMap.add("phone","200220022002");
//调用postForEntity
ResponseEntity<User> userResponseEntity = restTemplate.postForEntity(serviceName + "/service/addUser", paramMap, User.class);
HttpStatus statusCode = userResponseEntity.getStatusCode();
int statusCodeValue = userResponseEntity.getStatusCodeValue();
HttpHeaders headers = userResponseEntity.getHeaders();
User user = userResponseEntity.getBody();
System.out.println(statusCode);
System.out.println(statusCodeValue);
System.out.println(headers);
System.out.println(user.getId() + "------" + user.getName() + "------" + user.getPhone());
//调用postForObject
User user2 = restTemplate.postForObject(serviceName + "/service/addUser", paramMap, User.class);
System.out.println(user2.getId() + "======" + user2.getName() + "======" + user2.getPhone());
//传递User对象
User user3 = new User();
user3.setId(2003);
user3.setName("nxj");
user3.setPhone("200320032003");
User user4 = restTemplate.postForObject(serviceName + "/service/addUser2?token={token}&encode={encode}", user3, User.class,"123456","utf-8");
System.out.println(user4.getId() + "******" + user4.getName() + "******" + user4.getPhone());
//传递JSON值调用postForObject
String userJSON = "{\"id\" : 1088, \"name\" : \"殷素素\", \"phone\" : \"13900000000\"}";
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(userJSON, headers);
User user5 = restTemplate.postForObject(serviceName + "/service/addUser3", entity, User.class);
System.out.println(user5.getId() + "-*-*-*" + user5.getName() + "-*-*-*" + user5.getPhone());
return restTemplate.postForObject(serviceName + "/service/addUser", paramMap, User.class);
}
提供者:
@PostMapping("/service/addUser")
public User addUser(@RequestParam(value = "id") Integer id,
@RequestParam(value = "name") String name,
@RequestParam(value = "phone") String phone){
//进行业务处理(省略)
System.out.println("服务提供者1-->/service/addUser");
User user = new User();
user.setId(id);
user.setName(name);
user.setPhone(phone);
return user;
}
@PostMapping("/service/addUser2")
public User addUser2(@RequestBody User user,@RequestParam(value = "token")String token,@RequestParam(value = "encode")String encode){
//进行业务处理(省略)
System.out.println(token + "---" + encode);
System.out.println("服务提供者1-->/service/addUser2-->" + user.getId() + "--" + user.getName() + "--" + user.getPhone());
//将user对象插入数据库(暂时省略)
return user;
}
@PostMapping("/service/addUser3")
public User addUser3(@RequestBody User user){
//进行业务处理(省略)
System.out.println("服务提供者1-->/service/addUser3-->" + user.getId() + "--" + user.getName() + "--" + user.getPhone());
//将user对象插入数据库(暂时省略)
return user;
}
PUT 请求 – 修改数据
消费者:
@Autowired
private RestTemplate restTemplate;
@Value("${service.name}")
private String serviceName;
/**
* PUT请求,有参数,无返回值
* @return
*/
@RequestMapping("/web/updateUser")
public String updateUser(){
//要传的表单信息,参数数据(很坑人的),用普通HashMap包装参数,传不过去
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("id",3002);
paramMap.add("name","wx");
paramMap.add("phone","300220022002");
restTemplate.put(serviceName + "/service/updateUser",paramMap);
return "success updateUser";
}
提供者:
@PutMapping("/service/updateUser")
public User updateUser(@RequestParam(value = "id") Integer id,
@RequestParam(value = "name") String name,
@RequestParam(value = "phone") String phone){
//进行业务处理(省略)
System.out.println("服务提供者1-->/service/updateUser-->" + id + "--" + name + "--" + phone);
User user = new User();
user.setId(id);
user.setName(name);
user.setPhone(phone);
return user;
}
DELETE 请求 –删除数据
消费者:
/**
* DELETE请求。有参数,无返回值
* @return
*/
@RequestMapping("/web/deleteUser")
public String deleteUser(){
//定义数组
String[] userArry = {"101","nanjiang","18821611462"};
//定义map
Map<String, Object> paramMap = new ConcurrentHashMap<>();
paramMap.put("id",102);
paramMap.put("name","wuxiao");
paramMap.put("phone","18992384582");
restTemplate.delete(serviceName + "/service/deleteUser?id={0}&name={1}&phone={2}",userArry);
restTemplate.delete(serviceName + "/service/deleteUser?id={id}&name={name}&phone={phone}",paramMap);
return "success deleteUser";
}
提供者:
@DeleteMapping("/service/deleteUser")
public User deleteUser(@RequestParam(value = "id") Integer id,
@RequestParam(value = "name") String name,
@RequestParam(value = "phone") String phone){
//进行业务处理(省略)
System.out.println("服务提供者1-->/service/deleteUser-->" + id + "--" + name + "--" + phone);
User user = new User();
user.setId(id);
user.setName(name);
user.setPhone(phone);
return user;
}
Ribbon组件
Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡器;
Ribbon是Netflix公司发布的开源项目(组件、框架、jar包),主要功能是提供客户端的软件负载均衡算法,它会从Nacos中获取一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点;
客户端发送请求,则ribbon负载均衡器按某种算法(比如轮询、权重、随机等)从维护的可用服务端清单中取出一台服务端的地址,然后进行请求;
Spring Cloud Alibaba底层对Ribbon做了二次封装,可以让我们使用 RestTemplate的服务请求,自动转换成客户端负载均衡的服务调用;Ribbon支持多种负载均衡算法,还支持自定义的负载均衡算法;
服务端负载均衡:
客户端负载均衡:
Ribbon实现服务调用:
1、首先加入ribbon的依赖;
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2、使用ribbon,只需要一个注解@LoadBalanced:
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
在RestTemplate上面加入@LoadBalanced注解,这样就可以实现RestTemplate在调用时自动负载均衡;
Ribbon负载均衡策略:
Ribbon的负载均衡策略是由 IRule 接口定义, 该接口由如下实现:
要使用ribbon实现负载均衡,在Spring 的配置类里面把对应的负载均衡接口实现类作为一个Bean配置一下就行了,即像下面这样:
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
/**
* 更改负载均衡策略,默认是ZoneAvoidanceRule策略
*
* @return
*/
@Bean
public IRule iRule(){
return new NacosRule();
}
随机策略 | 描述 |
---|---|
RandomRule | 随机 |
RoundRobinRule | 轮询 |
AvailabilityFilteringRule | 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用 RoundRobinRule 策略,待统计信息足够会切换到该 WeightedResponseTimeRule 策略; |
WeightedResponseTimeRule | 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率越高,如果服务刚启动时统计信息不足,则 使用RoundRobinRule策略,待统计信息足够会切换到WeightedResponseTimeRule策 略; |
RetryRule | 先按照 RoundRobinRule 策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务; |
BestAvailableRule | 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务; |
ZoneAvoidanceRule (默认) | 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务; |
NacosRule | Nacos负载均衡; |
如果没有指定负载均衡策略,ribbon默认的负载均衡是ZoneAvoidanceRule
自定义负载均衡策略:
/**
* @author
* @Description: 自定义实现版本号负载均衡
* @date 2021/1/7 1:06
*/
public class MyNacosVersionRule extends AbstractLoadBalancerRule {
private static final Logger log = LoggerFactory.getLogger(MyNacosVersionRule.class);
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
// 负载均衡规则:优先选择同集群下,符合metadata的实例
// 没有同集群实例,就选择所有集群下,符合metadata的实例
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
String version = this.nacosDiscoveryProperties.getMetadata().get("version");
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
//所有实例
List<Instance> instances = namingService.selectInstances(name, true);
List<Instance> metadataMatchInstances = instances;
// 如果配置了版本映射,那么只调用元数据匹配的实例
if (StringUtils.isNotBlank(version)){
//JDK8流士编程
metadataMatchInstances = instances.stream()
.filter(instance -> Objects.equals(version,instance.getMetadata().get("version")))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(metadataMatchInstances)){
log.warn("未找到元数据匹配的目标服务实例, 请检查配置: version= {}, instance = {}", version, instances);
return null;
}
}
List<Instance> clusterMetadataMatchInstances = metadataMatchInstances;
// 如果配置了集群名称,需筛选同集群下元数据匹配的实例
if (StringUtils.isNotBlank(clusterName)) {
clusterMetadataMatchInstances = metadataMatchInstances.stream()
.filter(instance -> Objects.equals(clusterName, instance.getClusterName()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
clusterMetadataMatchInstances = metadataMatchInstances;
log.warn("发生跨集群调用, clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, version, clusterMetadataMatchInstances);
}
}
Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances);
return new NacosServer(instance);
}catch (Exception e){
log.error("发生异常", e);
return null;
}
}
}
application.properties文件:
#服务消费者指定自定义负载均衡实现版本
spring.cloud.nacos.discovery.metadata.version=v1
#服务提供者1指定自定义负载均衡实现版本
spring.cloud.nacos.discovery.metadata.version=v1
#服务提供者2指定自定义负载均衡实现版本
spring.cloud.nacos.discovery.metadata.version=v2
#通过配置文件指定负载均衡策略或者使用@Bean注解new MyNacosVersionRule()实现负载均衡
29-nacos-discovery-provider.ribbon.NFLoadBalancerRuleClassName=com.nj.cloudalibaba.ribbon.MyNacosVersionRule
红色部分为远程调用服务名称
Ribbon组件的核心接口组成:
接口 | 作用 | 默认值 |
---|---|---|
IclientConfig | 读取配置 | DefaultClientConfigImpl |
IRule | 负载均衡规则,选择实例 | ZoneAvoidanceRule |
IPing | 筛选掉ping不通的实例 | DumyPing(该类什么不干,认为每个实例都可以用,可以ping通) |
ServerList | 交给Ribbon的实例列表 | Ribbon:ConfigurationBasedServerList Spring Cloud Alibaba:NacosServerList |
ServerListFilter | 过滤掉不符合条件的实例 | ZonePreferenceServerListFilter |
ILoadBalancer | Ribbon的入口 | ZoneAwareLoadBalancer |
ServerListUpdater | 更新交给Ribbon的List的策略 | PollingServerListUpdater |
以上每一个接口都可以自定义进行扩展,和IRule扩展方式和配置方式都一样:
${applicationName};
ribbon:
NFLoadBalancerClassName: #ILoadBalancer该接口实现类
NFLoadBalancerRuleClassName: #IRule该接口实现类
NFLoadBalancerPingClassName: #Iping该接口实现类
NIWSServerListClassName: #ServerList该接口实现类
NIWSServerListFilterClassName: #ServiceListFilter该接口实现类
Nacos权重负载均衡:
Nacos的负载均衡策略NacosRule已经实现了基于权限的负载均衡,直接使用即可(权重负载均衡大多数的请求都是打在权重为1的微服务上):
@Bean
public IRule iRule(){
return new NacosRule();
}
Nacos同一集群优先负载均衡:
#服务消费者Nacos同一集群优先负载均衡,指定集群名称
spring.cloud.nacos.discovery.cluster-name=home
#服务提供者1Nacos同一集群优先负载均衡,指定集群名称
spring.cloud.nacos.discovery.cluster-name=home
#服务提供者2Nacos同一集群优先负载均衡,指定集群名称
spring.cloud.nacos.discovery.cluster-name=beijing
集群名称相同的优先调用,当具有相同名称的集群宕机了,才会调用另一个名称不相同的集群;
Nacos不能跨namespace调用:
#服务消费者的dev命名空间
spring.cloud.nacos.discovery.namespace=58e51f7d-f936-4727-b48b-37a92f6805d0
#服务提供者1的dev命名空间
spring.cloud.nacos.discovery.namespace=58e51f7d-f936-4727-b48b-37a92f6805d0
#服务提供者2的test命名空间
spring.cloud.nacos.discovery.namespace=b1b1aeac-2df3-45b4-81f1-21ae9e34bf88
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/77247.html