87--spring cloud (Feign声明式客户端接口/集成)

Feign 声明式客户端接口

Fegin是一个集成工具,它集成了

  • 远程调用
  • ribbon
  • hystrix

微服务应用中,ribbon 和 hystrix 总是同时出现,feign 整合了两者,并提供了声明式消费者客户端

用 feign 代替 hystrix+ribbon

feign

创建项目-远程调用

导入依赖pom.xml

actuator,web,netflix-eureka-client,netflix-hystrix,openfeign

需要添加 sp01-commons 依赖

<?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.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>sp09-feign</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sp09-feign</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR7</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.tedu</groupId>
            <artifactId>sp01-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </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: feign
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

主程序添加 @EnableFeignClients

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.openfeign.EnableFeignClients;
@EnableCircuitBreaker //启动断路器
@EnableFeignClients //启用feign客户端
@SpringBootApplication
public class Sp09FeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(Sp09FeignApplication.class, args);
    }
}

feign 声明式客户端

feign 利用了我们熟悉的 spring mvc 注解来对接口方法进行设置,降低了我们的学习成本
通过这些设置,feign可以拼接后台服务的访问路径和提交的参数

例如:

@FeignClient(value = "user-service")
public interface UserFeignClient {
    @GetMapping("/{userId}/score")
    JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);

}

当这样调用该方法:

@Autowired
private UserFeignClient userFeignClient;
//在控制层进行调用声明式接口
@GetMapping("/user-service/{userId}/score")
public  JsonResult addScore(@PathVariable Integer userId,Integer score){
    log.info("调用远程用户服务,新增积分");
    return  userFeignClient.addScore(userId,score);
}

那么 feign 会向服务器发送请求:

http://用户微服务/7/score?score=100
  • 注意:如果 score 参数名与变量名不同,需要添加参数名设置:
@GetMapping("/{userId}/score") 
JsonResult addScore(@PathVariable Integer userId, @RequestParam("score") Integer s);

ItemFeignClient

package cn.tedu.sp09.feign;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp09.feign.impl.ItemFeignClientFB;
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") //post提交的参数要放在请求协议体中 使用参数@RequestBody
    JsonResult decrease(@RequestBody List<Item> items);

}

UserFeignClient

注意,如果请求参数名与方法参数名不同,@RequestParam不能省略,并且要指定请求参数名:
@RequestParam("score") Integer s

package cn.tedu.sp09.feign;

import cn.tedu.sp01.pojo.User;
import cn.tedu.sp09.feign.impl.UserFeignClientFB;
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(value = "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);
}

OrderFeignClient

package cn.tedu.sp09.feign;

import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp09.feign.impl.OrderFeignClientFB;
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",fallback = OrderFeignClientFB.class)
public interface OrderFeignClient {

    @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.ItemFeignClient;
import cn.tedu.sp09.feign.OrderFeignClient;
import cn.tedu.sp09.feign.UserFeignClient;
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 ItemFeignClient itemFeignClient;
    @Autowired
    private UserFeignClient userFeignClient;
    @Autowired
    private OrderFeignClient orderFeignClient;

    @GetMapping("/item-service/{orderId}")
    public JsonResult<List<Item>> getItems(@PathVariable  String orderId){
        log.info("调用远程商品服务,获取订单列表");
        return itemFeignClient.getItems(orderId);
    }

    @PostMapping("/item-service/decreaseNumber")
    public  JsonResult decreaseNumber(List<Item> items){
        log.info("调用远程商品服务,减少库存");
        return  itemFeignClient.decrease(items);
    }
    @GetMapping("/user-service/{userId}")
    public JsonResult<User> getUser(@PathVariable  Integer userId){
        log.info("调用远程用户服务,获取用户信息");
        return userFeignClient.getUser(userId);
    }

    @GetMapping("/user-service/{userId}/score")
    public  JsonResult addScore(@PathVariable Integer userId,Integer score){
        log.info("调用远程用户服务,新增积分");
        return  userFeignClient.addScore(userId,score);
    }
    @GetMapping("/order-service/{orderId}")
    public JsonResult<Order> getOrder(@PathVariable  String orderId){
        log.info("调用远程订单服务,获取订单");
        return orderFeignClient.getOrder(orderId);
    }

