文章目录
- 六、Feign 声明式客户端接口
- 1 新建 sp09-feign 项目
- 2 feign + ribbon 负载均衡和重试
- 3 feign + hystrix 降级
- 4 feign + hystrix 监控和熔断测试
- 5 order service 调用商品服务和用户服务
- 七、hystrix + turbine 集群聚合监控
六、Feign 声明式客户端接口
Feign 集成了 Ribbon 和 Hystrix ,并提供了声明式消费者客户端。
- 声明式客户端:
只需要定义一个抽象的接口,就可以通过接 口调用远程服务,不需要写具体调用代码。
例如调用后台商品服务,接口可以这样定义:
@FeignClient(name="item-service")
public interface ItemFeignClient{
@GetMapping("/{orderId}")
JsonResult<List<Item>> getItems(@PathVarible String orderId);
}
通过注解配置以下3点:
- 服务id - - 确定调用哪个远程服务
- 路径 - - 调用一个服务的哪个路径
- 参数 - - 向这个路径提交什么参数数据
1 新建 sp09-feign 项目
1.1 新建springboot项目
1.2 编辑pom.xml
- 添加 sp01-commons 依赖
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>sp01-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 排除eureka-client的dataformat
<exclusions>
<exclusion>
<artifactId>jackson-dataformat-xml</artifactId>
<groupId>com.fasterxml.jackson.dataformat</groupId>
</exclusion>
</exclusions>
1.3 application.yml
spring:
application:
name: feign
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
1.4 主程序添加@EnableFeignClients
1.5 新建ItemFeignClient接口
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;
@FeignClient(name="item-service")
public interface ItemFeignClient {
@GetMapping("/{orderId}")
JsonResult<List<Item>> getItems(@PathVariable String orderId);
@PostMapping("/decreaseNumber")
JsonResult decreaseNumber(@RequestBody List<Item> items);
}
1.6 新建UserFeignClient接口
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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name="user-service")
public interface UserFeignClient {
@GetMapping("/{userId}")
JsonResult<User> getUser(@PathVariable Integer userId);
@GetMapping("/{userId}/score")
JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
}
1.7 新建OrderFeignClient接口
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;
@FeignClient(name="order-service")
public interface OrderFeignClient {
@GetMapping("/{orderId}")
JsonResult<Order> getOrder(@PathVariable String orderId);
@GetMapping("/")
JsonResult addOrder();
}
1.8 新建FeignController
package cn.tedu.sp09.controller;
import java.util.List;
import cn.tedu.sp09.feign.ItemFeignClient;
import cn.tedu.sp09.feign.OrderFeignClient;
import cn.tedu.sp09.feign.UserFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.web.bind.annotation.RestController;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
@RestController
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) {
return itemFeignClient.getItems(orderId);
}
@PostMapping("/item-service/decreaseNumber")
public JsonResult decreaseNumber(@RequestBody List<Item> items) {
return itemFeignClient.decreaseNumber(items);
}
/
@GetMapping("/user-service/{userId}")
public JsonResult<User> getUser(@PathVariable Integer userId) {
return userFeignClient.getUser(userId);
}
@GetMapping("/user-service/{userId}/score")
public JsonResult addScore(@PathVariable Integer userId, Integer score) {
return userFeignClient.addScore(userId, score);
}
/
@GetMapping("/order-service/{orderId}")
public JsonResult<Order> getOrder(@PathVariable String orderId) {
return orderFeignClient.getOrder(orderId);
}
@GetMapping("/order-service")
public JsonResult addOrder() {
return orderFeignClient.addOrder();
}
}
1.9 启动测试
调用流程
将以上项目启动,访问:
-
http://localhost:3001/item-service/decreaseNumber
使用postman,POST发送以下格式数据:
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
2 feign + ribbon 负载均衡和重试
Feign 集成了 Ribbon,提供了 Ribbon 的默认配置,默认已启动了负载均衡和重试,无需额外配置就可以使用Ribbon。
- 重试的默认配置参数:
MaxAutoRetries=0
MaxAutoRetriesNextServer=1
ReadTimeout=1000
ConnectTimeout=1000
不做任何配置,访问:
http://localhost:3001/item-service/35,
可以看到此时会在8001、8002轮询访问:
也会进行重试:
当8002延迟后,会更换8001继续访问,而当访问8001也失败了,本次访问就宣告失败。
编辑 application.yml 配置 ribbon
# 通用配置,对所有服务都有效
ribbon:
ReadTimeout: 1000
# 只对商品服务有效
item-service:
ribbon:
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 2
ReadTimeout: 500
访问 http://localhost:3001/item-service/35 查看效果。
3 feign + hystrix 降级
3.1 feign 启用 hystrix
feign 集成了 hystrix,但 默认不启用 hystrix,feign 不推荐启用hystrix(网关那一节会分析原因)。
启用 hystrix,添加基础配置:
1) 添加Hystrix完整依赖
(feign 没有包含完整的 hystrix 依赖)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2) 添加yml配置 feign.hystrix.enabled=true
,在Feign中启用Hystrix
feign:
hystrix:
enabled: true
3) 主程序添加@EnableCircuitBreaker
3.2 添加降级代码
3.2.1 添加单独的降级类并实现声明式客户端接口
ItemFeignClientFB
package cn.tedu.sp09.feign;
import cn.tedu.sp01.pojo.Item;
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("无法获取订单商品列表");
}
@Override
public JsonResult decreaseNumber(List<Item> items) {
return JsonResult.err("无法修改商品库存");
}
}
OrderFeignClientFB
package cn.tedu.sp09.feign;
import cn.tedu.sp01.pojo.Order;
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("无法获取商品订单");
}
@Override
public JsonResult addOrder() {
return JsonResult.err("无法保存订单");
}
}
UserFeignClientFB
package cn.tedu.sp09.feign;
import cn.tedu.sp01.pojo.User;
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("无法获取用户信息");
}
@Override
public JsonResult addScore(Integer userId, Integer score) {
return JsonResult.err("无法增加用户积分");
}
}
3.2.2 在 feign 远程接口中指定降级类
远程调用失败, 会执行降级类中的代码。
ItemFeignClient
...
@FeignClient(name="item-service",fallback = ItemFeignClientFB.class)
public interface ItemFeignClient {
...
}
OrderFeignClient
...
@FeignClient(name="order-service",fallback = OrderFeignClientFB.class)
public interface OrderFeignClient {
...
}
UserFeignClient
...
@FeignClient(name="user-service",fallback = UserFeignClientFB.class)
public interface UserFeignClient {
...
}
3.2.3 启动测试
4 feign + hystrix 监控和熔断测试
4.1 配置actuator,添加依赖
查看pom.xml文件,已添加 actuator 依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
4.2 配置yml,暴露 hystrix.stream
监控端点
management:
endpoints:
web:
exposure:
include: hystrix.stream
4.3 启动测试
此时只有ping… ,需要访问其他服务产生监控数据
-
启动 hystrix dashboard 服务(sp08),填入 feign 监控路径,开启监控
访问: http://localhost:4001/hystrix,
填入监控路径: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工具,并发测试。
5 order service 调用商品服务和用户服务
可以将 sp09-feign 项目关闭,后面不再使用。
5.1 修改sp04项目,完成远程调用
5.1.1 编辑pom.xml,添加依赖:feign、hystrix完整依赖、actuator
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
5.1.2 编辑 application.yml
未配置 ribbon 重试 和 hystrix 超时,采用默认值。
# 启用 hystrix
feign:
hystrix:
enabled: true
# 暴露 hystrix.stream 监控端点
management:
endpoints:
web:
exposure:
include: hystrix.stream
5.1.3 主程序添加注解
@EnableCircuitBreaker
- - 启用 hystrix
@EnableFeignClients
- - 启用 feign
5.1.4 添加声明式客户端接口,指定降级类
ItemFeignClient
package cn.tedu.sp04.order.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;
@FeignClient(name="item-service", fallback = ItemFeignClientFB.class)
public interface ItemFeignClient {
@GetMapping("/{orderId}")
JsonResult<List<Item>> getItems(@PathVariable String orderId);
@PostMapping("/decreaseNumber")
JsonResult decreaseNumber(@RequestBody List<Item> items);
}
UserFeignClient
package cn.tedu.sp04.order.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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name="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);
}
5.1.5 添加单独的降级类并实现声明式客户端接口
ItemFeignClientFB
package cn.tedu.sp04.order.feign;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class ItemFeignClientFB implements ItemFeignClient{
@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("无法修改商品库存");
}
}
UserFeignClientFB
package cn.tedu.sp04.order.feign;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import org.springframework.stereotype.Component;
@Component
public class UserFeignClientFB implements UserFeignClient{
@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("无法增加用户积分");
}
}
5.1.6 修改 OrderServiceImpl,使用客户端接口调用远程服务
package cn.tedu.sp04.order.service;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp04.order.feign.ItemFeignClient;
import cn.tedu.sp04.order.feign.UserFeignClient;
import cn.tedu.web.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private ItemFeignClient itemFeignClient;
@Autowired
private UserFeignClient userFeignClient;
@Override
public Order getOrder(String orderId) {
//调用 user-service 获取用户信息
//(id写死,真实项目需获取目前登录用户Id)
JsonResult<User> user = userFeignClient.getUser(8);
//调用 item-service 获取商品信息
JsonResult<List<Item>> items = itemFeignClient.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 减少商品库存
itemFeignClient.decreaseNumber(order.getItems());
//调用 user-service 增加用户积分
userFeignClient.addScore(order.getUser().getId(),1000);
log.info("保存订单:"+order);
}
}
5.1.7 order-service 配置启动参数,启动两台服务器
- 启动参数 :
--server.port=8201
--server.port=8202
5.1.8 启动测试
-
根据orderid,获取订单:http://localhost:8201/123abc
-
访问 http://localhost:4001/hystrix ,填入 order service 的监控路径,启动监控
http://localhost:8201/actuator/hystrix.stream
http://localhost:8202/actuator/hystrix.stream
七、hystrix + turbine 集群聚合监控
Turbine 可以聚合多台服务器的监控数据,聚合后交给 hystrix dashboard 来集中展示和监控。
1 搭建 Turbine 服务:
添加 Turbine 依赖
编辑 yml 配置:
turbine.app-config
:指定要聚合的服务 - - service1,service2…
turbine.cluster-name-expression
:给聚合后的数据起一个集群名,一般用 “default”
主程序添加注解 @EnableTurbine
2 新建 sp10-turbine 项目
2.1 新建springboot项目
2.2 查看 pom.xml
已添加 turbine 依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
2.3 编辑 application.yml
spring:
application:
name: turbine
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")
2.4 主程序添加 @EnableTurbine
2.5 访问测试
-
8201服务器产生监控数据:
http://localhost:8201/abc123
http://localhost:8201/ -
8202服务器产生监控数据:
http://localhost:8202/abc123
http://localhost:8202/ -
在 hystrix dashboard 中填入turbine 监控路径,开启监控
http://localhost:4001/hystrix -
turbine 监控路径
http://localhost:5001/turbine.stream
turbine聚合了order-service两台服务器的hystrix监控信息: