0%

SpringCloud(六)Feign负载均衡

概述

Feigh是什么

Feign是一个声明式的Web服务客户端,使得编写Web服务客户端变得非常容易,只需要创建一个接口,然后再上面添加注解即可
参考官网

使用Feign能让编写WebService客户端更加简单,它的使用方法是定义一个接口,然后再上面添加注解,用时

  • 也支持JAX-RS标准的注解.Feign
  • 也支持可拔插式的编码器和解码器,
  • Spring CLoud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters.
  • Feign可以与Eureka和RIbbon组合使用以支持负载均衡.

Feign能干什么

Feign旨在使编写Java HTTP客户端变得更容易.

前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法,但在实际开发中,由于对依赖服务的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用.所以,Feign在此基础上做了进一步的封装,由他来帮助我们定义和实现依赖服务接口的定义.在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon 时,手动封装服务调用客户端的开发量.

这也是为了满足社区广大开发者面向接口编程的习惯。


实操

先回顾一下 clouddemo-consumer工程中 Controller如何调用服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package top.zhenganwen.clouddemoconsumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import top.zhenganwen.clouddemo.entity.Dept;

import java.util.List;

@RestController
@RequestMapping("dept")
public class DeptConsumerController {

private static final String URL_PREFIX= "http://CLOUDDEMO-DEPT";

@Autowired
private RestTemplate restTemplate;

@PostMapping("add")
public Boolean add(Dept dept) {
return restTemplate.postForObject(URL_PREFIX + "/dept/add", dept, Boolean.class);
}
@GetMapping("list")
public List<Dept> list() {
return restTemplate.getForObject(URL_PREFIX + "/dept/list", List.class);
}

@GetMapping("get/{id}")
public Dept get(@PathVariable Long id) {
return restTemplate.getForObject(URL_PREFIX + "/dept/get/" + id, Dept.class);
}
}

这里虽然 RestTemplat帮我们高度封装了 http操作,但是这并不符合原来 Controller+Service的编程习惯,我们还可以在此基础之上封装 http://CLOUDDEMO-DEPT/dept/addhttp细节,只关注接口和方法。于是Feign粉墨登场了

Feign就是对 Ribbon+RestTemplate的再封装,Feign集成了 Ribbon(通过依赖传递可以看出:引入 starter-feign会包含 starter-ribbon的依赖),自然也能够和 Rureka客户端无缝整合

工程搭建

新建工程 clouddemo-dept-feign(与 clouddemo-consumer对比来看):

  1. 引入依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!--eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!--entity:Dept-->
<dependency>
<groupId>top.zhenganwen</groupId>
<artifactId>clouddemo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
  1. 配置 eureka集群信息:
1
2
3
4
5
eureka:
client:
serviceUrl:
defaultZone: http://eureka1.com:7001/eureka/,http://eureka2.com:7002/eureka/,http://eureka3.com:7003/eureka/
register-with-eureka: false
  1. 不在编写 RestTemplat+Ribbon(@LoadBalanced)了,而是编写接口+注解(该类要能被 @ComponentScan扫描到):
1
2
3
4
5
6
7
8
9
10
11
12
@FeignClient(value = "CLOUDDEMO-DEPT")
public interface DeptService {

@PostMapping("/dept/add")
Boolean add(Dept dept);

@GetMapping("/dept/list")
List<Dept> list();

@GetMapping("/dept/get/{id}")
Dept get(@PathVariable("id") Long id);
}

