集成工具(功能整合)java
微服務應用中,ribbon 和 hystrix 老是同時出現,feign 整合了二者,並提供了聲明式消費者客戶端web
只須要聲明一個抽象接口,就能夠經過接口作遠程調用,不須要再使用 RestTemplate 來調用spring
// 調用遠程的商品服務,獲取訂單的商品列表 // 經過註解,配置: // 1. 調用哪一個服務 // 2. 調用服務的哪一個路徑 // 3. 向路徑提交什麼參數數據 @FeignClient(name="item-service") public interface ItemClient { @GetMapping("/{orderId}") JsonResult<List<Item>> getItems(@PathVariable String orderId); }
在這裏使用 @GetMapping("/{orderId}"), 指定的是向遠程服務調用的路徑shell
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 利用了熟悉的 spring mvc 註解來對接口方法進行設置,下降了咱們的學習成本。
經過這些設置,feign能夠拼接後臺服務的訪問路徑和提交的參數
例如:express
@GetMapping("/{userId}/score") JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
當這樣調用該方法:apache
service.addScore(7, 100);
那麼 feign 會向服務器發送請求:緩存
http://用戶微服務/7/score?score=100
@GetMapping("/{userId}/score") JsonResult addScore(@PathVariable Integer userId, @RequestParam("score") Integer s
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); }
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 不能省略 }
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(); }
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://localhost:3001/item-service/decreaseNumber
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
重試的默認配置參數:服務器
ConnectTimeout=1000 ReadTimeout=1000 MaxAutoRetries=0 MaxAutoRetriesNextServer=1
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時不推薦啓用Hystrixmvc
啓用Hystrix基礎配置:
@EnableCircuitBreaker
feign: hystrix: enabled: true
...... feign: hystrix: enabled: true hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 500
... @FeignClient(name="item-service", fallback = ItemFeignServiceFB.class) public interface ItemFeignService { ...
... @FeignClient(name="user-service", fallback = UserFeignServiceFB.class) public interface UserFeignService { ...
... @FeignClient(name="order-service",fallback = OrderFeignServiceFB.class) public interface OrderFeignService { ...
降級類須要實現聲明式客戶端接口,在實現的抽象方法中添加降級代碼,
降級類須要添加 @Component
註解
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("沒法修改商品庫存"); } }
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("沒法增長用戶積分"); } }
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
<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); } }
hystrix.stream
監控端點查看pom.xml, 確認已經添加了 actuator
依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
hystrix.stream
端點management: endpoints: web: exposure: include: hystrix.stream
http://localhost:3001/actuator
啓動 hystrix dashboard 服務,填入 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 -n 20000 -c 50 http://localhost:3001/item-service/35`
sp09-feign項目關閉,再也不使用
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); } }
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); }
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); }
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("沒法修改商品庫存"); } }
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("沒法增長用戶積分"); } }
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); } }
--server.port=8201
--server.port=8202
hystrix dashboard 一次只能監控一個服務實例,使用 turbine 能夠聚集監控信息,將聚合後的信息提供給 hystrix dashboard 來集中展現和監控
<?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>
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); } }