Feign
集成工具(功能整合)远程调用:声明式客户端
ribbon 负载均衡和重试
hystrix 降级和熔断
feign 声明式客户端接口
微服务应用中,ribbon 和 hystrix 总是同时出现,feign 整合了两者,并提供了声明式消费者客户端用 feign 代替 hystrix+ribbon
只需要声明一个抽象接口,就可以通过接口做远程调用,不需要再使用 RestTemplate 来调用// 调用远程的商品服务,获取订单的商品列表
// 通过注解,配置:
// 1. 调用哪个服务
// 2. 调用服务的哪个路径
// 3. 向路径提交什么参数数据
@FeignClient(name="item-service")
public interface ItemClient {
@GetMapping("/{orderId}")
JsonResult> getItems(@PathVariable String orderId);
}
在这里使用 @GetMapping("/{orderId}"), 指定的是向远程服务调用的路径
新建 sp09-feign 项目
pom.xml需要添加 sp01-commons 依赖
application.ymlspring:
application:
name: feign
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
主程序添加 @EnableDiscoveryClient 和 @EnableFeignClientspackage cn.tedu.sp09;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Sp09FeignApplication {
public static void main(String[] args) {
SpringApplication.run(Sp09FeignApplication.class, args);
}
}
feign 声明式客户端
feign 利用了熟悉的 spring mvc 注解来对接口方法进行设置,降低了我们的学习成本。
通过这些设置,feign可以拼接后台服务的访问路径和提交的参数
例如:@GetMapping("/{userId}/score")
JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
当这样调用该方法:service.addScore(7, 100);
那么 feign 会向服务器发送请求:http://用户微服务/7/score?score=100注意:如果 score 参数名与变量名不同,需要添加参数名设置:@GetMapping("/{userId}/score")
JsonResult addScore(@PathVariable Integer userId, @RequestParam("score") Integer s
ItemClientpackage cn.tedu.sp09.feign;
import cn.tedu.sp01.pojo.Item;
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;
//根据服务id,从注册表得到主机地址
@FeignClient(name = "item-service")
public interface ItemClient {//封装了RestTemplate
@GetMapping("{orderId}")//(反向)将请求拼接发送
JsonResult> getItems(@PathVariable String orderId);
@PostMapping("/decreaseNumber")//(反向)将请求拼接发送
JsonResult> decreaseNumber(@RequestBody List items);
}
UserClientpackage cn.tedu.sp09.feign;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
//根据服务id,从注册表得到主机地址
@FeignClient(name = "user-service")
public interface UserClient {//封装了RestTemplate
@GetMapping("{userId}")//(反向)将请求拼接发送
JsonResult getUser(@PathVariable Integer userId);
//....../8/score?score=1000
@GetMapping("/{userId}/score")//(反向)将请求拼接发送
JsonResult> addScore(@PathVariable Integer userId,
@RequestParam Integer score);//@RequestParam 不能省略
}
OrderClientpackage cn.tedu.sp09.feign;
import cn.tedu.sp01.pojo.Order;
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;
//根据服务id,从注册表得到主机地址
@FeignClient(name = "order-service")
public interface OrderClient {//封装了RestTemplate
@GetMapping("{orderId}")//(反向)将请求拼接发送
JsonResult getOrder(@PathVariable String orderId);
@GetMapping("/")//(反向)将请求拼接发送
JsonResult> addOrder();
}
FeignControllerpackage 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.ItemClient;
import cn.tedu.sp09.feign.OrderClient;
import cn.tedu.sp09.feign.UserClient;
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 ItemClient itemClient;
@Autowired
private UserClient userClient;
@Autowired
private OrderClient orderClient;
//-----------item-service
@GetMapping("/item-service/{orderId}")
public JsonResult> getItems(@PathVariable String orderId){
return itemClient.getItems(orderId);
}
@PostMapping("/item-service/decreaseNumber")
public JsonResult> decreaseNumber(@RequestBody List items){
return itemClient.decreaseNumber(items);
}
//-----------user-service
@GetMapping("/user-service/{userId}")
public JsonResult getUser(@PathVariable Integer userId){
return userClient.getUser(userId);
}
@GetMapping("/user-service/{userId}/score")
public JsonResult> addScore(@PathVariable Integer userId,Integer score){
return userClient.addScore(userId, score);
}
//-----------order-service
@GetMapping("/order-service/{orderId}")
public JsonResult getOrder(@PathVariable String orderId){
return orderClient.getOrder(orderId);
}
@GetMapping("/order-service")
public JsonResult> addOrder(){
return orderClient.addOrder();
}
}
调用流程
启动服务,并访问测试
Feign 集成 Ribbon 负载均衡和重试无需额外配置,feign 默认已启用了 ribbon 负载均衡和重试机制。可以通过配置对参数进行调整
重试的默认配置参数:ConnectTimeout=1000
ReadTimeout=1000
MaxAutoRetries=0
MaxAutoRetriesNextServer=1
application.yml 配置 ribbon 超时和重试ribbon.xxx 全局配置
item-service.ribbon.xxx 对特定服务实例的配置spring:
application:
name: feign
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
# 调整 ribbon 的重试次数
# 针对所有服务的通用配置
ribbon:
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 2
ConnectTimeout: 1000
ReadTimeout: 500
#只针对 item-service这一个服务有效,对其它服务不应用这个配置
item-service:
ribbon:
MaxAutoRetries: 2
启动服务,访问测试
Feign 集成 Hystrix降级和熔断
Feign默认不启用Hystrix,使用feign时不推荐启用Hystrix
启用Hystrix基础配置:hystrix起步依赖
yml中配置启用hystrix
启动类添加注解 @EnableCircuitBreaker
application.yml 添加配置feign:
hystrix:
enabled: true
添加配置,暂时减小降级超时时间,以便后续对降级进行测试......
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 500
feign + hystrix 降级
feign 远程接口中指定降级类ItemFeignService...
@FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
public interface ItemFeignService {
...UserFeignService...
@FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
public interface UserFeignService {
...OrderFeignService...
@FeignClient(name="order-service",fallback = OrderFeignServiceFB.class)
public interface OrderFeignService {
...
降级类
降级类需要实现声明式客户端接口,在实现的抽象方法中添加降级代码,
降级类需要添加 @Component 注解ItemFeignServiceFBpackage cn.tedu.sp09.service;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;
@Component
public class ItemFeignServiceFB implements ItemFeignService {
@Override
public JsonResult> getItems(String orderId) {
return JsonResult.err("无法获取订单商品列表");
}
@Override
public JsonResult decreaseNumber(List items) {
return JsonResult.err("无法修改商品库存");
}
}UserFeignServiceFBpackage cn.tedu.sp09.service;
import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
@Component
public class UserFeignServiceFB implements UserFeignService {
@Override
public JsonResult getUser(Integer userId) {
return JsonResult.err("无法获取用户信息");
}
@Override
public JsonResult addScore(Integer userId, Integer score) {
return JsonResult.err("无法增加用户积分");
}
}OrderFeignServiceFBpackage cn.tedu.sp09.service;
import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.web.util.JsonResult;
@Component
public class OrderFeignServiceFB implements OrderFeignService {
@Override
public JsonResult getOrder(String orderId) {
return JsonResult.err("无法获取商品订单");
}
@Override
public JsonResult addOrder() {
return JsonResult.err("无法保存订单");
}
}
启动服务,访问测试
feign + hystrix 监控和熔断测试
修改sp09-feign项目pom.xml 添加 hystrix 起步依赖feign 没有包含完整的 hystrix 依赖
右键点击项目,编辑起步依赖,添加hystrix依赖
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
主程序添加 @EnableCircuitBreakerpackage 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.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableCircuitBreaker
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Sp09FeignApplication {
public static void main(String[] args) {
SpringApplication.run(Sp09FeignApplication.class, args);
}
}
sp09-feign 配置 actuator,暴露 hystrix.stream 监控端点
actuator 依赖
查看pom.xml, 确认已经添加了 actuator 依赖
org.springframework.boot
spring-boot-starter-actuator
application.yml 暴露 hystrix.stream 端点management:
endpoints:
web:
exposure:
include: hystrix.stream
启动服务,查看监控端点
hystrix dashboard
启动 hystrix dashboard 服务,填入 feign 监控路径,开启监控
访问 http://localhost:4001/hystrix填入 feign 监控路径:
http://localhost:3001/actuator/hystrix.stream
访问微服务,以产生监控数据
熔断测试用 ab 工具,以并发50次,来发送20000个请求`ab -n 20000 -c 50 http://localhost:3001/item-service/35`断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法
order service 调用商品库存服务和用户服务
sp09-feign项目关闭,不再使用
修改 sp04-orderservice 项目,添加 feign,调用 item service 和 user servicepom.xml
application.yml
主程序
ItemFeignService
UserFeignService
ItemFeignServiceFB
UserFeignServiceFB
OrderServiceImpl
pom.xml
右键点击项目编辑起步依赖,添加以下依赖:
actuator
feign
hystrix
application.ymlribbon 重试和 hystrix 超时这里没有设置,采用了默认值spring:
application:
name: order-service
server:
port: 8201
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
feign:
hystrix:
enabled: true
management:
endpoints:
web:
exposure:
include: hystrix.stream
主程序package cn.tedu.sp04;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
//@EnableDiscoveryClient
//@SpringBootApplication
@EnableFeignClients
@SpringCloudApplication
public class Sp04OrderserviceApplication {
public static void main(String[] args) {
SpringApplication.run(Sp04OrderserviceApplication.class, args);
}
}
ItemFeignServicepackage cn.tedu.sp04.order.feignclient;
import java.util.List;
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 cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;
@FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
public interface ItemFeignService {
@GetMapping("/{orderId}")
JsonResult> getItems(@PathVariable String orderId);
@PostMapping("/decreaseNumber")
JsonResult decreaseNumber(@RequestBody List items);
}
UserFeignServicepackage cn.tedu.sp04.order.feignclient;
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;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
@FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
public interface UserFeignService {
@GetMapping("/{userId}")
JsonResult getUser(@PathVariable Integer userId);
@GetMapping("/{userId}/score")
JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
}
ItemFeignServiceFB获取商品列表的降级方法,模拟使用缓存数据package cn.tedu.sp04.order.feignclient;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;
@Component
public class ItemFeignServiceFB implements ItemFeignService {
@Override
public JsonResult> getItems(String orderId) {
if(Math.random()<0.5) {
return JsonResult.ok().data(
Arrays.asList(new Item[] {
new Item(1,"缓存aaa",2),
new Item(2,"缓存bbb",1),
new Item(3,"缓存ccc",3),
new Item(4,"缓存ddd",1),
new Item(5,"缓存eee",5)
})
);
}
return JsonResult.err("无法获取订单商品列表");
}
@Override
public JsonResult decreaseNumber(List items) {
return JsonResult.err("无法修改商品库存");
}
}
UserFeignServiceFB获取用户信息的降级方法,模拟使用缓存数据package cn.tedu.sp04.order.feignclient;
import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
@Component
public class UserFeignServiceFB implements UserFeignService {
@Override
public JsonResult getUser(Integer userId) {
if(Math.random()<0.4) {
return JsonResult.ok(new User(userId, "缓存name"+userId, "缓存pwd"+userId));
}
return JsonResult.err("无法获取用户信息");
}
@Override
public JsonResult addScore(Integer userId, Integer score) {
return JsonResult.err("无法增加用户积分");
}
}
OrderServiceImplpackage cn.tedu.sp04.order.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp01.service.OrderService;
import cn.tedu.sp04.order.feignclient.ItemFeignService;
import cn.tedu.sp04.order.feignclient.UserFeignService;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private ItemFeignService itemService;
@Autowired
private UserFeignService userService;
@Override
public Order getOrder(String orderId) {
//调用user-service获取用户信息
JsonResult user = userService.getUser(7);
//调用item-service获取商品信息
JsonResult> items = itemService.getItems(orderId);
Order order = new Order();
order.setId(orderId);
order.setUser(user.getData());
order.setItems(items.getData());
return order;
}
@Override
public void addOrder(Order order) {
//调用item-service减少商品库存
itemService.decreaseNumber(order.getItems());
//TODO: 调用user-service增加用户积分
userService.addScore(7, 100);
log.info("保存订单:"+order);
}
}
order-service 配置启动参数,启动两台服务器--server.port=8201
--server.port=8202
启动服务,访问测试
hystrix dashboard 监控 order service 断路器
hystrix dashboard 监控 order service 断路器
Turbine
hystrix + turbine 集群聚合监控
hystrix dashboard 一次只能监控一个服务实例,使用 turbine 可以汇集监控信息,将聚合后的信息提供给 hystrix dashboard 来集中展示和监控
新建 sp10-turbine 项目
pom.xml<?xml version="1.0" encoding="UTF-8"?>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
cn.tedu
sp10-turbine
0.0.1-SNAPSHOT
sp10-turbine
Demo project for Spring Boot
1.8
Hoxton.RELEASE
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-turbine
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
application.ymlspring:
application:
name: turbin
server:
port: 5001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
turbine:
app-config: order-service
cluster-name-expression: new String("default")
主程序
添加 @EnableTurbine 和 @EnableDiscoveryClient 注解package cn.tedu.sp10;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.turbine.EnableTurbine;
@EnableTurbine
@EnableDiscoveryClient
@SpringBootApplication
public class Sp10TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(Sp10TurbineApplication.class, args);
}
}
访问测试