jjzjj

微服务之同步远程调用(Feign)

Linux、Python、 Golang、Java、DevOps、前端、English 2023-03-28 原文

前言

微服务的服务提供者和服务消费者解耦合之后,我们可以借助restTemplate这样的HTTP客户端,向微服务的服务提供者发起远程调用;

但是这样的代码有2大缺陷:

  • 代码可读性差,编程体验不统一

  • 当URL参数复杂时难以维护

 //使用RestTemplate发起远程调用
    @Autowired
    private RestTemplate restTemplate;
    public Order findById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.selectById(orderId);
        //2.调用user-service服务查询当前订单的用户信息
        //String url = "http://127.0.0.1:8081/user/" + order.getUserId();
        //使用RestTemplate发起远程调用
        String url = "http://user-service/user/" + order.getUserId();
        User user = restTemplate.getForObject(url, User.class);
        //3.user封装到order对象
        order.setUser(user);
        // 4.返回
        return order;
    }

 

一、SpringCloud项目搭建

鉴于微服务架构中职责单一性原则,2个微服务使用单独的数据库;

如果用户微服务需要查询订单微服务的数据,就需要使用Feign发起远程调用;

在远程调用过程中user-service是消费者order-service是生产者;

1.搭建流程图

2.搭建父项目

2.1.父项目依赖版本锁定

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zhanggen</groupId>
    <artifactId>spring-cloud-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--在打包的时候被打成jar,会被作为管理模块去编译-->
    <packaging>pom</packaging>
    <!--
        父项目中引入spring-boot-starter-parent继承Spring-boot的核心包
        子项目的spring-starter才可以被启动
    -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
        <relativePath/>
    </parent>

    <!--dependencyManagement进行版本锁定,不会真正引入依赖包-->
    <dependencyManagement>
        <dependencies>
            <!-- 给spring-cloud提供的Feign、Gateway、Eureka等提供版本锁定 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR10</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 给spring-cloud-alibaba提供的Nacos、Sentinel、Dubbo等提供版本锁定 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <modules>
        <module>user-service</module>
        <module>order-service</module>
    </modules>


</project>
pom.xml

3.搭建子项目

3.1.子项目依赖导入

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-demo</artifactId>
        <groupId>com.zhanggen</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>order-service</artifactId>

    <dependencies>
        <!--在子项中引入spring-boot-starter-web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--在子项中引入nacos依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--在子项中引入feign基本依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--在子项中引入httpclient依赖,添加后feign支持连接池-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>
    </dependencies>


</project>
pom

3.2.application配置文件

server:
  port: 9090
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
application.yml

 

二、Feign介绍

Feign是1个声明式的HTTP客户端,通过HTTP协议使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可

其作用就是帮助我们优雅的实现HTTP请求的发送,解决上面提到的问题。

而且Feign默认集成了Ribbon,所以使用Feign默认就实现了客户端负载均衡的效果

 

三、Feign使用

1.引入依赖

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

 

2.添加@EnableFeignClients注解

该注解配置在调用者的启动类上,以至于可以调用被调用这;

在order-service的启动类添加注解开启Feign的功能;

@MapperScan("com.zhanggen.order.mapper")
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients //开启Feign
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }

 

3.编写Feign的客户端

服务提供者提供接口以至于服务调用者可以调用;

在order-service项目中com.zhanggen.order.mapper包下新建一个接口,内容如下:

//FeignClient必须和@EnableFeignClients标注的类在同一个包下
//该接口用于向user-service微服务发起远程调用
//Feign底层默认使用HTTP协议发起远程调用
//@FeignClient("user-service")+@GetMapping("/user/{id}")确定服务提供者的URL(http://user-service/user/{id})
@FeignClient("user-service")
public interface UserClient {
    //根据userId查询用户信息
    @GetMapping("/user/{id}")
    User findById(@PathVariable Long id);
}

这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。

 

4.修改OrderService类

是不是看起来优雅多了。

//使用Feign发起远程调用
    @Autowired
    private UserClient userClient;

    public Order findById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.selectById(orderId);
        //2.调用user-service服务查询当前订单的用户信息
        //String url = "http://127.0.0.1:8081/user/" + order.getUserId();
        //使用RestTemplate发起远程调用
        //String url = "http://user-service/user/" + order.getUserId();
        //User user = restTemplate.getForObject(url, User.class);
        User user = userClient.findById(order.getUserId());
        //3.user封装到order对象
        order.setUser(user);
        // 4.返回
        return order;
    }

 

四、Feign配置

Feign可以支持很多的自定义配置,如下表所示:

类型作用说明
feign.Logger.Level 修改日志级别 包含四种不同的级别:NONE(默认)、BASIC、HEADERS、FULL NONE,不记录。默认选项 BASIC,仅记录请求方法和URL以及响应状态代码和执行时间。 HEADERS,记录基本信息以及请求和响应标头。 FULL,记录请求和响应的标题,正文和元数据。
feign.codec.Decoder 响应结果的解析器 http远程调用的结果做解析,例如解析json字符串为java对象
feign.codec.Encoder 请求参数编码 将请求参数编码,便于通过http请求发送,例如POST请求,将请求参数编码到请求体中
feign.Contract 支持的注解格式 默认是SpringMVC的注解
feign.Retryer 失败重试机制 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试,例如A服务无法访问,会尝试访问集群中的B服务

