SpringCloud学习记录(六)—— Feign

本文介绍 Feign 微服务框架的基本使用方法,包括服务调用、负载均衡、熔断器 Hystrix 和日志打印等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

这里记录了feign的入门、整合Ribbon、整合Hystrix熔断器 和 日志打印几个方面的内容。


一、为什么学习Feign

Ribbon的负载均衡(前面的学习记录)可以很大程度的简化代码的调用,可以通过服务的id直接选择实例进行调用:

 // 通过服务名user-service获取实例
 String url = "http://user-service/user/" + userId;
 return restTemplate.getForObject(url, User.class);

但是,这样写也有一个弊端,那就是还要手动拼接url和参数,如果被调用的服务id发生变化,那么我们要逐个方法去修改拼接的url;重复性的工作也很多,而且不利于后期的维护更新。由此,我们开始学习Feign,进一步的减少重复的工作量,提高代码的质量。


提示:以下是本篇文章正文内容,下面案例可供参考

练习代码地址
Feign 源码

二、使用步骤

2.1 简介

Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样,这样一来,我们就不用自己进行 url拼接、参数拼接 等操作,一切都可以交给feign进行操作。

2.2 引入依赖

使用Feign要导入相关的依赖(在服务调用者的pom.xml文件引入),版本与父工程引入的springcloud版本一致;

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.3 编写Feign的客户端

package com.kaikeba.client;

import com.kaikeba.configure.FeignConfigure;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * Feign客户端
 */
@FeignClient(value = "user-service")
public interface ConsumerUserFeignClient {

    @GetMapping("/user/{userId}")
    String searchByUserId(@PathVariable Integer userId);
}

  • 首先,客户端是一个接口,Feign会通过动态代理,帮我们生成实现类(有点类似与Mybatis 中的Mapper接口);
  • @FeignClient 注解生命这是一个Feign的客户端。通过value属性指定被调用服务的名称(服务id);
  • 接口中定义的方法,完全采用SpringMVC的注解,Feign会帮我们生成调用的URL,并得到访问结果;
  • Feign本质上调用服务的方式,也是通过拼接url和参数实现的,只是这是通过动态代理实现的,简化了我们手写的代码;
  • 公共的路径可以提取到类上,如上面代码中,如果"/user"是这个客户端中,所有的方法都有的访问路径,则可以不在方法上写,提取到类上。如
@FeignClient(value = "user-service/user")
public interface ConsumerUserFeignClient {

    @GetMapping("/{userId}")
    String searchByUserId(@PathVariable Integer userId);
}

2.4 开启Feign

在启动类上通过**@EnableFeignClient**开启Feign功能

@EnableFeignClients
@SpringBootApplication
public class ConsumerServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerServiceApplication.class);
    }
}

2.5 调用

在代码中通过刚才的编写的客户端进行调用

package com.kaikeba.controller;

import com.kaikeba.client.ConsumerUserFeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
   @Resource
    private ConsumerUserFeignClient consumerUserFeignClient;

   @RequestMapping("/{userId}")
   public String searchByUserId(@PathVariable Integer userId) {
       String s = consumerUserFeignClient.searchByUserId(userId);
       return s;
   }
}

三、入门案例

3.1 父工程pom.xml

<?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.kaikeba</groupId>
    <artifactId>springcloud-feign-01</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>user-service</module>
        <module>eureka-server</module>
        <module>consumer-service</module>
    </modules>

    <!-- springboot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.18.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <springcloud.version>Greenwich.SR1</springcloud.version>
        <tk.mybatis.version>2.1.5</tk.mybatis.version>
        <mysql.connector.version>8.0.25</mysql.connector.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- spring cloud  -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${springcloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- tk-mybatis -->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${tk.mybatis.version}</version>
            </dependency>

            <!-- mysql驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.connector.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.2 服务调用者

我们还是用consumer-service充当服务调用者

3.2.1 pom.xml

<?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>springcloud-feign-01</artifactId>
        <groupId>com.kaikeba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>consumer-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

</project>

3.2.2 application.yml

server:
  port: ${port:20000}

spring:
  application:
    name: consumer-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka

3.2.3 ConsumerController.java

package com.kaikeba.controller;

import com.kaikeba.client.ConsumerUserFeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
   @Resource
    private ConsumerUserFeignClient consumerUserFeignClient;

   @RequestMapping("/{userId}")
   public String searchByUserId(@PathVariable Integer userId) {
       String s = consumerUserFeignClient.searchByUserId(userId);
       return s;
   }
}

