微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

在微服务架构中很多功能都需要调用多个服务才能完成某一项功能,一个成熟的微服务集群,内部调用必然依赖一个好的 RPC 框架,比如:基于 Http 协议的 Feign,基于私有 tcp 协议的 Dubbo

继上面几篇 Spring Cloud Alibaba 之 Nacos 系列学习结束后,今天我们开始学习微服务系列中的 服务调用 Feign

话不多说,开始今天的学习。

基本介绍

1. Feign 是什么

FeignSpring Cloud Netflix组件中的轻量级Restful的 HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了RibbonRestTemplate, 实现了WebService的面向接口编程,进一步降低了项目的耦合度。

Feign 通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,封装了 Http 调用流程。

2. 为什么要使用 Feign

如果不使用 RPC 框架,那么调用服务需要走 Http 的话,配置请求 head、body,然后才能发起请求。获得响应体后,还需解析等操作,十分繁琐。

Feign 旨在使编写 JAVA HTTP 客户端变得更加简单,Feign 简化了RestTemplate代码,实现了Ribbon负载均衡,使代码变得更加简洁,也少了客户端调用的代码,使用 Feign 实现负载均衡是首选方案,只需要你创建一个接口,然后在上面添加注解即可。

Feign 是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程 HTTP 请求。让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全无感知这是远程方法,无需关注与远程的交互细节,更无需关注分布式环境开发。

3. OpenFeign

Feign 内置了Ribbon,用来做客户端负载均衡调用服务注册中心的服务。
Feign 支持的注解和用法参考官方文档:https://github.com/OpenFeign/feign官方文档,使用 Feign 的注解定义接口,然后调用这个接口,就可以调用服务注册中心的服务。

Feign本身并不支持Spring MVC的注解,它有一套自己的注解,为了更方便的使用Spring Cloud孵化了OpenFeign。并且支持了Spring MVC的注解,如@RequestMapping@PathVariable等等。
OpenFeign@FeignClient可以解析Spring MVC@RequestMapping注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均衡调用服务。

  • Feign 采用的是基于接口的注解
  • Feign 整合了 Ribbon,具有负载均衡的能力
  • 整合了 Hystrix,具有熔断的能力

代码实战

实际开发中,我们一般把 Fegin 暴露的接口单独放在一个 module 下,要演示完整的 Fegin 调用,我们还需要一个服务提供者和一个服务消费者,本次演示代码模块如下:

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

其中:

  • cloud-system 是一个服务提供者,提供一个 /user/getUserInfo 的接口
  • cloud-auth 是一个服务消费者,通过 Feign 来调用服务提供者中的 /user/getUserInfo 接口
  • cloud-api 就是一个单独的 module,专门用来放暴露给其他服务的接口(或者不要这个 module,代码直接放在服务消费者中也一样)

我们使用 Nacos 作为本次案例的注册中心。Nacos 相关的知识可以看下前面的文章。

好了,接下来我们来看看具体配置。

1. 服务提供者 cloud-system 配置

服务提供者比较简单,只有一个测试用的接口。

  • application.yml
server:
  port: 9202

# Spring
spring:
  application:
    # 应用名称
    name: cloud-system
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 127.0.0.1:8848
  • UserController.java
@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/getUserInfo")
    public Map<String, Object> getUserInfo(int userId){
        Map<String, Object> map = new HashMap<String, Object>();
        User user = new User(1, "小黑", 26);
        map.put("code", 200);
        map.put("data", user.toString());
        return map;
    }
}

其中的 User 类很简单就三个字段,这里就不贴了。

  • 启动项目

前提是 Nacos 配置中心已启动,然后启动 cloud-system,启动成功后访问 http://localhost:9202/user/getUserInfo?userId=1 地址

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

好的,没有问题。

2. cloud-api Feign 关键配置

  • 添加依赖
<!-- spring cloud openfeign -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 新建RemoteUserService.java服务接口
/**
 * @Author: ezhang
 * @Date: Created in 2022/1/9 17:48
 * @Description: 用户服务
 */

@FeignClient(contextId = "remoteUserService", value = "cloud-system", fallbackFactory = RemoteUserFallbackFactory.class)
public interface RemoteUserService {