一般情况下,Feign的默认配置就能满足我们使用,如果要自定义配置时,只需要创建自定义的@Bean覆盖默认Bean即可。

下面以日志为例来演示如何自定义配置,修改Feign日志有 2 种方式: 配置文件方式、Java代码方式

1.配置文件方式(推荐)

基于配置文件修改feign的日志级别可以针对单个服务:

feign:  
  client:
    config: 
      user-service: # 针对某个微服务的配置,也可以针对所有服务,这个位置换成default
        loggerLevel: FULL #  日志级别 

2.Java代码方式

也可以基于Java代码来修改日志级别,在com.zhanggen.order.config包下创建类,然后声明一个Logger.Level的对象:

package com.zhanggen.order.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;

public class FeignDefaultConfiguration  {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL; // 日志级别为FULL
    }
}

2.1.配置局部

如果我想让以上日志配置在调用某1个服务提供者时生效,在FeigClien接口中修改如下;

@FeignClient(value = "user-service",configuration = FeignDefaultConfiguration.class)
public interface UserClient {
    //根据userId查询用户信息
    @GetMapping("/user/{id}")
    User findById(@PathVariable Long id);
}

2.2.配置全局

如果我想让以上日志配置在调用任意服务提供者时生效,在启动类上修改如下;

@EnableFeignClients(defaultConfiguration = FeignDefaultConfiguration.class)
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

 

五、Feign连接池

Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。

1.pom引入依赖

<!--httpClient的依赖内置连接池 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

2.application.yml配置

max-connections-per-route:再调用某1个服务提供者时,最多从连接池中拿出50个连接发起远程调用,避免服务雪崩;

feign:
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 设置最大的连接数
    max-connections-per-route: 50 # 并行接收一个服务的请求数量
    

 

六、Feign公共模块

在微服务架构中肯定会存在多个服务消费者,这些服务消费者都需要借助Feign发起远程调用;

在实际的分布式架构开发中我们一般把Feign抽取出来作为1个公共模块,以便于其他微服务模块使用;

 

1.创建feign-api公共模块

把之前在order-service中编写的UserClient、User、FeignDefaultConfiguration剪切到feign-api项目中

在fein-api模块中添加spring-cloud-starter-openfeign依赖

<dependencies>
        <!--openfeign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--mybatis-plus 实体类注解要用到-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-core</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!--httpclient基于连接池的调用-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>
    </dependencies>

 

2.服务消费者(调用方)

2.1.引入公共模块

服务消费者pom文件引入feign-api公共模块的依赖;

  <dependency>
      <groupId>com.zhanggen</groupId>
     <artifactId>fein-api</artifactId>
      <version>1.0-SNAPSHOT</version>
  </dependency>

2.2.启动类

服务消费者启动类设置@EnableFeignClients注解一定要设置Feign公共模块中的配置类和Feign公共模块的包;

@EnableFeignClients(defaultConfiguration = FeignDefaultConfiguration.class,
        basePackages = "com.zhanggen.feign")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

服务消费者配置文件

如果遇到调用超时问题,可以在服务消费者的配置文件中设置一下超时时间

feign:
  client:
    config:
      default:
        #不设置connectTimeout会导致readTimeout设置不生效
        connectTimeout: 3000
        readTimeout: 6000

 2.3.Feign常见报错

 Did you forget to include spring-cloud-starter-netflix-ribbon?

     <dependency>
        <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>

 

3.服务提供者(被调用方)

创建1个client包专门对内面向Feign远程调用,而controller包专门对外面向前端调用;

  

 

参考

Feign微服spancolorstyleJava

有关微服务之同步远程调用(Feign)的更多相关文章

  1. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  2. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  3. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  4. ruby - 调用其他方法的 TDD 方法的正确方法 - 2

    我需要一些关于TDD概念的帮助。假设我有以下代码defexecute(command)casecommandwhen"c"create_new_characterwhen"i"display_inventoryendenddefcreate_new_character#dostufftocreatenewcharacterenddefdisplay_inventory#dostufftodisplayinventoryend现在我不确定要为什么编写单元测试。如果我为execute方法编写单元测试,那不是几乎涵盖了我对create_new_character和display_invent

  5. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  6. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  7. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  8. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  9. ruby - 如何找到调用当前方法的方法 - 2

    如何找到调用此方法的位置?defto_xml(options={})binding.pryoptions=options.to_hifoptions&&options.respond_to?(:to_h)serializable_hash(options).to_xml(options)end 最佳答案 键入caller。这将返回当前调用堆栈。文档:Kernel#caller.例子[0]%rspecspec10/16|===================================================62=====

  10. ruby-on-rails - 使用 HTTParty 的非常基本的 Rails 4.1 API 调用 - 2

    Rails相对较新。我正在尝试调用一个API,它应该向我返回一个唯一的URL。我的应用程序中捆绑了HTTParty。我已经创建了一个UniqueNumberController,并且我已经阅读了几个HTTParty指南,直到我想要什么,但也许我只是有点迷路,真的不知道该怎么做。基本上,我需要做的就是调用API,获取它返回的URL,然后将该URL插入到用户的数据库中。谁能给我指出正确的方向或与我分享一些代码? 最佳答案 假设API为JSON格式并返回如下数据:{"url":"http://example.com/unique-url"

随机推荐