3.2.4 User.java

package com.kaikeba.entity;

import lombok.Data;
import lombok.experimental.Accessors;

import java.util.Date;

@Data
@Accessors(chain = true)
public class User {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Integer age;
    private Integer sex;
    private Date birthday;
    private Date created;
    private Date updated;
    private String note;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                ", birthday=" + birthday +
                ", created=" + created +
                ", updated=" + updated +
                ", note='" + note + '\'' +
                '}';
    }
}

3.2.5 ConsumerServiceApplication.java

package com.kaikeba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@SpringBootApplication
public class ConsumerServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerServiceApplication.class);
    }
}

3.2.6 客户端ConsumerUserFeignClient.java

package com.kaikeba.client;

import com.kaikeba.configure.FeignConfigure;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * Feign客户端
 */
@FeignClient(value = "user-service")
public interface ConsumerUserFeignClient {

    @GetMapping("/user/{userId}")
    String searchByUserId(@PathVariable Integer userId);
}

3.3 服务提供者

我们使用user-service充当服务的提供者

3.3.1 pom.xml

<?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>springcloud-feign-01</artifactId>
        <groupId>com.kaikeba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-service</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

</project>

3.3.2 application.yml

server:
  port: ${port:10000}

spring:
  application:
    name: user-service # 服务名
  datasource: # 数据源
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/springcloud
    driver-class-name: com.mysql.cj.jdbc.Driver

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka # 注册中心地址
  instance:
    ip-address: 127.0.0.1 # 显示的ip
    prefer-ip-address: true # 以ip的形式被调用
    lease-expiration-duration-in-seconds: 90 # 服务失效时间:默认90s
    lease-renewal-interval-in-seconds: 30 # 服务续约的时间间隔

3.3.3 UserServiceApplication.java

package com.kaikeba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class);
    }
}

3.3.4 User.java

package com.kaikeba.entity;

import lombok.Data;
import lombok.experimental.Accessors;

import javax.persistence.*;
import java.util.Date;

@Data
@Accessors(chain = true)
@Table(name = "tb_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "user_name")
    private String username;
    private String password;
    private String name;
    private Integer age;
    private Integer sex;
    private Date birthday;
    private Date created;
    private Date updated;
    private String note;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                ", birthday=" + birthday +
                ", created=" + created +
                ", updated=" + updated +
                ", note='" + note + '\'' +
                '}';
    }
}

3.3.5 UserMapper.java

package com.kaikeba.mapper;

import com.kaikeba.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User> {
}

3.3.6 UserService.java

package com.kaikeba.service;

import com.kaikeba.entity.User;

public interface UserService {
    /**
     * 根据主键查询
     */
    User findById(Integer userId);
}

3.3.7 UserServiceImpl.java

package com.kaikeba.service.impl;

import com.kaikeba.entity.User;
import com.kaikeba.mapper.UserMapper;
import com.kaikeba.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public User findById(Integer userId) {
        return userMapper.selectByPrimaryKey(userId);
    }
}

3.3.8 UserController.java

package com.kaikeba.controller;

import com.kaikeba.entity.User;
import com.kaikeba.service.UserService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;

    @RequestMapping("/{userId}")
    public String searchByUserId(@PathVariable Integer userId) {
        User user = userService.findById(userId);
        return user.toString();
    }
}

3.4 注册中心

我们的注册中心还是Eureka-server

3.4.1 pom.xml

<?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>springcloud-feign-01</artifactId>
        <groupId>com.kaikeba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka-server</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>

3.4.2 application.yml

server:
  port: 8080 #访问端口

spring:
  application:
    name: eureka-server #服务名

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka # 其他微服务注册使用的uri
    register-with-eureka: false # 不把自己当作服务注册到注册中心
  server:
    enable-self-preservation: false # 关闭自我保护
    eviction-interval-timer-in-ms: 6000 # 扫描失效服务的时间间隔默认是(60 * 1000)s

3.4.3 EurekaServerApplication .java

package com.kaikeba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class);
    }
}

3.5 测试结果

3.5.1 注册中心

在这里插入图片描述

3.5.2 调用

在这里插入图片描述

四、 负载均衡

4.1 依赖

Feign本身已经集成了Ribbon的依赖自动配置
在这里插入图片描述
因此,我们不用在额外的引入依赖,也不用显示注册RestTemplate对象。

4.2 配置

