在微服务架构中很多功能都需要调用多个服务才能完成某一项功能,一个成熟的微服务集群,内部调用必然依赖一个好的 RPC 框架,比如:基于 Http 协议的 Feign
,基于私有 tcp 协议的 Dubbo
。
- 微服务系列:Spring Cloud Alibaba 之 Nacos 注册中心
- 微服务系列:Spring Cloud Alibaba 之 Nacos 配置中心
- 微服务系列:Spring Cloud Alibaba 之 Nacos 集群搭建
继上面几篇 Spring Cloud Alibaba 之 Nacos
系列学习结束后,今天我们开始学习微服务系列中的 服务调用 Feign
。
话不多说,开始今天的学习。
基本介绍
1. Feign 是什么
Feign
是Spring Cloud Netflix
组件中的轻量级Restful
的 HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了Ribbon
和RestTemplate
, 实现了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
调用,我们还需要一个服务提供者和一个服务消费者,本次演示代码模块如下:
其中:
-
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
地址
好的,没有问题。
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);
}
注意事项:
- 必须加
@FeignClient
声明; - 其他 value 属性(等同于 name 属性)的值是 yml 文件中
spring.application.name
的值,就是注册到注册中心的实例名字,这里建议用全局变量来填写,方便管理更多的微服务; - 如果 yml 文件中配置了
server.servlet.context-path
,则必须配上 path 属性。否则后面调用这个接口会出现 404 问题; - 方法参数必须加
@RequestParam
注解(否则,后面调用该接口会报 405 错误),若是 POST 请求方式,需要根据情况加@RequestBody
注解; -
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
控制台显示注册成功。
这次我们访问地址:http://localhost:9203/test/getUserInfo?userId=1
调用成功。
我们再试下调用失败的情况,关掉服务提供者 cloud-system
,再次访问上面的地址
进入到了失败回调中,没有问题。
负载均衡
Feign
默认集成了Ribbon
,Nacos
也很好的兼容了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
2. 复制一份 cloud-system
,修改端口号为 9212
这里为了方便,我将采用取巧的方式去启动一个新的 cloud-system
。
这里我们直接在 idea 中复制一份配置出来,然后修改 VM 参数端口号为 9212,点击确定就可以启动了
Nacos
控制台可以看到,两个服务提供者已经注册好了
3. 访问测试
再次访问地址: http://localhost:9203/test/getUserInfo?userId=2
默认负载均衡策略是轮询,交替展示,当然策略也可以配置和自定义。
注:
本文完整代码地址:cloud-feign (gitee.com)
PS:都看到这里了,点个赞吧,彦祖!