Feign
集成工具(性能整合)
- 近程调用:申明式客户端
- ribbon 负载平衡和重试
- hystrix 降级和熔断
feign 申明式客户端接口
微服务利用中,ribbon 和 hystrix 总是同时呈现,feign 整合了两者,并提供了 申明式消费者 客户端
- 用 feign 代替 hystrix+ribbon
只须要申明一个形象接口,就能够通过接口做近程调用,不须要再应用 RestTemplate 来调用
// 调用近程的商品服务,获取订单的商品列表
// 通过注解,配置:// 1. 调用哪个服务
// 2. 调用服务的哪个门路
// 3. 向门路提交什么参数数据
@FeignClient(name="item-service")
public interface ItemClient {@GetMapping("/{orderId}")
JsonResult<List<Item>> getItems(@PathVariable String orderId);
}
在这里应用 @GetMapping(“/{orderId}”),指定的是向近程服务调用的门路
新建 sp09-feign 我的项目
pom.xml
- 须要增加 sp01-commons 依赖
application.yml
spring:
application:
name: feign
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
主程序增加 @EnableDiscoveryClient
和 @EnableFeignClients
package 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
ItemClient
package 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<List<Item>> getItems(@PathVariable String orderId);
@PostMapping("/decreaseNumber")//(反向)将申请拼接发送
JsonResult<?> decreaseNumber(@RequestBody List<Item> items);
}
UserClient
package 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<User> getUser(@PathVariable Integer userId);
//....../8/score?score=1000
@GetMapping("/{userId}/score")//(反向)将申请拼接发送
JsonResult<?> addScore(@PathVariable Integer userId,
@RequestParam Integer score);//@RequestParam 不能省略
}
OrderClient
package 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<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.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<List<Item>> getItems(@PathVariable String orderId){return itemClient.getItems(orderId);
}
@PostMapping("/item-service/decreaseNumber")
public JsonResult<?> decreaseNumber(@RequestBody List<Item> items){return itemClient.decreaseNumber(items);
}
//-----------user-service
@GetMapping("/user-service/{userId}")
public JsonResult<User> 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<Order> getOrder(@PathVariable String orderId){return orderClient.getOrder(orderId);
}
@GetMapping("/order-service")
public JsonResult<?> addOrder(){return orderClient.addOrder();
}
}
调用流程
启动服务,并拜访测试
- http://eureka1:2001
- http://localhost:3001/item-service/35
http://localhost:3001/item-service/decreaseNumber
应用 postman,POST 发送以下格局数据:[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
- http://localhost:3001/user-service/7
- http://localhost:3001/user-service/7/score?score=100
- http://localhost:3001/order-service/123abc
- http://localhost:3001/order-service/
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
启动服务,拜访测试
http://localhost:3001/item-service/35
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
注解
ItemFeignServiceFB
package 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<List<Item>> getItems(String orderId) {return JsonResult.err("无奈获取订单商品列表");
}
@Override
public JsonResult decreaseNumber(List<Item> items) {return JsonResult.err("无奈批改商品库存");
}
}
UserFeignServiceFB
package 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<User> getUser(Integer userId) {return JsonResult.err("无奈获取用户信息");
}
@Override
public JsonResult addScore(Integer userId, Integer score) {return JsonResult.err("无奈减少用户积分");
}
}
OrderFeignServiceFB
package 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<Order> getOrder(String orderId) {return JsonResult.err("无奈获取商品订单");
}
@Override
public JsonResult addOrder() {return JsonResult.err("无奈保留订单");
}
}
启动服务,拜访测试
http://localhost:3001/item-service/35
feign + hystrix 监控和熔断测试
批改 sp09-feign 我的项目
pom.xml 增加 hystrix 起步依赖
- feign 没有蕴含残缺的 hystrix 依赖
右键点击我的项目, 编辑起步依赖, 增加 hystrix 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
主程序增加 @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.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
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yml 裸露 hystrix.stream
端点
management:
endpoints:
web:
exposure:
include: hystrix.stream
启动服务,查看监控端点
http://localhost:3001/actuator
hystrix dashboard
启动 hystrix dashboard 服务,填入 feign 监控门路,开启监控
拜访 http://localhost:4001/hystrix
- 填入 feign 监控门路:
http://localhost:3001/actuator/hystrix.stream
- 拜访微服务,以产生监控数据
http://localhost:3001/item-service/35
http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100
http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/
熔断测试
- 用 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 service
- pom.xml
- application.yml
- 主程序
- ItemFeignService
- UserFeignService
- ItemFeignServiceFB
- UserFeignServiceFB
- OrderServiceImpl
pom.xml
- 右键点击我的项目编辑起步依赖, 增加以下依赖:
- actuator
- feign
- hystrix
application.yml
- ribbon 重试和 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);
}
}
ItemFeignService
package 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<List<Item>> getItems(@PathVariable String orderId);
@PostMapping("/decreaseNumber")
JsonResult decreaseNumber(@RequestBody List<Item> items);
}
UserFeignService
package 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<User> 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<List<Item>> 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<Item> 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<User> 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("无奈减少用户积分");
}
}
OrderServiceImpl
package 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> user = userService.getUser(7);
// 调用 item-service 获取商品信息
JsonResult<List<Item>> 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
启动服务,拜访测试
- 依据 orderid,获取订单
http://localhost:8201/123abc
http://localhost:8202/123abc - 保留订单
http://localhost:8201/
http://localhost:8202/
hystrix dashboard 监控 order service 断路器
- 拜访 http://localhost:4001/hystrix,填入 order service 的断路器监控门路,启动监控
- http://localhost:8201/actuator/hystrix.stream
- http://localhost:8202/actuator/hystrix.stream
hystrix dashboard 监控 order service 断路器
- 拜访 http://localhost:4001/hystrix,填入 order service 的断路器监控门路,启动监控
- http://localhost:8201/actuator/hystrix.stream
- http://localhost:8202/actuator/hystrix.stream
Turbine
hystrix + turbine 集群聚合监控
hystrix dashboard 一次只能监控一个服务实例,应用 turbine 能够会集监控信息,将聚合后的信息提供给 hystrix dashboard 来集中展现和监控
新建 sp10-turbine 我的项目
pom.xml
<?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.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>sp10-turbine</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sp10-turbine</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<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-turbine</artifactId>
</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: 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);
}
}
拜访测试
- 8201 服务器产生监控数据:
http://localhost:8201/abc123
http://localhost:8201/ - 8202 服务器产生监控数据:
http://localhost:8202/abc123
http://localhost:8202/ - turbine 监控门路
http://localhost:5001/turbine.stream - 在 hystrix dashboard 中填入 turbine 监控门路,开启监控
http://localhost:4001/hystrix - turbine 聚合了 order-service 两台服务器的 hystrix 监控信息