服务间的通信

img点击并拖拽以移动 微服务服务间的通信主要有两种方法:

  • HTTP REST方式:使用HTTP协议进行数据传递(Json
  • RPC 方式:远程过程调用(二进制的对象序列化数据

OSI:物理层—-数据链路层—-网络层—-传输层(RPC)—-会话层—-表示层—-应用层(HTTP

在springcloud中服务间调用方式主要是使用 http restful方式进行服务间调用

基于RestTemplate的服务调用

说明

spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。

RestTemplate的服务调用

# 1.按照创建consul客户端的方法创建两个服务并注册到consul注册中心中
- users    代表用户服务 端口为 8888
- orders 代表商品服务 端口为 9999
	`注意:这里服务仅仅用来测试,没有实际业务意义

img点击并拖拽以移动

# 2.在商品服务(ORDER)中提供服务方法
@RestController
@RequestMapping("order")
public class OrderController {
    @Value("${server.port}")
    private int port;
    @GetMapping("Test")
    public String Demo(){
        System.out.println("Order Demo");
        return "Order服务调用成功,服务端口号为:"+port;
    }

}
# 3.在用户服务(USER)中使用restTemplate调用商品服务中提供的方法

public class UserController {
    @RequestMapping("/user")
    public String InvokeDemo(){
        System.out.println("User Demo");
        //在用户服务中调用订单服务  服务地址URL:http://localhost:9999/order/Test(GET方式,返回值为String类型)
        //创建RestTemplate对象
        RestTemplate restTemplate = new RestTemplate();
        String orderResult= restTemplate.getForObject("http://localhost:9999/order/Test",String.class);
        System.out.println("调用订单服务成功,结果:"+orderResult);
        return "调用订单服务成功,结果:"+orderResult;
    }
}
# 4.启动两个服务,测试服务调用
- 浏览器访问用户服务 http://localhost:8888/user

- 结果成功打印出数据,服务调用成功:
调用订单服务成功,结果:Order服务调用成功,服务端口号为:9999

img点击并拖拽以移动

总结

RestTemplate是直接基于服务地址调用,并没有在服务注册中心获取我们注册的服务,也没有办法完成服务的负载均衡,如果需要实现服务的负载均衡需要自己书写服务负载均衡策略。

基于Ribbon的服务调用

使用RestTemplate进行服务间通信时的问题

  • 调用服务的路径主机和服务端口直接写死在ur1中,无法实现服务集群时请求负载均衡
  • 调用服务的请求路径写死在代码中,日后提供服务的服务路径发生变化时不利于后继维护工作

解决RestTemplate负载均衡问题:

  • 自定义负载均衡解决策略,随机调取集群中不同节点的服务(无法实现服务健康检查,负载均衡策略过于单一:随机)
  • 使用SpringCloud提供的组件Ribbon解决负载均衡调用推荐

说明:

官方网址: https://github.com/Netflix/ribbon

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。

Ribbon是负载均衡 客户端 组件,就是用来实现请求调用时的负载均衡,而真正发起请求的还是RestTemplate

Ribbon负载均衡原理

img点击并拖拽以移动 调用流程:

首先,每个微服务都有可能搭建服务集群,当前服务(用户服务)用Ribbon通过咱们要调用的服务的服务名(ORDERS)在服务注册中心( Eureka ,consul,zk等)中查找,将ORDERS服务所对应的主机列表(9999,9990)拉取到服务(用户服务)本地并缓存,然后Ribbon通过自身的负载均衡策略,在主机列表中选出一个服务,让当前服务(用户服务)再通过RestTemplate去调用选出的这个服务(订单服务)。

注:Ribbon是将所对应的主机列表从服务注册中心拉取并缓存到本地服务的,为了防止所拉取的服务中某个服务宕机而造成服务调用的失败,服务注册中心会实时检测每个服务的健康状况,如果某个服务宕机,服务注册中心会将其清除,更新服务列表,然后向当前服务(用户服务)发起更新缓存请求,更新主机列表去除已宕机的服务。当宕机的这个服务恢复后,进行同样的操作,使服务注册中心的服务主机列表和当前服务缓存的一致。

Ribbon服务调用

# 1.项目中引入依赖
- 说明: 
	1.如果使用的是eureka client 和 consul client,无须引入依赖,因为在eureka,consul中默认集成了ribbon组件
	2.如果使用的client中没有ribbon依赖需要显式引入如下依赖
<!--引入ribbon依赖-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
# 2.使用restTemplate + ribbon进行服务调用:用户服务调用订单服务
- Ribbon提供了三种方式:
- 使用DiscoveryClient     进行客户端调用
- 使用LoadBalanceClient    进行客户端调用
- 使用@loadBalanced注解     进行客户端调用

微服务搭建集群技巧:

搭建ORDERS服务集群(9999,6666),ORDERS服务具体信息见上节内容:img

img点击并拖拽以移动

使用discovery client 进行客户端调用(未实现负载均衡

由于spring-cloud-starter-consul-discovery注解中已经集成了Ribbon相关依赖,因此DiscoveryClient 和LoadBalanceClient可以直接拿来就用,**在UserController中进行测试**:

@Autowired //服务注册与发现客户端对象
    private DiscoveryClient discoveryClient;
    @GetMapping("TestDiscoveryClient")
    public String TestDiscoveryClient(){
        //从服务注册中心获取ORDERS服务的主机列表
        List<ServiceInstance> orders = discoveryClient.getInstances("ORDERS");
        orders.forEach(e->{
            System.out.println("服务主机"+e.getHost());
            System.out.println("服务端口"+e.getPort());
            System.out.println("服务地址"+e.getUri());
        });
        return "ok";
    }
访问 :http://localhost:8888/TestDiscoveryClient

结果:
服务主机localhost
服务端口6666
服务地址http://localhost:6666
服务主机localhost
服务端口9999
服务地址http://localhost:9999

配合RestTemplate调用服务:

@Autowired //服务注册与发现客户端对象
 private DiscoveryClient discoveryClient;
 @GetMapping("TestDiscoveryClient")
 public String TestDiscoveryClient(){
     //从服务注册中心获取ORDERS服务的主机列表
     List<ServiceInstance> orders = discoveryClient.getInstances("ORDERS");
     /*orders.forEach(e->{
         System.out.println("服务主机"+e.getHost());
         System.out.println("服务端口"+e.getPort());
         System.out.println("服务地址"+e.getUri());
     });*/
     //调用主机列表中的第一个服务,也是固定调用,并未实现负载均衡
     String result=new RestTemplate().getForObject(orders.get(0).getUri()+"/order/Test",String.class);
     return "ok"+result;
 }

img点击并拖拽以移动

调用从服务注册中心拉取的主机列表中的第二个服务:

  //调用主机列表中的第二个服务
String result=new RestTemplate().getForObject(orders.get(1).getUri()+"/order/Test",String.class);

img点击并拖拽以移动

使用LoadBalanceClient进行客户端调用(负载均衡)

@Autowired//负载均衡的客户端对象
private LoadBalancerClient loadBalancerClient;
//使用LoadBalancerClient进行服务调用
@GetMapping("LoadBalancerClient")
public String LoadBalancerClient() {
    //根据负载均衡策略选取拉取下来的主机列表中的某一个服务调用(默认轮循策略)
    //轮询策略:
    //每次按照拉取的主机列表中的顺序调用
    ServiceInstance order = loadBalancerClient.choose("ORDERS");
    System.out.println(order);
    String result=new RestTemplate().getForObject(order.getUri()+"/order/Test",String.class);
    return result;
}

img点击并拖拽以移动

再调一次,会变成6666(轮循策略):

img点击并拖拽以移动

推荐:使用@loadBalanced注解进行调用(负载均衡

修饰范围:用在方法上

作用:让当前方法(或对象)具有Ribbon负载均衡的特性

创建RestTemplate对象的工厂方法:

@Configuration 
public class BeansConfig {
    //工厂中创建restTemplate
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

注意:给方法restTemplate加了@LoadBalanced注解后,使其创建的对象具有了Ribbon负载均衡的特性。

调用:

 @Autowired //注入具有Ribbon负载均衡的RestTemplate对象
 private RestTemplate restTemplate;
 @GetMapping("loadBalanced")
 public String loadBalanced() {
 String result = restTemplate.getForObject("http://ORDERS/order/Test" , String.class);
//此时,restTemplate调用getForObject时,会自动先通过服务ID(服务名)ORDERS拉取主机列表,然后再负载均衡调用
        return result;
    }

img点击并拖拽以移动

再调一次:

img点击并拖拽以移动

总结:虽然使用Ribbon+RestTemplate可以实现服务调用的负载均衡,但是我们发现,路径还是写死在代码中,如:”http://ORDERS/order/Test",不利于后期维护。

Ribbon负载均衡策略

根据ServiceInstance order = loadBalancerClient.choose(“ORDERS”);源码分析:img点击并拖拽以移动

  • RoundRobinRule: 轮循策略,按顺序循环选择 Server
  • RandomRule:随机策略,随机选择 Server
  • AvailabilityFilteringRule:可用过滤策略
    ,会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
  • WeightedResponseTimeRule:响应时间加权策略 ,根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到WeightedResponseTimeRule
  • RetryRule:重试策略 ,先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。
  • BestAviableRule: 最低并发策略 ,会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

修改服务的默认负载均衡策略

只需在配置文件中如下配置:

服务ID(服务名).ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.策略名

如:

# 修改用户服务调用订单服务默认负载均衡策略不在使用轮询  使用随机策略
ORDERS.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

img点击并拖拽以移动

Ribbon停止维护了🤣🤣,但是Ribbon-core和Ribbon-loadblance依然在大规模生产实践中部署,因此我们依然可以用其实现服务间通信的负载均衡

基于OpenFeign服务调用

思考: 使用RestTemplate+ribbon已经可以完成对服务的调用,为什么还要使用feign?

通过RestTemplate+ribbon调用服务:

String restTemplateForObject = restTemplate.getForObject("http://服务名/url?参数" + name, String.class);

存在问题:

虽然使用RestTemplate+ribbon可以实现服务调用的负载均衡,但是每次调用服务都需要写这些代码,存在大量的代码冗余,服务地址如果修改,维护成本增高(如果调用的这个服务路径URL发生变更,那么每个服务调用地址都要重新修改),使用时不够灵活。

说明:https://cloud.spring.io/spring-cloud-openfeign/reference/html/

Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性(可以使用springmvc的注解),可使用Feign 注解和JAX-RS注解(REST注解)。Feign支持可插拔的编码器和解码器。**Feign默认集成了Ribbon**,默认实现了负载均衡的效果并且springcloud为feign添加了springmvc注解的支持。

img点击并拖拽以移动

openFeign 服务调用

1.按照创建Consul客户端的方式创建类别服务和商品服务:

类别服务:8787

商品服务:8788

img点击并拖拽以移动

并启动,注册到注册中心:

img

1.在类别服务(服务调用方)引入OpenFeign依赖

<!--Open Feign依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

img点击并拖拽以移动

2.在类别服务(服务调用方)加入注解开启OpenFeign支持

@SpringBootApplication
@EnableDiscoveryClient//consul客户端服务,开启服务注册
@EnableFeignClients//开启OpenFeign客户端调用
public class CategoryApplication {
    public static void main(String[] args) {
        SpringApplication.run(CategoryApplication.class,args);
    }
}

3.商品服务中的方法:

@RestController
public class ProductController {
    @Value("${server.port}")
    private int port;
    @GetMapping("product")
    public String product(){
        return "product商品服务 ok,当前服务端口"+port;
    }
}

4.在类别服务(服务调用方)中创建一个客户端调用接口:



@FeignClient(value="PRODUCT")//value属性用来指定:调用服务名称
public interface ProductClient {

    @GetMapping("/product") //书写服务调用路径
     String Feignproduct(); //方法的返回值类型和参数列表要与要调用的方法的一致
}

5.在类别服务中引入,并调用:


@RestController
public class CategoryController {
    //注入客户端对象
    @Autowired
    private ProductClient productClient;
    @GetMapping("category")
    public String product(){
        String msg = productClient.Feignproduct();
        return "category ok,在类别服务中调用商品服务:"+msg;
    }
}

6.搭建商品服务集群(8788,8799),并在类别服务中测试调用:

img点击并拖拽以移动 访问并测试服务:http://localhost:8787/category

img点击并拖拽以移动

在调一次(可以发现实现负载均衡,因为feign集成了Ribbon,两个端口下的商品服务依次被调用):

img点击并拖拽以移动

openFeign参数传递

零散和对象类型参数传递

1.queryString方式传递参数: ?name=sovzn

**注意:queryString方式传递参数在openfeign接口声明方法必须给参数加入注解@RequestParam**,如果接口中声明的的参数名和要调用的方法中声明的参数名不一致的话,要在接口的注解中指明,如:@RequestParam(”xxx”),xxx为要调用的方法中的参数名,如果一致的话,注解中可以不写任何东西

在ProductController中编写方法:

//定义一个接收零散类型参数方法 queryString
    @GetMapping("TestQueryString")
     public String TestQueryString(String name, Integer age){
        return "商品服务:当前服务端口"+port+"接收到的参数-name:"+name+",age:"+age;
     }

在客户端调用接口ProductClient中声明方法:

//在feign接口中声明调用商品服务ProductController中方法TestQueryString的方法,传递参数,name和age
  @GetMapping("TestQueryString")
  String TestQueryString(@RequestParam String name, @RequestParam Integer age);

在类别服务CategoryController中调用并传递参数:

//零散类型QueryString参数传递
   @Autowired
   private ProductClient productClient;
    @GetMapping("Test")
    public String Test(){
        String msg = productClient.TestQueryString("sovzn",23);
        return "零散类型参数传递,在类别服务中调用商品服务:"+msg;
    }

测试访问:http://localhost:8787/Test

零散类型参数传递,在类别服务中调用商品服务:商品服务:当前服务端口8799接收到的参数-name:sovzn,age:23

img点击并拖拽以移动 再调一次:

img

2.路径传递参数

**注意:使用路径传参时必须给接口和要调用的方法中的参数都加入注解@PathVariable**,采用路径传参的话,要调用的方法中的参数名和接口中参数名可以不一致,但是,不管是接口中的参数名,还是方法中的参数名,如果与所对应的路径中的参数名不一致的话,要在@PathVariable中指明,如下。

在ProductController中编写方法:

//定义一个接收零散类型参数方法  路径传递参数
  @GetMapping("TestURL/{id}/{name}")
  public String TestURL(@PathVariable Integer id,@PathVariable String name){
      return "商品服务:当前服务端口"+port+"接收到的参数-id:"+id+",name:"+name;
  }

在客户端调用接口ProductClient中声明方法:

//在feign接口中声明调用商品服务ProductController中方法TestURL的方法,传递参数,id和name
   @GetMapping("TestURL/{ID}/{name}")
    //若路径中的参数名和声明的不一致,则要@PathVariable在中指明,如下
   String TestURL(@PathVariable("ID") Integer id, @PathVariable String name);

在类别服务CategoryController中调用并传递参数

//零散类型路径传递参数
   @GetMapping("Test2")
   public String Test2(){
       String msg = productClient.TestURL(1,"shiyaochang");
       return "零散类型参数传递,在类别服务中调用商品服务:"+msg;
   }

测试访问:http://localhost:8787/Test2

img点击并拖拽以移动 在调一次:

img

3.对象类型参数传递(Json格式对象)

注意:对象类型参数传递时给接口和要调用的方法中的参数都加入注解@RequestBody即可,不用考虑接口和要调用的方法中的参数名是否一致

先在两个服务中创建实体:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private Integer id;
    private String name;
    private Double price;
}

在ProductController中编写方法:

//定义一个接收对象类型参数方法
   @PostMapping("TestObject")
   public String TestObject(@RequestBody Product product){
       return "商品服务:当前服务端口"+port+"接收到的对象为: \n"+product.toString();
   }

在客户端调用接口ProductClient中声明方法:

//在feign接口中声明调用商品服务ProductController中方法TestObject的方法,传递对象参数
 @PostMapping("TestObject")
 String TestObject(@RequestBody Product product);

在类别服务CategoryController中调用并传递参数

//对象类型传参
@GetMapping("Test3")
String TestObject() {
    String msg = productClient.TestObject(new Product(1,"商品名",11.23));
    return "对象类型参数传递,在类别服务中调用商品服务:"+msg;

}

测试访问:http://localhost:8787/Test3

img

在调一次:

img

数组和集合类型参数传递

1.数组类型参数传递 采用 queryString 方式:/TestArray?ids=21&ids=22

**注意给接口方法的参数加入注解@RequestParam**,同样如果接口中声明的的参数名和要调用的方法中声明的参数名不一致的话,接口的注解中要指明,如:@RequestParam(”ids”),ids为要调用的方法中的参数名,如果一致的话,注解中可以不写任何东西,如下:。

在ProductController中编写方法:

//定义一个接收数组类型的方法
    @RequestMapping("TestArray")
    public String TestArray( String[] ids){
        //手动转为结合
        List<String> strings = Arrays.asList(ids);
        return "当前服务端口为: "+port+"   得到的数据为:"+strings;
    }

在客户端调用接口ProductClient中声明方法:

//在feign接口中声明调用商品服务中TestArray的接口方法 传递一个数组类型 queryString方式
   @GetMapping("TestArray")
   String Test3(@RequestParam("ids") String[] IDs);

在类别服务CategoryController中调用并传递参数

//数组类型传参
   @GetMapping("TestArray")
   String TestArray() {
       String msg = productClient.TestArray(new String[]{"21","23","24"});
       return "数组类型参数传递:"+msg;

   }

测试:http://localhost:8787/TestArray

img点击并拖拽以移动

2.集合类型传参

注意:SpringMvc不能直接接收集合类型参数,如果想要接收集合类型参数,必须将集合放入对象中,使用对象的方式接收

定义一个用于接收集合的对象:

//定义用来接收集合类型参数的对象
public class CollectionVO {
    
    private List<String> ids;//接收集合声明在这里

    public List<String> getIds() {
        return ids;
    }
    public void setIds(List<String> ids) {
        this.ids = ids;
    }
}

在ProductController中编写方法:

    //定义一个接收集合类型参数的方法
    @GetMapping("TestList")
    public String TestList(CollectionVO collectionVO){
        return "当前服务端口为: "+port+"   得到的集合数据为:"+collectionVO.getIds();
    }
}

在客户端调用接口ProductClient中声明方法:不能直接使用集合传参,同样使用数组参数

//声明一个调用商品服务中TestList的接口
    @RequestMapping("/TestList")
    String TestList(@RequestParam String[] ids);

在类别服务CategoryController中调用并传递参数

@GetMapping("TestList")
   String TestList() {
       String msg = productClient.TestList(new String[]{"用对象中的集合接收","23","24"});
       return "数组类型参数传递:"+msg;

   }

测试:http://localhost:8787/TestList

img点击并拖拽以移动

openFeign调用服务相应处理

1.调用服务返回对象的处理:

//在商品服务的ProductController中定义一个接口接收id类型参数,返回一个基于id查询的对象
    @GetMapping("/product/{id}")
    public Product product(@PathVariable("id") Integer id){
        return new Product(id,"超短连衣裙",23.23);
    }
//在类别服务的feign接口 ProductClient中声明调用根据id查询商品信息接口
   @GetMapping("/product/{id}")
   Product product(@PathVariable("id") Integer id);
//在类别服务CategoryController中调用并接收返回值
@GetMapping("product")
    Product getproduct() {
        Product product = productClient.product(100);
        return product;
    }

测试:http://localhost:8787/product

img点击并拖拽以移动

2.调用服务返回集合的处理

//在商品服务的ProductController中定义一个接口接收参数id,返回一个基于id查询的对象集合
@GetMapping("/products")
    public List<Product> findByCategoryId(Integer categoryId){
        //模拟调用业务逻辑根据类别id查询商品列表
        List<Product> products = new ArrayList<>();
        products.add(new Product(categoryId,"短裙",23.23));
        products.add(new Product(categoryId,"超短裙",23.23));
        products.add(new Product(categoryId,"超级超短裙",23.23));
        return products;
    }
//在类别服务的feign接口 ProductClient中声明调用根据id查询商品信息接口
   @GetMapping("/product/{id}")
   Product product(@PathVariable("id") Integer id);
//在类别服务CategoryController中调用并接收返回值
@GetMapping("products")
    List<Product>  getproducts() {
        List<Product> products = productClient.products(12);
        return products;

    }

测试:http://localhost:8787/products

img点击并拖拽以移动

3.调用服务返回Map集合的处理

//在商品服务的ProductController中定义一个接口接收参数id,当前页page,每页记录数rows,返回一个基于这三个参数查询的商品信息和总条数,用Map集合封装

    @GetMapping("/productsPage")
    public Map<String,Object> findByCategoryIdAndPage(Integer page,Integer rows,Integer categoryId){
        System.out.println("当前页:"+page);
        System.out.println("每页的记录数:"+rows);
        System.out.println("当前类别id:"+categoryId);
        //根据类别id分页查询符合当前页集合数据  List<Product>   select * from t_product where categoryId=? limt ?(page-1)*rows,?(rows)
        //根据类别id查询当前类别下总条数       totalCount           select count(id) from t_product where categoryId=?
        Map<String, Object> map = new HashMap<>();
        List<Product> products = new ArrayList<>();
        products.add(new Product(1,"短裙",23.23));
        products.add(new Product(2,"超短裙",23.23));
        products.add(new Product(3,"超级超短裙",23.23));
        int total = 1000;
        map.put("products",products);
        map.put("total", total);
        return map;
    }
///在类别服务的feign接口 ProductClient中声明调用商品服务根据类别id查询并分页查询商品信息 以及总条数
    @GetMapping("/productsPage")
    Map<String,Object> findByCategoryIdAndPage(@RequestParam("page") Integer page, @RequestParam("rows") Integer rows, @RequestParam("categoryId") Integer categoryId);
}
//在类别服务CategoryController中调用并接收返回值

@GetMapping("productPage")
    Map<String,Object>  getproductsPage() {
        Map<String,Object> result = productClient.findByCategoryIdAndPage(1,3,23);
        return result;

    }

测试:http://localhost:8787/productPage

img点击并拖拽以移动

问题:

直接用Map<String,Object>来接收服务调用返回的数据,虽然可以,但是无法将key为products所对应的

数据(商品信息列表)转为一个List集合,因为它在Map中是以对象的形式存在。

为了解决以上问题,我们可以继续用String来接收服务调用返回的Json数据,然后反序列化来将其转换为List集合。

  • 序列化:对象转为Json
  • 反序列化:json字符串转为对象

先在类别服务中引入alibaba的fastjson依赖:

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
           <version>1.2.76</version>
       </dependency>

用String接收返回数据:

//Feign 接口
  @GetMapping("/productsPage")
    String findByCategoryIdAndPage(@RequestParam("page") Integer page, @RequestParam("rows") Integer rows, @RequestParam("categoryId") Integer categoryId);

 @GetMapping("productPage")
String  getproductsPage() {
    String result = productClient.findByCategoryIdAndPage(1,3,23);
     //将Json字符串转为对象jsonObject,其实JSONObject就是Map<String,Object>
     JSONObject jsonObject= JSONObject.parseObject(result);
     Object products = jsonObject.get("products");
     //反序列化:将jsonObject中的products转为List
     List<Product> Listproducts = jsonObject.parseArray(products.toString(), Product.class);
     Listproducts.forEach(System.out::println);
     return result;

 }

测试:http://localhost:8787/productPage

img点击并拖拽以移动

OpenFeign超时设置

默认情况下,openFiegn在进行服务调用时,要求服务提供方处理业务逻辑时间必须在1S内返回,如果超过1S没有返回则OpenFeign会直接报错,不会等待服务执行,但是往往在处理复杂业务逻辑是可能会超过1S,因此需要修改OpenFeign的默认服务调用超时时间。

调用超时会出现如下错误:

img点击并拖拽以移动

修改OpenFeign默认超时时间

在调用端(类别服务)进行如下配置:

feign.client.config.PRODUCTS.connectTimeout=5000  		#配置指定服务(商品服务)连接超时
feign.client.config.PRODUCTS.readTimeout=5000		  	#配置指定服务(商品服务)等待超时
#feign.client.config.default.connectTimeout=5000  		#配置所有服务连接超时
#feign.client.config.default.readTimeout=5000			#配置所有服务等待超时

OpenFeign调用详细日志展示

往往在服务调用时我们需要详细展示feign的日志,默认feign在调用是并不是最详细日志输出,因此在调试程序时应该开启feign的详细日志展示。feign对日志的处理非常灵活可为每个feign客户端指定日志记录策略,每个客户端都会创建一个logger,默认情况下logger的名称是feign的全限定名,需要注意的是,feign日志的打印只会DEBUG级别做出响应。

我们可以为feign客户端配置各自feign的logger.lever对象,告诉feign记录那些日志,feign的logger.lever有以下的几种:

  • NONE: 不记录任何日志
  • BASIC: 仅仅记录请求方法,url,响应状态代码及执行时间
  • HEADERS: 记录Basic级别的基础上,记录请求和响应的header
  • FULL: 记录请求和响应的header,body和元数据

开启feign日志展示

feign.client.config.PRODUCTS.loggerLevel=full  #开启指定服务(商品服务)日志展示
#feign.client.config.default.loggerLevel=full  #全局开启服务日志展示
logging.level.com.baizhi.feignclients=debug    #指定feign调用客户端对象所在包,必须是debug级别

如:

img点击并拖拽以移动