Spring Cloud 框架 -- RestTemplate 与客户端负载均衡

RestTemplate 介绍

RestTemplate 是从 Spring3.0 开始支持的一个 http 请求工具,这个请求工具是 Spring 自带的,与 Spring Boot 和 Spring Cloud 都无关。

RestTemplate 提供了常见的 REST 请求方法模板,如 GET、POST、PUT、DELETE 请求以及一些通用的请求执行方法 exchange 和 execute 方法。

RestTemplate 本身实现了 RestOperations 接口,而在 RestOperations 接口中,定义了常见 RESTful 操作,这些操作在 RestTemplate 中都得到了很好的实现。

GET

首先,在 provider 中定义一个 hello2 接口:

@GetMapping("/hello2")
    public String hello2(String name){
        return "hello" +name;
    }

接下来,在 consumer 中去访问这个接口,也就是调用 RestTemplate 中的 GET 请求。

可以看到,在 RestTemplate 中,关于 GET 请求,一共有两大类方法:

这两大类方法实际是重载的,唯一不同的,就是返回值类型。

  • getForObject 返回的是一个对象,这个对象就是服务端返回的具体值。

  • getForEntity 返回的是一个 ResponseEntity,这个 ResponseEntity 中除了服务端返回的具体数据外,还保留了 http 响应头的数据。

@GetMapping("/hello4")
    public void hello4(){
        String s1 = restTemplate.getForObject("http://PROVIDER/hello?name={1}", String.class, "youlinwei");
        System.out.println(s1);
        ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://PROVIDER/hello", String.class, "youlinwei");
        String body = responseEntity.getBody();
        System.out.println("body:" +body);
        HttpStatus statusCode = responseEntity.getStatusCode();
        System.out.println("HttpStatus:" + statusCode);
        int statusCodeValue = responseEntity.getStatusCodeValue();
        System.out.println("statusCodeValue:" +statusCodeValue);
        HttpHeaders headers = responseEntity.getHeaders();
        Set<String> keySet = headers.keySet();
        System.out.println("----------- header ----------");
        for (String s: keySet){
            System.out.println(s + ":" + headers.get(s));
        }

    }

POST

首先在 provider 中提供两个 POST 接口,同时,因为 POST 请求可能需要传递 JSON ,所以,这里我们创建一个普通的 Maven 项目作为 commons 模块,然后这个 commons 模块被 provider 和 consumer 共同引用,这样我们就可以方便的传递 JSON 了。

commons 模块创建成功后,首先在 commons 模块中添加 User 对象,然后该模块分别被 provider 和 consumer 引用,即在 provider/pom.xml 和 consumer/pom.xml 中添加如下依赖:

<dependency>
            <groupId>com.example</groupId>
            <artifactId>commons</artifactId>
            <version>1.0-SNAPSHOT</version>
</dependency>

然后,我们在 provider 中提供两个 POST 接口:

// 以  key-value 形式传参
    @PostMapping("/user1")
    public User addUser1(User user){
        return user;
    }

    // 以 json 形式传参
    @PostMapping("/user2")
    public User addUser2(@RequestBody User user){
        return user;
    }

这里定义了两个 User 添加的方法,两个方法代表了两种不同的传参方式。第一种方法是以 key-value 形式传参,第二种是以 json 形式传参。

定义完成后,接下来,我们在 commons 中调用这两个 POST 接口。

如上图,RestTemplate 的 POST 方法和前面的 GET 方法很像。只是多出来一个 postForLocation 方法。

这里,我们先使用 postForObject ,然后再看看 postForLocation。

  @GetMapping("/hello6")
    public void hello6(){
        LinkedMultiValueMap<Object, Object> map = new LinkedMultiValueMap<>();
        map.add("username","you");
        map.add("password","123");
        map.add("id",11);
        // 以 key-value 形式传参
        User user = restTemplate.postForObject("http://PROVIDER/user1", map, User.class);
        System.out.println(user);

        // 以 json 形式传参
        user.setId(12);
        user = restTemplate.postForObject("http://PROVIDER/user2", user, User.class);
        System.out.println(user);
    }

如果 postForObject 方法的第二个参数 MultiValueMap ,则参数是以 key-value 形式传递;如第二个参数是普通对象,则参数是以 json 形式来传递的。

最后再看看 postForLocation ,有的时候,当我执行完一个 post 请求之后,立马要进行重定向。比如注册的时候,注册是一个 post 请求,注册完成后,立马重定向到登录页面。这个时候就需要使用 postForLocation。

首先,在 provider 上提供一个用户注册接口:

@Controller
public class RegisterController {
    @PostMapping("/register")
    public String register(User user){
        return "redirect:http://PROVIDER/loginPage?username=" + user.getUsername();
    }
    @GetMapping("/loginPage")
    @ResponseBody
    public String loginPage(String username){
        return "loginPage:" +username;
    }
}

注意,这里的 POST 接口 响应码一定是302 ,否则 postForLocation 无效。

注意,重定向的地址,一定要写出绝对路径。

@GetMapping("/hello7")
    public void hello7(){
        LinkedMultiValueMap<Object, Object> map = new LinkedMultiValueMap<>();
        map.add("username","you");
        map.add("password","123");
        map.add("id",11);
        URI uri = restTemplate.postForLocation("http://PROVIDER/register", map);
        String s = restTemplate.getForObject(uri, String.class);
        System.out.println(s);
    }

调用 postForLocation 方法,返回的是 uri ,这个 uri 就是重定向的地址,拿到 uri 之后,就可以直接发送新的请求了。

客户端负载均衡

客户端负载均衡,就是相对服务端负载均衡而言的。

服务端负载均衡,就是传统的 Nginx 的方式。用 Nginx 做负载均衡,我们称之为服务端负载均衡。

如下图所示:

这种均衡,称为服务端负载均衡。它的一个特点就是,客户端并不知道此时为它提供服务的 server 到底是哪一个,它也不关心。反正请求发给 Nginx,Nginx 再将请求转发给 Tomcat,客户端只需要记住 Nginx 的地址即可。

客户端负载均衡则是另外一种情形:

客户端负载均衡,就是调用的客户端本身是知道所有 Server 的详细信息的,当需要调用 Server 上的接口时,客户端就从自身所维护的 Server 列表中,根据提前配置好的负载均衡策略,自己挑选一个 Server 来调用。此时,客户端知道它所调用的 Server 到底是哪一个。

在 RestTemplate 中,要想使用负载均衡功能,只需要给 RestTemplate 实例上添加一个 @LoadBalanced 注解即可,此时,RestTemplate 就会自动具备负载均衡功能,这个负载均衡就是客户端负载均衡。

    @Bean
    // 给 RestTemplate 实例添加 @LoadBalanced 注解,自动开启负载均衡
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }

负载均衡原理

在 Spring Cloud 中,实现负载均衡非常容易。只需要添加 @LoadBalanced 注解即可。只要添加了该注解,一个原本普通做 Rest 请求的工具 RestTemplate 就会自动具备负载均衡功能,这个是怎么实现的呢?

整体上来说,这个功能的实现就是三个核心点:

-从 Eureka Client 本地缓存的服务注册信息中,选择一个可以调用的服务。

  • 根据 1 中所选择的服务,重构请求 URL 地址。

  • 将 1、2 步的功能嵌入到 RestTemplate 中。

每天学习一点点,每天进步一点点。

posted @ 2020-07-10 16:34  爱吃西瓜的番茄酱  阅读(623)  评论(0编辑  收藏  举报