    /**
     * 根据 userId 查询用户信息
     * 需要添加 @RequestParam,用于纠正参数映射,不然会报 405 错误
     * @param userId
     * @return
     */
    @GetMapping(value = "/user/getUserInfo")
    Map<String, Object> getUserInfo(@RequestParam("userId") int userId);

}

注意事项:

  1. 必须加 @FeignClient 声明;
  2. 其他 value 属性(等同于 name 属性)的值是 yml 文件中 spring.application.name 的值,就是注册到注册中心的实例名字,这里建议用全局变量来填写,方便管理更多的微服务;
  3. 如果 yml 文件中配置了 server.servlet.context-path,则必须配上 path 属性。否则后面调用这个接口会出现 404 问题;
  4. 方法参数必须加 @RequestParam 注解(否则,后面调用该接口会报 405 错误),若是 POST 请求方式,需要根据情况加 @RequestBody 注解;
  5. contextId 的作用参考这篇博客:@FeignClient注解 中属性 contextId使用
  • 新建RemoteUserFallbackFactory.java降级实现
/**
 * @Author: ezhang
 * @Date: Created in 2022/1/9 18:04
 * @Description: 用户服务降级处理
 */
// 注意不要忘记这个注解
@Component
public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserService> {

    @Override
    public RemoteUserService create(final Throwable throwable) {
        return new RemoteUserService()
        {
            @Override
            public Map<String, Object> getUserInfo(int userId) {
                Map<String, Object> map = new HashMap<>();
                map.put("code", 500);
                map.put("msg", "获取用户信息失败");
                return map;
            }
        };
    }
}

3. 服务消费者 cloud-auth 配置

  • pom.xml 文件
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
    <groupId>com.ezhang</groupId>
    <artifactId>cloud-api</artifactId>
</dependency>

这里依赖 cloud-api

  • application.yml
server:
  port: 9203
  
spring:
  application:
    # 应用名称
    name: cloud-auth
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 127.0.0.1:8848
feign:
  hystrix:
    enabled: true

注意 feign.hystrix.enabled: true 不要忘了,否则进不去 RemoteUserFallbackFactory 回调。

  • TestController.java
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private RemoteUserService remoteUserService;

    /**
     * 获取用户信息
     */
    @GetMapping("/getUserInfo")
    public Map<String, Object> getUserInfo(int userId)
    {
        return remoteUserService.getUserInfo(userId);
    }
}
  • 启动类添加 @EnableFeignClients 注解
@SpringBootApplication
@EnableFeignClients
public class CloudAuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudAuthApplication.class, args);
    }
}

4. 启动测试

启动上面的服务提供者和服务消费者,Nacos 控制台显示注册成功。

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

这次我们访问地址:http://localhost:9203/test/getUserInfo?userId=1

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

调用成功。

我们再试下调用失败的情况,关掉服务提供者 cloud-system,再次访问上面的地址

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

进入到了失败回调中,没有问题。

负载均衡

Feign默认集成了RibbonNacos也很好的兼容了Feign,默认实现了负载均衡的效果。

负载均衡,需要至少两个服务提供者 cloud-system,我们来简单的验证一下:

1. 修改一下服务提供者的接口代码,返回端口号

@RestController
@RequestMapping("/user")
public class UserController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/getUserInfo")
    public Map<String, Object> getUserInfo(int userId){
        Map<String, Object> map = new HashMap<>();
        map.put("code", 200);
        map.put("serverPort", serverPort);
        return map;
    }
}

浏览器访问老地址 http://localhost:9203/test/getUserInfo?userId=2

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

2. 复制一份 cloud-system,修改端口号为 9212

这里为了方便,我将采用取巧的方式去启动一个新的 cloud-system

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

这里我们直接在 idea 中复制一份配置出来,然后修改 VM 参数端口号为 9212,点击确定就可以启动了

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

Nacos 控制台可以看到,两个服务提供者已经注册好了

3. 访问测试

再次访问地址: http://localhost:9203/test/getUserInfo?userId=2

微服务系列:服务调用 Spring Cloud 之 OpenFeign 详细入门

默认负载均衡策略是轮询,交替展示,当然策略也可以配置和自定义。

注:

本文完整代码地址:cloud-feign (gitee.com)

PS:都看到这里了,点个赞吧,彦祖!

上一篇:SpringCloud(1-5) OpenFeign


下一篇:OpenFeign源码分析二