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@SpringBootApplicationpublic 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@Slf4jpublic 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=1000ReadTimeout=1000MaxAutoRetries=0MaxAutoRetriesNextServer=1

application.yml 配置 ribbon 超时和重试

  • ribbon.xxx 全局配置
  • item-service.ribbon.xxx 对特定服务实例的配置
spring:  application:    name: feignserver:  port: 3001eureka:  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根底配置:

  1. hystrix起步依赖
  2. yml中配置启用hystrix
  3. 启动类增加注解 @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;@Componentpublic 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;@Componentpublic 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;@Componentpublic 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@SpringBootApplicationpublic 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
  1. pom.xml
  2. application.yml
  3. 主程序
  4. ItemFeignService
  5. UserFeignService
  6. ItemFeignServiceFB
  7. UserFeignServiceFB
  8. 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@SpringCloudApplicationpublic 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;@Componentpublic 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;@Componentpublic 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@Servicepublic 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@SpringBootApplicationpublic 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监控信息