RestTemplete请求模板类

@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支持多种负载均衡算法,还支持自定义的负载均衡算法;

  服务端负载均衡:

resttemplate 下载 cookie_java


  客户端负载均衡:

resttemplate 下载 cookie_spring cloud alibaba_02

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 接口定义, 该接口由如下实现:

resttemplate 下载 cookie_java_03


要使用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权重负载均衡:

resttemplate 下载 cookie_java_04


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

resttemplate 下载 cookie_ribbon_05

  服务消费者和服务提供者的命名空间必须相同才能调用,不能跨命名空间进行调用,命名空间可以实现服务的完全隔离;