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 远程调用

Demo
下面我们先不使用ribbon, 单独使用RestTemplate来执行远程调用

  1. 新建 ribbon 项目
  2. pom.xml
  3. application.yml
  4. 主程序
  5. controller
  6. 启动,并访问测试

新建 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
Ribbon

负载均衡

负载均衡
Ribbon 对 RestTemplate 做了封装,增强了 RestTemplate 的功能

  1. 获得地址表
  2. 轮询一个服务的主机地址列表
  3. RestTemplate负责执行最终调用

添加负载均衡

  1. ribbon依赖
  2. @LoadBalanced 注解,对 RestTemplate 进行功能增强
  3. 修改调用地址,使用 服务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 重试

重试
一种容错机制,当调用远程服务失败,可以自动重试调用
添加重试:

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的重试机制,在 feign 和 zuul 中进一步进行了封装,后续可以使用feign或zuul的重试机制

禾白少二
57 声望26 粉丝

引用和评论

0 条评论