    @GetMapping("/order-service")
    public  JsonResult addOrder(){
        log.info("调用远程订单服务,新增订单");
        return  orderFeignClient.addOrder();
    }
}

调用流程

流程

feign + ribbon 负载均衡和重试

无需额外配置,feign 默认已启用了 ribbon 负载均衡和重试机制。可以通过配置对参数进行调整

重试的默认配置参数:

ConnectTimeout=1000
ReadTimeout=1000
MaxAutoRetries=0
MaxAutoRetriesNextServer=1

可以自己配置重试参数:

ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  
item-service:
  ribbon:
    MaxAutoRetries: 1
    MaxAutoRetriesNextServer: 2
    ConnectTimeout: 1000
    ReadTimeout: 500

Feign 集成 Hystrix

feign 启用 hystrix

feign 默认没有启用 hystrix,不推荐启用Hystrix, 如果有特殊需求,可以添加配置,启用 hystrix

  • 添加 Hystrix 完整依赖
  • 添加注解 @EnableCircuitBreaker
  • feign.hystrix.enabled=true

application.yml 添加配置

feign:
  hystrix:
    enabled: true

启用 hystrix 后,访问服务

默认1秒会快速失败,没有降级方法时,会显示白板页

可以添加配置,暂时减小降级超时时间,以便后续对降级进行测试

feign:
  hystrix:
    enabled: true
    
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500

feign + hystrix 降级

feign 远程接口中指定降级类

远程调用失败, 会执行降级类中的代码

添加降级代码

// 声明式客户端接口添加注解属性
@FeignClient(name="item-service", fallback=降级类.class)
public interface ItemFeignClient {
    
}


//定义降级类,实现声明式客户端接口
public class 降级类 implements ItemFeignClient {
        
}

ItemFeignClient

@FeignClient(name = "item-service" ,fallback = ItemFeignClientFB.class )
public interface ItemFeignClient {
............
}

UserFeignClient

@FeignClient(value = "user-service",fallback = UserFeignClientFB.class)
public interface UserFeignClient {

........

}

OrderFeignClient

@FeignClient(name = "order-service",fallback = OrderFeignClientFB.class)
public interface OrderFeignClient {

.......
}

降级类

降级类需要实现远程接口

ItemFeignClientFB

package cn.tedu.sp09.feign.impl;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp09.feign.ItemFeignClient;
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().msg("获取商品列表失败");
    }

    @Override
    public JsonResult decrease(List<Item> items) {
        return JsonResult.err().msg("减少商品库存失败");
    }
}

UserFeignClientFB

package cn.tedu.sp09.feign.impl;

import cn.tedu.sp01.pojo.User;
import cn.tedu.sp09.feign.UserFeignClient;
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().msg("获取用户失败");
    }

    @Override
    public JsonResult addScore(Integer userId, Integer score) {
        return JsonResult.err().msg("新增积分失败");
    }
}

OrderFeignClientFB

package cn.tedu.sp09.feign.impl;

import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
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().msg("获取订单失败");
    }

    @Override
    public JsonResult addOrder() {
        return JsonResult.err().msg("新增订单失败");
    }
}

启动服务,访问测试

Feign + hystrix 监控和熔断测试

主程序添加 @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.openfeign.EnableFeignClients;
@EnableCircuitBreaker //启动断路器
@EnableFeignClients //启用feign客户端
@SpringBootApplication
public class Sp09FeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(Sp09FeignApplication.class, args);
    }

}

配置 actuator,暴露 hystrix.stream 监控端点

actuator 依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.yml 暴露 hystrix.stream 端点

m.e.w.e.i=hystrix.stream

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

hystrix dashboard

  1. 重启项目,调用后台服务

    http://localhost:3001/item-service/45ty3t3t4

    http://localhost:3001/user-service/7

    http://localhost:3001/order-service/56564

  2. 查看监控端点,有没有数据

    http://localhost:3001/actuator/hystrix.stream

  3. 访问仪表盘,开启监控

    http://localhost:4001/hystrix

熔断测试

  • 用 ab 工具,以并发50次,来发送20000个请求
ab -n 20000 -c 50 http://localhost:3001/item-service/35
  • 断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法

压力测试

原文地址:https://www.cnblogs.com/liqbk/p/13583553.html