其中 value="CLOUDDEMO-DEPT"是给Ribbon针对服务做负载均获取服务实例给 RestTemplate调用的(前面说过了:Feign集成了 Ribbon并高度封装了 RestTemplate

易错点

  1. 这里@PathVariable必须加上 value值,即使形参名 Long id和路径中的 {id}同名,也不可省略,否则之后会报错:PathVariable annotation was empty on param 0
  2. 该类要能被 @ComponentScan扫描到,否则 @EnableFeignClients不会起作用
  1. 在启动类上启用EurekaFeign组件:
1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class ClouddemoFeginApplication {

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

值得提醒的是 Feign不一定要和 Eureka一起使用,只不过本例使用了 Eureka

@EnableFeignClients只会使能被 @ComponentScan扫秒到的添加了 @FeignClient的接口起作用

  1. Feign客户端(DeptService)当做 bean注入使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package top.zhenganwen.clouddemo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import top.zhenganwen.clouddemo.entity.Dept;
import top.zhenganwen.clouddemo.service.DeptService;

import java.util.List;

@RestController
@RequestMapping("dept")
public class DeptController {

@Autowired
private DeptService deptService;

@PostMapping("add")
public Boolean add(Dept dept) {
return deptService.add(dept);
}
@GetMapping("list")
public List<Dept> list() {
return deptService.list();
}

@GetMapping("get/{id}")
public Dept get(@PathVariable Long id) {
return deptService.get(id);
}
}
  1. 依次启动 Eureka Server集群(3个)、clouddemo-providerclouddemo-provider2,最后启动 clouddemo-dept-feign,测试调用和负载均衡发现:localhost/dept/get/1可以正常调用负载均衡被启用并且是默认的轮询策略。

  2. 更改负载均衡策略(只需将相应的策略注册为 bean即可):

1
2
3
4
5
6
7
8
@Configuration
public class ConfigBeans {

@Bean
public IRule getRule() {
return new RandomRule();
}
}

​ 重启后再次测试发现策略已变为随机访问

对比前的 DeptConsumerController可以发现,封装了 RestTemplate之后是完完全全面向接口编程了。并且 Feign还集成了 Ribbon,有关 Ribbon的配置如 @EnableRibbon(value="CLOUDDEMO-DEPT",configuration=MyRule.class)对特定服务启用负载均衡,使用 MyRule定义的策略等都被隐藏了。

将接口迁移到common工程

上述虽然实现了 Feign的使用,隐藏了 RestTemplateRibbon的实现细节,但是如果将 DeptService写在 clouddemo-dept-feign中,如果其他消费者也要调 CLOUDDEMO-DEPT服务岂不是又要写一次。因此将 FeignClient写到 common工程以供复用才对。

  1. 引入 feign依赖:
1
2
3
4
5
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
  1. DeptService复制到common工程的 top.zhenganwen.clouddemo.service中,将 common工程重新打包安装到本地仓库
  2. 删除 clouddemo-dept-feign中的top.zhenganwen.clouddemo.service文件夹
  3. 修改dept-feign工程启动类注解:
    1. 添加 @ComponentScan("top.zhenganwen.clouddemo"),因为将 DeptService迁移到 common工程后,dept-feing工程的启动类上的注解 @SpringBootApplication是扫描不到的,因此要重新定义 @ComponentScan的扫描范围是 commondept-feign工程代码的根路径,即 top.zhenganwen.clouddemo
    2. 添加 @EnableFeignClients(basePackages = {"top.zhenganwen.clouddemo.service"}),指定 FeignClient所在包
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableEurekaClient
@ComponentScan("top.zhenganwen.clouddemo")
@EnableFeignClients(basePackages = {"top.zhenganwen.clouddemo.service"})
public class ClouddemoFeginApplication {

public static void main(String[] args) {
SpringApplication.run(ClouddemoFeginApplication.class, args);
}
}
  1. 再次测试

Feign和Ribbon的区别是什么

Feign只不过是集成了 Ribbon实现了负载均衡。但与 Ribbon相比,Feign只需要定义服务绑定接口且以声明式(接口方法添加 @RequestMapping@GetMapping等,方法形参要与服务提供方接口中的保持一致)的方法,优雅而简单地实现了服务的调用:

1
2
3
4
5
6
7
8
9
10
11
12
@FeignClient(value = "CLOUDDEMO-DEPT")
public interface DeptService {

@PostMapping("/dept/add")
Boolean add(Dept dept);

@GetMapping("/dept/list")
List<Dept> list();

@GetMapping("/dept/get/{id}")
Dept get(@PathVariable("id") Long id);
}
鼓励一下~