87--spring cloud (Feign声明式客户端接口/集成)
Feign 声明式客户端接口
Fegin是一个集成工具,它集成了
- 远程调用
- ribbon
- hystrix
微服务应用中,ribbon 和 hystrix 总是同时出现,feign 整合了两者,并提供了声明式消费者客户端
用 feign 代替 hystrix+ribbon
创建项目-远程调用
导入依赖pom.xml
actuator
,web
,netflix-eureka-client
,netflix-hystrix
,openfeign
需要添加 sp01-commons 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>sp09-feign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sp09-feign</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR7</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>sp01-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
application:
name: feign
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
主程序添加 @EnableFeignClients
package cn.tedu.sp09;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableCircuitBreaker //启动断路器
@EnableFeignClients //启用feign客户端
@SpringBootApplication
public class Sp09FeignApplication {
public static void main(String[] args) {
SpringApplication.run(Sp09FeignApplication.class, args);
}
}
feign 声明式客户端
feign 利用了我们熟悉的 spring mvc 注解来对接口方法进行设置,降低了我们的学习成本。
通过这些设置,feign可以拼接后台服务的访问路径和提交的参数例如:
@FeignClient(value = "user-service") public interface UserFeignClient { @GetMapping("/{userId}/score") JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score); }
当这样调用该方法:
@Autowired private UserFeignClient userFeignClient; //在控制层进行调用声明式接口 @GetMapping("/user-service/{userId}/score") public JsonResult addScore(@PathVariable Integer userId,Integer score){ log.info("调用远程用户服务,新增积分"); return userFeignClient.addScore(userId,score); }
那么 feign 会向服务器发送请求:
http://用户微服务/7/score?score=100
- 注意:如果 score 参数名与变量名不同,需要添加参数名设置:
@GetMapping("/{userId}/score") JsonResult addScore(@PathVariable Integer userId, @RequestParam("score") Integer s);
ItemFeignClient
package cn.tedu.sp09.feign;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp09.feign.impl.ItemFeignClientFB;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@FeignClient(name = "item-service" ,fallback = ItemFeignClientFB.class )
public interface ItemFeignClient {
@GetMapping("/{orderId}")
JsonResult<List<Item>> getItems(@PathVariable String orderId);
@PostMapping("/decreaseNumber") //post提交的参数要放在请求协议体中 使用参数@RequestBody
JsonResult decrease(@RequestBody List<Item> items);
}
UserFeignClient
注意,如果请求参数名与方法参数名不同,@RequestParam
不能省略,并且要指定请求参数名:
@RequestParam("score") Integer s
package cn.tedu.sp09.feign;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp09.feign.impl.UserFeignClientFB;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "user-service",fallback = UserFeignClientFB.class)
public interface UserFeignClient {
@GetMapping("/{userId}")
JsonResult<User> getUser(@PathVariable Integer userId);
@GetMapping("/{userId}/score")
JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
}
OrderFeignClient
package cn.tedu.sp09.feign;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp09.feign.impl.OrderFeignClientFB;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "order-service",fallback = OrderFeignClientFB.class)
public interface OrderFeignClient {
@GetMapping("/{orderId}")
JsonResult<Order> getOrder(@PathVariable String orderId);
@GetMapping("/")
JsonResult addOrder();
}
FeignController
package cn.tedu.sp09.controller;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp09.feign.ItemFeignClient;
import cn.tedu.sp09.feign.OrderFeignClient;
import cn.tedu.sp09.feign.UserFeignClient;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@Slf4j
public class FeignController {
@Autowired
private ItemFeignClient itemFeignClient;
@Autowired
private UserFeignClient userFeignClient;
@Autowired
private OrderFeignClient orderFeignClient;
@GetMapping("/item-service/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId){
log.info("调用远程商品服务,获取订单列表");
return itemFeignClient.getItems(orderId);
}
@PostMapping("/item-service/decreaseNumber")
public JsonResult decreaseNumber(List<Item> items){
log.info("调用远程商品服务,减少库存");
return itemFeignClient.decrease(items);
}
@GetMapping("/user-service/{userId}")
public JsonResult<User> getUser(@PathVariable Integer userId){
log.info("调用远程用户服务,获取用户信息");
return userFeignClient.getUser(userId);
}
@GetMapping("/user-service/{userId}/score")
public JsonResult addScore(@PathVariable Integer userId,Integer score){
log.info("调用远程用户服务,新增积分");
return userFeignClient.addScore(userId,score);
}
@GetMapping("/order-service/{orderId}")
public JsonResult<Order> getOrder(@PathVariable String orderId){
log.info("调用远程订单服务,获取订单");
return orderFeignClient.getOrder(orderId);
}
@GetMapping("/order-service")
public JsonResult addOrder(){
log.info("调用远程订单服务,新增订单");
return orderFeignClient.addOrder();
}
}
调用流程
feign + ribbon 负载均衡和重试
无需额外配置,feign 默认已启用了 ribbon 负载均衡和重试机制。可以通过配置对参数进行调整
重试的默认配置参数:
ConnectTimeout=1000 ReadTimeout=1000 MaxAutoRetries=0 MaxAutoRetriesNextServer=1
可以自己配置重试参数:
ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000
item-service:
ribbon:
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 2
ConnectTimeout: 1000
ReadTimeout: 500
Feign 集成 Hystrix
feign 启用 hystrix
feign 默认没有启用 hystrix,不推荐启用Hystrix, 如果有特殊需求,可以添加配置,启用 hystrix
添加 Hystrix 完整依赖
添加注解 @EnableCircuitBreaker
feign.hystrix.enabled=true
application.yml 添加配置
feign:
hystrix:
enabled: true
启用 hystrix 后,访问服务
默认1秒会快速失败,没有降级方法时,会显示白板页
可以添加配置,暂时减小降级超时时间,以便后续对降级进行测试
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 500
feign + hystrix 降级
feign 远程接口中指定降级类
远程调用失败, 会执行降级类中的代码
添加降级代码
// 声明式客户端接口添加注解属性
@FeignClient(name="item-service", fallback=降级类.class)
public interface ItemFeignClient {
}
//定义降级类,实现声明式客户端接口
public class 降级类 implements ItemFeignClient {
}
ItemFeignClient
@FeignClient(name = "item-service" ,fallback = ItemFeignClientFB.class )
public interface ItemFeignClient {
............
}
UserFeignClient
@FeignClient(value = "user-service",fallback = UserFeignClientFB.class)
public interface UserFeignClient {
........
}
OrderFeignClient
@FeignClient(name = "order-service",fallback = OrderFeignClientFB.class)
public interface OrderFeignClient {
.......
}
降级类
降级类需要实现远程接口
ItemFeignClientFB
package cn.tedu.sp09.feign.impl;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp09.feign.ItemFeignClient;
import cn.tedu.web.util.JsonResult;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ItemFeignClientFB implements ItemFeignClient {
@Override
public JsonResult<List<Item>> getItems(String orderId) {
return JsonResult.err().msg("获取商品列表失败");
}
@Override
public JsonResult decrease(List<Item> items) {
return JsonResult.err().msg("减少商品库存失败");
}
}
UserFeignClientFB
package cn.tedu.sp09.feign.impl;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp09.feign.UserFeignClient;
import cn.tedu.web.util.JsonResult;
import org.springframework.stereotype.Component;
@Component
public class UserFeignClientFB implements UserFeignClient {
@Override
public JsonResult<User> getUser(Integer userId) {
return JsonResult.err().msg("获取用户失败");
}
@Override
public JsonResult addScore(Integer userId, Integer score) {
return JsonResult.err().msg("新增积分失败");
}
}
OrderFeignClientFB
package cn.tedu.sp09.feign.impl;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import org.springframework.stereotype.Component;
@Component
public class OrderFeignClientFB implements OrderFeignClient{
@Override
public JsonResult<Order> getOrder(String orderId) {
return JsonResult.err().msg("获取订单失败");
}
@Override
public JsonResult addOrder() {
return JsonResult.err().msg("新增订单失败");
}
}
启动服务,访问测试
Feign + hystrix 监控和熔断测试
主程序添加 @EnableCircuitBreaker
package cn.tedu.sp09;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableCircuitBreaker //启动断路器
@EnableFeignClients //启用feign客户端
@SpringBootApplication
public class Sp09FeignApplication {
public static void main(String[] args) {
SpringApplication.run(Sp09FeignApplication.class, args);
}
}
配置 actuator,暴露 hystrix.stream
监控端点
actuator 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yml 暴露 hystrix.stream
端点
m.e.w.e.i=hystrix.stream
management:
endpoints:
web:
exposure:
include: hystrix.stream
hystrix dashboard
-
重启项目,调用后台服务
http://localhost:3001/item-service/45ty3t3t4
-
查看监控端点,有没有数据
-
访问仪表盘,开启监控
熔断测试
- 用 ab 工具,以并发50次,来发送20000个请求
ab -n 20000 -c 50 http://localhost:3001/item-service/35
- 断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法