4.2.1 配置项
ribbon:
  ConnectTimeout: 1000 # 链接超时时长
  ReadTimeout: 2000 # 数据通信超时时长
  MaxAutoRetries: 1 # 当前服务器的重试测试
  MaxAutoRetriesNextServer: 1 # 集群中其他实例的重试次数(包括自己这个实例)
  OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
  • ConnectTimeout:表示连接时的超时时间

  • ReadTimeout:表示数据通信的超时时间

  • MaxAutoRetries:表示当前服务器实例的重试次数

  • MaxAutoRetriesNextServer:表示对起群中其他实例的请求次数(包括当前实例)
    会对当前实例进行第二次请求,比如集群中有3个实例,每个请求2次,那么在请求当前实例失败之后,重新请求 时还是会请求当前实例,也就是说,总共会请求:2 + 3*2 = 8次。

  • OkToRetryOnAllOperations:是否对所有的请求方式都重试

4.2.2 特定配置

如果是对所有的服务的Ribbon配置都一致,就能按照4.2.1中的进行配置,若是要对某个服务进行特定的配置,就需要在ribbon前增加服务id,如下:

user-service
	ribbon:
		ReadTimeout: 2000 # 读取超时时长
		ConnectTimeout: 1000 # 建立链接的超时时长

user-service是服务的id;

4.3 服务重试

ribbon有自己的重试机制,一旦超时,就会重新发起请求
在这里插入图片描述
如上图,我们配置的超时时长是2000ms,重试次数是1次,所以当请求失败时,页面显示的时间就是2000(ms) * 2 = 4000(ms) , 如果不想重试,将重试次数设置成0即可;

五、 熔断器Hystrix

5.1 使用步骤

  • Feign默认的集成Hystrix,我们不用额外引入依赖;只不过默认的是关闭(不开启)状态;
  • 使用时,我们要自己编写熔断降级的实现类和实现方法;
  • 熔断类需要实现Feign接口,在对应的方法中编写降级逻辑;
  • 并且在Feign客户端接口中进行配置。
    在这里插入图片描述

5.2 开启配置

在配置文件中,增加如下配置,开始熔断器

feign:
	hystrix:
		enabled: true # 开启Feign的熔断功能

5.3 熔断处理类

package com.kaikeba.client;

import org.springframework.stereotype.Component;

@Component
public class FeignClientFallBack implements ConsumerUserFeignClient {
    @Override
    public String searchByUserId(Integer userId) {
        return "查询出错!!!";
    }
}

5.4 客户端中添加

在Feign客户端中**@FeignClient**注解中的 fallback 属性指定熔断降级的处理类

package com.kaikeba.client;

import com.kaikeba.configure.FeignConfigure;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * Feign客户端
 */
@FeignClient(value = "user-service/user", fallback = FeignClientFallBack.class)
public interface ConsumerUserFeignClient {

    @GetMapping("/{userId}")
    String searchByUserId(@PathVariable Integer userId);
}

5.5 测试结果

服务请求出错时,不是报错,是返回对应信息。
在这里插入图片描述

六、 打印日志

我们原来设置日志级别是通过在配置文件中设置 logging.level.报名=日志级别来配置的:

logging:
  level:
    com.kaikeba: debug

然而这对Feign是不生效的。因为@FeignClient注解修饰的客户端,在被代理是,都会创建一个新的Feign.Logger实例,我们需要对这个实例。进行额外的日志界别配置才可以。

6.1 日志级别

Feign有四种日志级别

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

6.2 日志配置类

package com.kaikeba.configure;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Feign 的日志配置
 */
@Configuration
public class FeignConfigure {

    @Bean
    public Logger.Level feignLoggerLevel() {
    	// 记录所有请求和响应的明细,包括头信息、请求体、元数据
        return Logger.Level.FULL;
    }
}

6.3 指定日志配置

package com.kaikeba.client;

import com.kaikeba.configure.FeignConfigure;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * Feign客户端
 */
@FeignClient(value = "user-service", fallback = FeignClientFallBack.class, configuration = FeignConfigure.class)
public interface ConsumerUserFeignClient {

    @GetMapping("/user/{userId}")
    String searchByUserId(@PathVariable Integer userId);
}

6.4 测试结果

在这里插入图片描述

七、 压缩

Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:

feign:
	compression:
		request:
			enabled: true # 开启请求压缩
		response:
			enabled: true # 开启响应压缩

其他的压缩配置

feign:
	compression:
		request:
			enabled: true # 开启请求压缩
			mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
			min-request-size: 2048 # 设置触发压缩的大小下

注:上面的数据类型、压缩大小下限均为默认值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值