一、feign 远程调用-声明式的客户端
微服务应用中,ribbon 和 hystrix 总是同时出现,feign 整合了两者,并提供了声明式消费者客户端
- 用 feign 代替 hystrix+ribbon
1. 新建 sp09-feign 项目
2. 添加依赖
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
cn.tedu
sp09-feign
0.0.1-SNAPSHOT
sp09-feign
Demo project for Spring Boot
1.8
Hoxton.RELEASE
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-openfeign
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
3. 修改application.yml文建
spring:
application:
name: feign
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
4. 主程序添加 @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);
}
}
5. 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);
5.1 ItemFeignService
package cn.tedu.sp09.service;
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("item-service")
public interface ItemFeignService {
@GetMapping("/{orderId}")
JsonResult> getItems(@PathVariable String orderId);
@PostMapping("/decreaseNumber")
JsonResult decreaseNumber(@RequestBody List- items);
}
5.2 UserFeignService
- 注意,如果请求参数名与方法参数名不同,
@RequestParam
不能省略,并且要指定请求参数名:@RequestParam("score") Integer s
package cn.tedu.sp09.service;
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("user-service")
public interface UserFeignService {
@GetMapping("/{userId}")
JsonResult getUser(@PathVariable Integer userId);
// 拼接路径 /{userId}/score?score=新增积分
@GetMapping("/{userId}/score")
JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
5.3 OrderFeignService
package cn.tedu.sp09.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.web.util.JsonResult;
@FeignClient("order-service")
public interface OrderFeignService {
@GetMapping("/{orderId}")
JsonResult getOrder(@PathVariable String orderId);
@GetMapping("/")
JsonResult addOrder();
}
5.4 FeignController
package cn.tedu.sp09.controller;
import java.util.List;
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.sp09.service.ItemFeignService;
import cn.tedu.sp09.service.OrderFeignService;
import cn.tedu.sp09.service.UserFeignService;
import cn.tedu.web.util.JsonResult;
@RestController
public class FeignController {
@Autowired
private ItemFeignService itemService;
@Autowired
private UserFeignService userService;
@Autowired
private OrderFeignService orderService;
@GetMapping("/item-service/{orderId}")
public JsonResult> getItems(@PathVariable String orderId) {
return itemService.getItems(orderId);
}
@PostMapping("/item-service/decreaseNumber")
public JsonResult decreaseNumber(@RequestBody List- items) {
return itemService.decreaseNumber(items);
}
@GetMapping("/user-service/{userId}")
public JsonResult
getUser(@PathVariable Integer userId) {
return userService.getUser(userId);
}
@GetMapping("/user-service/{userId}/score")
public JsonResult addScore(@PathVariable Integer userId, Integer score) {
return userService.addScore(userId, score);
}
@GetMapping("/order-service/{orderId}")
public JsonResult getOrder(@PathVariable String orderId) {
return orderService.getOrder(orderId);
}
@GetMapping("/order-service")
public JsonResult addOrder() {
return orderService.addOrder();
}
}
6.启动服务,并访问测试
- 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
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:
ConnectTimeout: 1000
ReadTimeout: 1000
item-service:
ribbon:
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 2
ConnectTimeout: 1000
ReadTimeout: 500
2.启动服务,访问测试
http://localhost:3001/item-service/35
三、feign + hystrix 降级
feign 默认没有启用 hystrix,添加配置,启用 hystrix
1. application.yml 添加配置
feign:
hystrix:
enabled: true
启用 hystrix 后,访问服务
http://localhost:3001/item-service/35
默认1秒会快速失败,没有降级方法时,会显示白板页
1.1 可以添加配置,暂时减小降级超时时间,以便后续对降级进行测试
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 500
2. 指定降级类
远程调用失败, 会执行降级类中的代码
2.1 ItemFeignService
@FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
public interface ItemFeignService {
2.2 UserFeignService
@FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
public interface UserFeignService {
2.3 OrderFeignService
@FeignClient(name="order-service",fallback = OrderFeignServiceFB.class)
public interface OrderFeignService {
2.4 降级类
降级类需要实现远程接口
2.4.1 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> getItems(String orderId) {
return JsonResult.err("无法获取订单商品列表");
}
@Override
public JsonResult decreaseNumber(List- items) {
return JsonResult.err("无法修改商品库存");
}
}
2.4.2UserFeignServiceFB
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 getUser(Integer userId) {
return JsonResult.err("无法获取用户信息");
}
@Override
public JsonResult addScore(Integer userId, Integer score) {
return JsonResult.err("无法增加用户积分");
}
}
2.4.3 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 getOrder(String orderId) {
return JsonResult.err("无法获取商品订单");
}
@Override
public JsonResult addOrder() {
return JsonResult.err("无法保存订单");
}
}
3. 启动服务,访问测试
http://localhost:3001/item-service/35
四、feign + hystrix 监控和熔断测试
1.pom.xml 添加 hystrix 起步依赖
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2. 主程序添加 @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);
}
}
3. 配置 actuator依赖
org.springframework.boot
spring-boot-starter-actuator
4. application.yml暴露 hystrix.stream
监控端点
management:
endpoints:
web:
exposure:
include: hystrix.stream
5. 启动服务,查看监控端点
http://localhost:3001/actuator
5.1 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/
5.2 熔断测试
- 用 ab 工具,以并发50次,来发送20000个请求
ab -n 20000 -c 50 http://localhost:3001/item-service/35
- 断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法
五、order service 调用商品库存服务和用户服务
修改 sp04-orderservice 项目,添加 feign,调用 item service 和 user service
1. 添加依赖
- actuator
- feign
- hystrix
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
cn.tedu
sp04-orderservice
0.0.1-SNAPSHOT
sp04-orderservice
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
cn.tedu
sp01-commons
0.0.1-SNAPSHOT
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.boot
spring-boot-maven-plugin
org.springframework.cloud
spring-cloud-dependencies
Hoxton.RELEASE
pom
import
2. 修改 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
3. 主程序添加 @EnableFeignClients 注解
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);
}
}
4. 指定降级类
4.1 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> getItems(@PathVariable String orderId);
@PostMapping("/decreaseNumber")
JsonResult decreaseNumber(@RequestBody List- items);
}
4.2 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 getUser(@PathVariable Integer userId);
@GetMapping("/{userId}/score")
JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
}
4.3ItemFeignServiceFB
- 获取商品列表的降级方法,模拟使用缓存数据
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("无法修改商品库存");
}
}
5. 降级类
5.1 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("无法增加用户积分");
}
}
5.2 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 = 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);
}
}
6. order-service 配置启动参数,启动两台服务器
--server.port=8201
--server.port=8202
7. 启动服务,访问测试
- 根据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 + turbine 集群聚合监控
hystrix dashboard 一次只能监控一个服务实例,使用 turbine 可以汇集监控信息,将聚合后的信息提供给 hystrix dashboard 来集中展示和监控
1. 新建 sp10-turbine 项目
2.添加依赖
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
3.修改 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")`
4. 主程序
添加 @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);
}
}
5.访问测试
- 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监控信息