概述
目的:解决微服务调用问题。如何从微服务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:8848config:file-extension: yamlprofiles: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 {@AutowiredUserFeignClient userFeignClient;@AutowiredProviderFeigngClient 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 {@Beanpublic 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 {@Beanpublic Logger.Level liver(){/** NONE:默认的,不显示任何日志* BASIC:仅记录请求方法、RUL、响应状态码及执行时间* HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息* FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据*/return Logger.Level.FULL;}
}
yml文件中为FeignClient接口配置日志级别
logging:level:com.qf.feign.XXXClient: debug