概述
目的:解决微服务调用问题。如何从微服务A调用微服务B提供的接口。
特性:
- 声明式语法,简化接口调用代码开发。
- 像调用本地方法一样调用其他微服务中的接口。
- 集成了Eureka服务发现,可以从注册中心中发现微服务。
- 集成了Spring Cloud LoadBalancer,提供客户端负载均衡。
- 从调用发起方控制微服务调用的请求时间,防止服务雪崩。
使用Feign进行微服务调用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
导入依赖
<!-- springboot web start -->
<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>
<dependency>
<groupId>com.qf</groupId>
<artifactId>common</artifactId>
</dependency>
<!-- feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 引入nacos配置中心依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
yml文件
spring:
application:
name: feign-demo #跟配置的前缀名字
cloud:
nacos:
server-addr: 127.0.0.1:8848
config:
file-extension: yaml
profiles:
active: dev #跟配置的后缀 设备名匹配
logging:
level:
com.qf.feignconsumer.feign.UserFeignClient: debug
server:
port: 9090
feign:
client:
config:
default:
# 建立连接的超时时间
connectTimeout: 5000
# 发送请求后等待接口响应结果的超时时间
readTimeout: 3000
创建FeignClient接口
package com.qf.feignconsumer.feign;
import com.qf.common.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient("service-user")
public interface UserFeignClient {
/**
* 注意,如果少加了@RequestParam,会抛出如下异常
* Caused by: java.lang.IllegalStateException: Method has too many Body parameters
* @param pagenum
* @param pagesize
* @return
* @throws InterruptedException
*/
@GetMapping("/user/page")
public List<User> getUserByPage(@RequestParam("pagenum") Integer pagenum,@RequestParam("pagesize")Integer pagesize) throws InterruptedException;
@GetMapping("/user/getall")
public List<User> getAll();
@PostMapping("/user/update")
public User updateUser(@RequestBody User user);
@DeleteMapping("/user/delete/{id}")
public User deleteUser(@PathVariable("id") Integer id);
}
使用主启动类
package com.qf.feignconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient//注册到nacos中
@EnableFeignClients//注意:使用feign时 需要添加该注解
public class FeignApp9090 {
public static void main(String[] args) {
SpringApplication.run(FeignApp9090.class,args);
}
}
FeignController
package com.qf.feignconsumer.controller;
import com.qf.common.entity.User;
import com.qf.common.vo.ResultVo;
import com.qf.feignconsumer.feign.ProviderFeigngClient;
import com.qf.feignconsumer.feign.UserFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class FeignController {
@Autowired
UserFeignClient userFeignClient;
@Autowired
ProviderFeigngClient providerFeigngClient;
@GetMapping("/u/test4")
public ResultVo utest4() throws InterruptedException {
List<User> userByPage = userFeignClient.getUserByPage(1, 2);
return ResultVo.ok(1,"ok",userByPage);
}
@GetMapping("/echo")
public ResultVo echo(String msg){
String echo = providerFeigngClient.echo(msg);
return ResultVo.ok(1,"asd",echo);
}
@GetMapping("/u/test1")
public ResultVo utest1(){
//使用feignclient发起微服务用
List<User> users = userFeignClient.getAll();
System.out.println(users);
return ResultVo.ok(1,"ok",users);
}
@GetMapping("/u/test2")
public ResultVo utest2(){
//使用feignclient发起微服务用
System.out.println("utest2");
User user = new User(100, "luffy", "123");
User user1 = userFeignClient.updateUser(user);
return ResultVo.ok(1,"ok",user1);
}
@GetMapping("/u/test3")
public ResultVo utest3(){
//使用feignclient发起微服务用
System.out.println("utest3");
User user1 = userFeignClient.deleteUser(100);
return ResultVo.ok(1,"ok",user1);
}
}
日志配置类
package com.qf.feignconsumer.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level liver(){
/*
* NONE:默认的,不显示任何日志
* BASIC:仅记录请求方法、RUL、响应状态码及执行时间
* HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
* FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
*/
return Logger.Level.FULL;
}
}
提供服务方的controller
package com.qf.userprovider.controller;
import com.qf.common.entity.User;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/page")
public List<User> getUserByPage(@RequestParam("pagenum") Integer pagenum,@RequestParam("pagesize")Integer pagesize) throws InterruptedException {
User user = new User(1, "zhangsan1", "1234567");
User user2 = new User(2, "lisi", "12345");
Thread.sleep(5000);
List<User> users = Arrays.asList(user, user2);
return users;
}
@GetMapping("/getall")
public List<User> getAll(){
User user1 = new User(1, "zhangsan", "12345677");
User user2 = new User(2, "lisi", "asdasdaas");
List<User> users = Arrays.asList(user1, user2);
return users;
}
@PostMapping("/update")
public User updateUser(@RequestBody User user){
System.out.println(user);
//根据id更新用户
user.setPassword("88889888");
return user;
}
@DeleteMapping("/delete/{id}")
public User deleteUser(@PathVariable("id") Integer id){
System.out.println("要删除用户的id="+id);
//去数据库里删除
User luffy = new User(id, "luffy", "12345");
return luffy;
}
}
Feign负载均衡
注册中心中观察,发现Micro1微服务在Eureka注册中心中有两个服务节点
当发起feign调用时,究竟调用的是哪个节点呢?
@RestController
@RequestMapping("/user")
public class UserController {
@Value("${server.port}")
String port;
@GetMapping("/findAll")
List<User> findAll(){
System.out.println(port);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
List<User> users = Arrays.asList(new User(1,"zhangsan", "123456", 29), new User(2,"lisi", "12345", 19));
return users;
}
}
Feign使用的是轮询负载均衡算法,当有多个节点时,采用轮询的方式依次调用。
Feign调用超时时间设置
feign:
client:
config:
default:
# 建立连接的超时时间
connectTimeout: 5000
# 发送请求后等待接口响应结果的超时时间
readTimeout: 10000
将微服务的接口响应时间延长,观察接口调用,超时抛异常
@GetMapping("/findAll")
List<User> findAll(){
System.out.println(port);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
List<User> users = Arrays.asList(new User(1,"zhangsan", "123456", 29), new User(2,"lisi", "12345", 19));
return users;
}
添加全局异常处理
@RestControllerAdvice
public class ExHandler {
@ExceptionHandler(Exception.class)
public String handleEx(Exception e){
return e.getMessage();
}
}
Feign配置日志
添加一个配置类
@Configuration
public class FeignConfig {
@Bean
public Logger.Level liver(){
/*
* NONE:默认的,不显示任何日志
* BASIC:仅记录请求方法、RUL、响应状态码及执行时间
* HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
* FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
*/
return Logger.Level.FULL;
}
}
yml文件中为FeignClient接口配置日志级别
logging:
level:
com.qf.feign.XXXClient: debug