在分布式微服务架构中,各个服务之间独立运行,通过轻量级http请求相互调用,那么具体该如何调用呢?使用HttpClient吗?当然不是,那太麻烦了,使用的是OpenFeign
1.Open-Feign介绍
介绍Open-Feign之前,先分别介绍一下Ribbon、Feign、RestTemplate
- Ribbon
- Netflix开源负载均衡组件
- Feign
- Spring Cloud组件中的一个轻量级的声明式HTTP客户端,内置了Ribbon用来实现负载均衡,去调用服务注册中心的服务。
- RestTemplate
- Spring 提供的用于访问Http服务的客户端,RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率
RestTemplate 本文不做过多的介绍和演示,实际的微服务项目中使用也很少。感兴趣的同学可以自行查询了解
值得一提的是RestTemplate可以结合@LoadBalanced
注解来实现实现负载均衡,既然如此;那为什么还需要Feign呢?
虽然可以利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。如下这段调用另外一个服务接口获取数据的代码
String serverURL = "http://localhost";
int userId = 1;
String result = restTemplate.getForObject(serverURL + "/user/" + userId , String.class);
但在开发中由于调用某一个接口的位置不止一处;依赖的服务也不止一个;往往一个接口会被多处调用,比如在项目中需要经常调用获取用户信息的接口,每次都写这么一段相似的代码,有点麻烦;万一业务迅速迭代,接口地址或参数列表发生变化;就需要做大量修改,这显然是不合适的。
因此,在开发中通常都会针对每个微服务自行封装一些客户端来保证对这些依赖服务调用,在Feign的实现下;开发者只需要创建一个接口并使用注解的方式来配置它即可完成与服务方的接口的绑定。
Feign是由Netflix(奈飞,美国的优爱腾)开发并开源的,并没有很好的支持Spring,因此Spring在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等。也是就是OpenFeign。OpenFeign的@FeignClient
可以解析SpringMVC的@RequestMapping
注解下的接口,以实现Feign客户端和接口的绑定;并通过动态代理的方式产生实现类,实现类中做负载均衡以及请求调用。
以上的理论和概念如果有同学看不懂或不是很明白,没有关系,这些介绍只是让读者同学对OpenFeign有一个大概的了解;结合下面的代码演示,相信同学们就可以理解
2.演示
在前文Nacos 服务注册中心中我已经在项目spring-cloud-alibaba-demo
中创建了commodity-provider-8081
,为了演示服务调用;我再创建一个Moduleorder-provider-7001
Module创建完成后引入基础依赖,父项目和版本选择可阅读前文Nacos 服务注册中心
<!-- 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>
<!-- 监控当前应用的健康状态 Nacos 依赖于它 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
编写yml配置文件 , 配置项的详解可以阅读前文Nacos 服务注册中心
server:
port: 7001
spring:
application:
name: order-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: '*'
编写主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class OrderProvider7001 {
public static void main(String[] args) {
SpringApplication.run(OrderProvider7001.class,args);
}
}
编写一个简单的业务测试Controller
@RestController
public class OrderController {
@Value("${server.port}")
private Integer port;
@Value("${spring.application.name}")
private String applicationName;
@GetMapping("/order/hello")
public String hello() {
return "服务: " + applicationName + " 端口: " + port;
}
}
commodity-provider-8081
中的CommodityController.java
也改成了类似的逻辑,如下图
启动commodity-provider-8081
和order-provider-7001
打开nacos的管理界面http://localhost:8848/nacos
两个服务均已注册成功
两个服务均可正常访问
2.1 服务调用
在此,我使用order-provider-7001
中的接口来调用commodity-provider-8081
中的接口演示微服务调用。
2.1.1 commodity-provider-8081 需要的改动
在商品服务commodity-provider-808
的CommodityController
中增加商品查询接口,用Map模拟数据库查询
@GetMapping("/commodity/{id}")
public String getCommodity(@PathVariable Integer id) {
Map<Integer, String> map = new HashMap<>();
map.put(1,"手机");
map.put(2,"空调");
map.put(3,"冰箱");
return "服务: " + applicationName + " 端口: " + port + " 商品信息 => 商品Id :" + id + " , 商品名称 : " + map.get(id);
}
重启之后,测试新增加的接口可以正常访问
2.1.2 order-provider-7001 需要的改动
首先引入Open Feign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在order-provider-7001
中创建CommodityService
@Component
@FeignClient("commodity-provider")
public interface CommodityService {
@GetMapping("/commodity/{id}")
String getCommodity(@PathVariable("id") Integer id);
}
@FeignClient
声明该类是一个可以发送Http请求的客户端,其中的commodity-provider
表示该客户端将会请求在服务注册中心的哪个服务
也就是说@FeignClient("commodity-provider")
指定该类将会访问注册在Nacos中的商品服务(commodity-provider)
@GetMapping("/commodity/{id}")
该注解是将该方法与商品服务中的请求进行绑定,调用此接口方法就是在请求商品服务中的/commodity/{id}
接口
如下代码,我在原来的OrderController中增加请求
@Resource
private CommodityService commodityService;
@GetMapping("/order/commodity/{id}")
public String getCommodity(@PathVariable("id") Integer id) {
// <A> 请求商品服务
return commodityService.getCommodity(id);
}
当在<A>
处调用该方法时候;OpenFeign会使用动态代理自动帮我们请求商品服务中的接口,并将请求结果以接口返回值的方式传递过来。
最后,在主启动类上添加注解@EnableFeignClients
开启openFeign客户端
启动两个服务
访问http://localhost:7001/order/commodity/1
可见订单服务调用商品服务成功。
3.负载均衡
3.1 什么是负载均衡?
在分布式微服务结构中,整个系统被划分为很多的不同的服务
在业务量很少的使用,可能每个服务只需要一个实例就够了,但是当业务高峰期的时候,比如双十一的时候,大量订单涌入;一个订单有时包含多个商品,因此商品服务的压力可能远远大于订单服务。这时候就需要对商品服务进行扩容,
如上图,商品服务扩容至两个;在订单服务调用商品服务的时候,这两个可以一起供外部调用,比如第一次请求商品服务1,第二次请求商品服务2,第三次再请求商品服务1,如此循环。这种负载均衡的策略叫轮询,也是负载均衡中的常用策略,OpenFeign中默认集成了负载负载均衡。
3.1 商品Module
为了演示负载均衡,我在项目中再创建一个商品服务commodity-provider-8082
,Module中的内容和commodity-provider-8081
一样,只是端口不同
然后分别启动三个服务
在Nacos中可以看到,商品服务有两个健康实例,点击右侧的详情,可见;这两个实例正是我们注册的commodity-provider-8081
和commodity-provider-8082
两个服务
访问订单服务http://localhost:7001/order/commodity/1
,该请求会调用商品服务
负载均衡调用成功