SpringCloud精简入门(注册中心Eureka、负载均衡、熔断器、远程调用、网关)

死亡三连问

SpringCloud 是啥?

  • 官方一点的说法:SpringCloud是在SpringBoot基础上构建的,用于快速构建分布式系统的通用模式的工具集(注意了:重点在于它只是一个工具),因此我把它简单的理解为构建微服务的一个工具
  • SpringCloud主要就是将各家较为成熟的服务框架组合起来,再通过SpringBoot风格进行封装,屏蔽掉了原先复杂的配置和实现原理,最终让开发者可以简单方便且易维护的开发的分布式系统开发工具包
  • 那么SpringCloud和云到底有啥关系呢,其实由于使用SpringCloud开发的应用程序非常适合在Docker或Pass上部署,所以由SpringCloud构建的程序又叫做云原生应用,可以简单的理解为面向云环境的软件架构

微服务 是啥?

  • 微服务,见名知义,就是较小的服务单元,与微服务相对的单体架构
  • 单体架构 单体架构就是将所有的业务场景(表现层、业务逻辑层、数据访问层)都放在一个工程中,部署在一台服务器上
  • 那么相对单体架构的微服务架构就是将单一程序开发成一个微服务
  • 每个微服务运行在自己的进程中,并使用轻量级的机制通信,通常是HTTP RESTFUL API(也可以采用消息队列来通信)。
  • 这些服务围绕着业务能力来划分,并通过自动化部署机制来独立部署(如Jenkins),降低出错频率
  • 这些服务可以使用不同的编程语言,不同的数据库,以保证最低限度的集中式管理(例如Eureka,Zookeeper)。

为啥 需要微服务?

  • 一般呢,小的公司是用不到微服务的,怎么样?是不是这个技术瞬间高大上了起来
  • 微服务架构的出现 当然是由于单体架构不给力导致的
  • 在业务的逻辑越来越复杂的时候,单体架构的代码可读性和可维护性就会越来越低,面对海量用户时,单体架构系统的并发能力也会随之下降
  • 那么就需要使用微服务架构将一个复杂业务拆分成多个小业务,将复杂问题不断拆分成一个个简单的小问题
  • 微服务是分布式系统,业务与业务之间完全解耦,也就是说一个业务出现了问题,不会影响其他的业务,可以显著提高生产力
  • 同时当我们需要增加业务时,可以根据业务再拆分,具有极强的横向扩展能力,面对高并发的场景可以将服务集群化部署,加强系统负载能力
  • 因为现实中,一个工程可能由多个服务单元组成,但是多个服务可能使用不同的技术或语言实现,微服务可以让这种融合更自由

一些概念性的东西

SpringCloud的主要应用过程

首先展示一下源自网络的springCloud组件架构图:
在这里插入图片描述
结合这张图来说一下springCloud的应用过程:

  • 所有的请求(移动端、客户端有一个算一个)都统一通过网关服务(Zuul Proxy)来访问内部服务
  • 网关接收到请求后,从注册中心(Eureka)获取可用的微服务,所有的微服务都需要在注册中心进行注册
  • 其中Ribbon主要用于负载均衡,它可以帮助判断并分发具体的请求到后端的具体应用服务实例,起到了分发压力的作用
  • 微服务(services)之间通过Feign进行通信处理业务,保持服务的一致性
  • Hystrix负责处理服务超时熔断
  • Turbine监控 服务间的调用和熔断的相关指标

从上面这张图中也可以看出SpringCloud主要的组件,解释一下这些组件都具体是做什么用的:

  • Netflix Eureka 该组件负责服务的注册与发现,Eureka体系包括:服务注册中心、服务提供者、服务消费者
  • Netflix Hystrix 熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点。比如突然某个服务出现了故障,当请求多时,就会发生严重的阻塞影响整体服务响应。Hystrix会及时发现某个不在状态不稳定服务,将其他服务调过来响应
  • Netflix Zuul 该组件是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul相当于设备和Netflix流应用的Web网站后端所有请求的前门
  • Netflix Archaius 该组件用于配置管理API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。原理是每隔60s从配置源读取一次内容,这样修改配置文件后不需要重启服务就可以使修改后的内容生效,前提是使用archaius的API来读取
  • Spring Cloud Config 配置中心,配置管理工具包,可以将配置放在远程服务器,集中化管理集群配置,目前支持本地存储、GIt以及SubVersion。方便以后统一管理、升级装备
  • Spring Cloud Bus 事件、消息总线,用于在集群中传播状态变化,可以与Spring Cloud Config联合实现热部署
  • Spring Cloud for Cloud Foundry 一个开源Paas云平台,支持多种框架、语言、运行时环境、云平台以及应用服务,使开发人员可以在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构问题
  • Spring Cloud Cluster 提供在分布式系统中的集群所需要的基础功能支持,如选举、集群的状态一致性、全局锁、tokens等常见状态模式的抽象和实现
  • Spring Cloud Consul Consul是一个支持多数据中心分布式高可用的服务发现和配置共享的服务软件,consul是一个服务发现与配置工具,与Docker容器可以无缝集成

开始操作

完整的文件结构

整个项目分为四个模块consumer、provider、euraka、gateway
在这里插入图片描述
consumer结构
在这里插入图片描述
eureka结构
在这里插入图片描述
gateway文件结构
在这里插入图片描述
provider文件结构
在这里插入图片描述

搭建基础框架

  • IDEA创建maven项目
    new
  • 更改父级Pom文件
    完整的父级Pom文件如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>springCloud</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <gson.version>2.8.0</gson.version>
        <jackson.version>2.9.5</jackson.version>
        <swagger.version>2.9.2</swagger.version>
        <mybatis.plus.boot.version>3.1.0</mybatis.plus.boot.version>
        <hutool.version>5.7.19</hutool.version>
        <druid.version>1.1.10</druid.version>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

主要的部分就是Properties和dependency两部分,本次测试项目中并没有使用到数据库,但实际业务需要使用到,所以记得添加

  • 创建生产者Provider
    在项目的父级目录上右键new->module->maven
    在这里插入图片描述
  • 更改provider的pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springCloud</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>provider</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>
  • 添加provider的基础文件
    在这里插入图片描述
    Controller层代码如下
@RestController
@RequestMapping("provider")
public class ProviderController {
    //使用Map模拟数据库
    private Map<Integer, User> map;

    {
        map = new HashMap<>();
        map.put(1, new User(1, "Johnny"));
        map.put(2, new User(2, "Timmy"));
    }

    @GetMapping("/findAll")
    public List<User> findAll() {
        System.out.println("Provider::findAll");
        List<User> list = new ArrayList<>();
        for (Map.Entry<Integer, User> users : map.entrySet()) {
            list.add(users.getValue());
        }
        return list;
    }

    @GetMapping("/findById/{id}")
    public User findById(@PathVariable("id") Integer id) {
        return map.get(id);
    }
}

pojo层代码如下

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
}

ProviderApplication启动类代码如下

@SpringBootApplication
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class);
    }
}
  • Provider启动测试
    启动provider的启动类,使用postman测试
    如下即provider基础编写完成
    在这里插入图片描述
    在这里插入图片描述
  • 添加消费者Consumer模块
    步骤同Provider
    在这里插入图片描述
  • 更改consumer的pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springCloud</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>consumer</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>
  • 添加Consumer基础代码
    在这里插入图片描述

Controller层代码如下:

@RestController
@RequestMapping("consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("/findAll")
    public List<User> findAll() {

        /*因为我们是消费者,我们只能通过访问生产者的方式来获取数据
        SpringCloud 基于springBoot的restFul(http)协议*/

        //provider的请求url
        System.out.println("Consumer:: findAll");
        String baseUrl = "http://localhost:8081/provider/findAll";
        List list = restTemplate.getForObject(baseUrl, List.class);
        return list;
    }

    @GetMapping("/findById/{id}")
    public User findById(@PathVariable("id") Integer id) {
        String baseUrl = "http://localhost:8081/provider/findById/" + id;
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }
}

pojo层代码如下
和provider的完全一致

Consumer启动类代码如下

@SpringBootApplication
public class ConsumerApplication {
    //springBoot没有主动注入RestTemplate,需要我们手动注入
    @Autowired
    private RestTemplateBuilder templateBuilder;

    @Bean
    public RestTemplate restTemplate() {
        return templateBuilder.build();
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }
}
  • Consumer代码启动测试
    因为同时Provider代码也必须是运行状态,所以需要先给provider改一下运行端口号
    provider的application.yml文件中添加
    server.port: 8081
    这里有个idea小技巧,为了看到所有的模块运行情况,我们需要底栏显示Services
    在这里插入图片描述
    找到本项目的.idea文件夹下面的workspace.xml文件,在文件末尾添加
 <component name="RunDashboard">
    <option name="configurationTypes">
      <set>
        <option value="SpringBootApplicationConfigurationType" />
      </set>
    </option>
  </component>

后重启IDEA使之生效

启动consumer,provider
在这里插入图片描述
在这里插入图片描述

Eureka注册中心

  • 创建一个子模块eureka
    方式同consumer、provider
    在这里插入图片描述
  • 更改pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springCloud</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

</project>
  • 因为Eureka只是注册中心,没有实际业务,所以完整的文件结构如下
    在这里插入图片描述
    启动类EurekaApplication中代码
@SpringBootApplication
@EnableEurekaServer  //声明当前模块是一个Eureka服务端
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class);
    }
}

配置文件

server:
  # 实际业务不要用这个端口号,易被攻击
  port: 8761

#声明注册中心的url
eureka:
  client:
    service-url:
      defaultZone: http://0.0.0.0:8761/eureka
    # 关闭注册自己
    fetch-registry: false
    register-with-eureka: false

Done!! 启动程序,url访问localhost:8761
在这里插入图片描述
我们看到Instance currently registered with Eureka中空空如也,这是因为我们没有进行对微服务的注册,重申一遍:所有的微服务都需要注册到注册中心

  • 将provider注册到注册中心
    1、在Provider的pom文件中增加eureka client的依赖
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

2、启动类上加注解

@EnableDiscoveryClient
这里想说一下@EnableDiscoveryClient@EnableEurekaClient
从表面意思上看,似乎是@EnableEurekaClient更应该用在这里,但实际上这个注解已经是老版了,应用场景单一

并且使用后会在界面显示这样的信息在这里插入图片描述
3、配置文件增加配置

server:
  port: 8081

# 服务名称
spring:
  application:
    name: provider

#eureka配置
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

启动后显示eureka页面显示已经注册了一个实例在这里插入图片描述

  • 将consumer注册到注册中心
    步骤同provider,相同部分不再赘述

在这里插入图片描述

  • consumer中通过注册中心的方式调用provider
    ConsumerController改为如下内容
@RestController
@RequestMapping("consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/findAll")
    public List<User> findAll() {

        /*在provider和consumer都注册到注册中心后,可以通过注册的名称来获取Ip地址*/
        //通过discoveryClient获得的是一个实例列表,因为通常我们会做负载均衡,所以不止一个实例
        System.out.println("Eureka:: Consumer");
        List<ServiceInstance> providers = discoveryClient.getInstances("provider");
        ServiceInstance serviceInstance = providers.get(0);
        URI uri = serviceInstance.getUri();
        System.out.println(uri);
        String scheme = serviceInstance.getScheme();
        String host = serviceInstance.getHost();
        int port = serviceInstance.getPort();
        //拼接url
        String eurekaUrl = scheme + "://" + host + ":" + port + "/provider/findAll";
        System.out.println(eurekaUrl);
        List list = restTemplate.getForObject(eurekaUrl, List.class);




        return list;
    }

    @GetMapping("/findById/{id}")
    public User findById(@PathVariable("id") Integer id) {
        String baseUrl = "http://localhost:8081/provider/findById/" + id;
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }
}

运行Consumer后使用postman调用findAll接口,控制台显示如下
在这里插入图片描述

Ribbon负载均衡

  • 首先我们需要启动两个provider
    只需在启动了一个provider后,更改端口号再启动一个provider即可
    启动完成后,在注册中心会看到有两个provider
    在这里插入图片描述
  • 在Consumer的RestTemplate上面添加@LoadBalanced
    该注释是开启负载均衡的作用
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
    //springBoot没有主动注入RestTemplate,需要我们手动注入
    @Autowired
    private RestTemplateBuilder templateBuilder;

    @Bean
    @LoadBalanced  //开启负载均衡 只增加了这里请注意,其余都不变
    public RestTemplate restTemplate() {
        return templateBuilder.build();
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }
}
  • 在Consumer的Controller层更改访问provider的形式
    开启负载均衡之后,可以直接使用实例名称进行访问接口,而无需通过discoveryClient自己手动获取

以下仅显示Conusmer的findAll的更改,其余模块没有更改

@GetMapping("/findAll")
    public List<User> findAll() {
        /*方案3:开启负载均衡后,通过discoveryClient获得的是provider的列表*/
        //第一个provider是实例名称,即在provider配置文件中的name
        String balancedUrl = "http://provider/provider/findAll";
        List list = restTemplate.getForObject(balancedUrl, List.class);
        return list;
    }
  • 启动所有微服务后,访问consumer的接口
    在这里插入图片描述
    在这里插入图片描述
    可以看到是拿到了结果,那么我们怎么知道到底是访问了那个Provider呢,虽然实际是不用关心的,但在学习阶段我们最好关心以下
    通过打印日志可以看到,刚刚请求的 8082端口的provider
    在这里插入图片描述
  • PS:我们还可以通过配置文件更改负载均衡的策略
    比如当前默认的是轮询机制,我们可以配置成随机之类的
    研究了一下,好像是如果要针对某个服务配置负载均衡策略,就需要在配置文件里增加
#RandomRule 随机
#RoundRobinRule 轮询
#provider是服务名,记得切换称自己的
provider:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

如果设置全局负载均衡策略,就需要在consumer的配置类中返回一个配置
像这样

  @Bean
    public IRule defaultLBStrategy(){
        return new RandomRule();
    }

!!!需要注意的是,两者不能同时用,用其中一个的时候,记得注释另一个

Hystrix 熔断器

插播! 插播! 紧急插播 Hystrix原理
先上手操作,再了解一下原理,还是蛮重要的

  • Consumer的Pom文件中增加Hystrix的依赖
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
  • Conusmer的启动类添加注释开启熔断
    因为我们这里只测试一个Provider的熔断,所以记得取消负载均衡的注释
    在这里插入图片描述

  • Consumer的配置文件中设置熔断器的配置

# 配置熔断策略:
hystrix:
  command:
    default:
      circuitBreaker:
        # 原理分析中解释配置含义
        # 强制打开熔断器 默认false关闭的。测试配置是否生效
        forceOpen: false
        # 触发熔断错误比例阈值,默认值50%
        errorThresholdPercentage: 50
        # 熔断后休眠时长,默认值5秒
        sleepWindowInMilliseconds: 5000
        # 熔断触发最小请求次数,默认值是20
        requestVolumeThreshold: 10
      execution:
        isolation:
          thread:
            # 熔断超时设置,默认为1秒
            timeoutInMilliseconds: 2000
  • 在Conusmer的Controller层编写降级的FallBack方法
@RestController
@RequestMapping("consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @HystrixCommand(fallbackMethod = "findAllFallBack")
    @GetMapping("/findAll")
    public List<User> findAll() {

        /* 方案2: 在provider和consumer都注册到注册中心后,可以通过注册的名称来获取Ip地址*/
        //通过discoveryClient获得的是一个实例列表,因为通常我们会做负载均衡,所以不止一个实例
//        System.out.println("Eureka:: Consumer");
        List<ServiceInstance> providers = discoveryClient.getInstances("provider");
        ServiceInstance serviceInstance = providers.get(0);
        URI uri = serviceInstance.getUri();
        System.out.println(uri);
        String scheme = serviceInstance.getScheme();
        String host = serviceInstance.getHost();
        int port = serviceInstance.getPort();
        //拼接url
        String eurekaUrl = scheme + "://" + host + ":" + port + "/provider/findAll";
        System.out.println(eurekaUrl);
        List list = restTemplate.getForObject(eurekaUrl, List.class);

        return list;
    }

    public List<User> findAllFallBack() {
        System.out.println("Ops!! find All Fall Back");
        return null;
    }
}

在这里插入图片描述
!! 注意:FallBack方法的返回值必须和FindAll方法一致

  • 在Provider的findAll方法中让线程睡几秒,触发熔断
    因为我们配置的触发熔断时间为1秒,所以当请求返回超过1秒时就会触发熔断
    在这里插入图片描述

  • 熔断效果
    在请求时,没有得到正确返回,触发了熔断
    在这里插入图片描述
    Cosumer的运行控制台输出了FallBack方法中的内容
    在这里插入图片描述

  • PS:当我们需要所有的请求都共用一个FallBack方法时,可以配置全局熔断器
    只需要两步
    1、在Controller类(注意这里是类不是方法)上添加注解 @DefaultProperties(defaultFallback = “defaultFallback”)
    2、在目标方法上添加注解 @HystrixCommand (注意不需要再次指定fallbackMethod)
    ps:暗戳戳的说一句,感觉也没有省事到哪里去

Feign远程调用

老样子,先操作后理解
插播! 插播! Feign的插播

  • Consumer的pom文件中添加依赖
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  • Consumer启动类中添加开启Feign客户端的注释
@EnableFeignClients  //开启feign客户端

在这里插入图片描述

  • Consumer中编写Feign客户端
    feign客户端在Consumer里就是一个仿照ProviderController中方法的接口,没有实质的方法实现
@FeignClient(value = "provider",path = "/provider")
public interface ConsumerClient {
    //伪装成ProviderController中的findAll
    @GetMapping("/findAll")
    List<User> findAll() throws Exception;
}

  • Consumer中的Controller使用Feign发送请求
@RestController
@RequestMapping("consumer")
public class ConsumerController {
    //注入Feign客户端
    @Qualifier("com.dean.feign.ConsumerClient")
    @Autowired
    private ConsumerClient consumerClient;

    @GetMapping("/findAll")
    public List<User> findAll() throws Exception {
        /*方案4:feign客户端方式访问*/
        return consumerClient.findAll();
    }
}

  • 启动后发送请求可正常获得结果
    在这里插入图片描述
Feign的熔断器

是的,Feign里面也结合了熔断器,让熔断器的实现更加容易
如果需要进一步配置feign的熔断器,需要添加依赖

<dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-hystrix</artifactId>
            <version>9.7.0</version>
        </dependency>

然后在配置文件里进行配置

feign:
  hystrix:
    enabled: true

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 15000
  threadpool:
    default:
      coreSize: 40
      maximumSize: 100
      maxQueueSize: 100

如不需要这些高级配置只需要按下列步骤进行即可

  • Consumer里实现ConsumerClient
    我们之前写了一个接口ConsumerClient假装是ProviderController,那么当我们实现它的时候,它就成为了一个编写降级服务的类
@Component
public class ConsumerFallBack implements ConsumerClient {

    //feign Hystrix 服务降级方法
    @Override
    public List<User> findAll() throws Exception {
        System.out.println("feign Hystrix");
        return null;
    }
}
  • Consumer的配置文件中编写Feign Hystrix的配置
    因为Feign内置了熔断器,我们就不再需要熔断器本身的一些配置和注解了,只需要设置Feign的Hystrix配置即可
# feign 熔断器
feign:
  hystrix:
    enabled: true
  • Consumer的Feign客户端添加FallBack所在的类
@FeignClient(value = "provider",path = "/provider",fallback = ConsumerFallBack.class)
public interface ConsumerClient {
    //伪装成ProviderController中的findAll
    @GetMapping("/findAll")
    List<User> findAll() throws Exception;

}

在这里插入图片描述

  • 启动后,可以看到熔断器有效
    在这里插入图片描述
    Consumer的控制台这边可以看到输出了我们降级方法中的内容
    在这里插入图片描述

GateWay网关

  • 创建一个SpringBoot组件Gateway
    方式同创建provider 和consumer
    文件结构如下:
    在这里插入图片描述

  • Gateway的pom文件中添加依赖
    注意这里有个大坑,因为我们之前在父工程的中添加了spring-boot-starter-web的依赖,但是网关使用时不能有这个依赖,所以需要我们从父工程中移除,分别添加到使用到这个依赖的子工程中(在本项目里就是consumer、provider、eureka)
    一定要移除,移除完成后在gateway的pom文件中添加如下依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  • 编写Gateway启动类GateWayApplication
    注意网关也只是个微服务,需要注册到注册中心
@EnableDiscoveryClient
@SpringBootApplication
public class GateWayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GateWayApplication.class);
    }
}
  • 配置Gateway的配置文件Appliaction.yml
    网关重在配置
server:
  port: 9000

# 服务名称
spring:
  application:
    name: gateway
  # 网关配置
  cloud:
    gateway:
      # 全局配置
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 *表示通配 允许所有的域 实际可以填写ip
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
      # 网关路由配置
      routes:
        - id: provider-router # 路由id 唯一标识
          uri: lb://provider # 路由地址,动态路由  lb是一个网关的协议
          predicates: # 断言
            - Path=/provider/** # 请求url 多个使用逗号 即所有的/provider/**请求会交由uri中写的服务provider来处理

#eureka配置
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
  • 编写过滤器TokenFilter
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class TokenFliter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("There is GateWay");

        return chain.filter(exchange);
    }

    //可能有多个网关,执行顺序由getOrder决定
    @Override
    public int getOrder() {
        return 0;
    }
}
  • 完成后顺序启动Eureka、Provider、GateWay
    可以看到注册中心出现了两个服务provider和gateway
    在这里插入图片描述
    本来我们访问provider的findAll方式是这样
    在这里插入图片描述
    也就是访问的是provider服务
    但当我们配置了网关以后,可以将provider的真正服务端口号隐藏起来,访问网关服务来访问到findAll
    注意:这点不要忘,从网关的端口号去访问服务,而不是服务本身的端口号 ,我们的目的是隐藏真实的端口号
    在这里插入图片描述
    网关控制台这边也可以看到我们通过TokenFliter中的fliter方法打印的字样
    在这里插入图片描述
路由前缀
  • 网关的配置文件中更改配置(隐藏前缀)
    在gateway中可以通过配置路由的过滤器PrefixPath来实现映射路径的前缀添加,可以起到隐藏接口地址的作用,避免接口地址暴露
    啥意思呢
    就是以我们的provider来看,我们之前请求路径是/provider/findAll,但通过配置之后我们可以隐藏/provider,只访问findAll即可
predicates:
  - Path=/**
filters:
  - PrefixPath=/provider

测试
在这里插入图片描述
可以看到通过该配置,即隐藏了实际服务的端口号也隐藏了请求的前缀

  • 网关的配置文件中更改配置(增加前缀)
    在gateway中通过配置路由过滤器StripPrefix,实现映射路径中的地址的去除。
    通过StripPrefix=1来指定路由中需要去除的前缀个数
    例如我们通过访问路径/api/provider/findAll将会被路由解析到/provider/findAll
predicates:
  - Path=/api/**
filters:
  - StripPrefix=1

测试
在这里插入图片描述
PS:以上两个也可以都加

          predicates: # 断言
            - Path=/api/** # 
          filters:
            - StripPrefix=1
            - PrefixPath=/provider

但一定要注意StripPrefix和PrefixPath的顺序问题
写成现在这样的意思就是先去掉/api,再添加/provider,所以请求的时候可以直接用/api/findAll请求

过滤器

过滤器是网关的一个重要功能,实现了请求的鉴权,前面路由前缀的功能也是使用过滤器实现的

过滤器分为全局过滤器和局部过滤器
可以同时配置全局过滤器和局部过滤器

全局过滤器(实现GlobalFilter及Ordered)
例如上面所说的TokenFilter

@Component
public class TokenFliter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("There is GateWay");

        return chain.filter(exchange);
    }

    //设置过滤器执行顺序,值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

自定义局部过滤器
有两种实现方式

  • 直接实现GatewayFilter接口,重写里面的filter方法
  • 继承AbstractGatewayFilterFactory类,重写apply方法(实际上底层也是重写filter方法)
    以下是第二种方式的代码示例
package com.dean.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;


//类名的格式必须为XXXGatewayFilterFactory
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
    public static final String AUTH_NAME = "name";

    //构造函数
    public AuthGatewayFilterFactory() {
        super(Config.class);
    }

    //如果要获得配置的-Auth的值,必须重写这个方法,否则拿不到
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(AUTH_NAME);
    }


    //过滤逻辑
    @Override
    public GatewayFilter apply(AuthGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            System.out.println("AuthGateWay Filter");
            System.out.println(config.name);
            //可以通过exchange拿到uri,进行拼接或处理  参考PrefixPathGateWayFilterFactory
            return chain.filter(exchange);
        };
    }

    //用于接收配置文件中的参数
    public static class Config {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

主要有几个重点:
1、类名必须是XXXGatewayFilterFactory
2、配置文件里的配置就是这个XXX
3、shortcutFieldOrder()方法必须重写,否则拿不到配置的值

gateway的yaml中路由处进行配置

      # 网关路由配置
      routes:
        #id是个列表 有多个路由配置时就写多个id及下面的配置,以下是完整的一个
        - id: provider-router # 路由id 唯一标识
          uri: lb://provider # 路由地址,动态路由  lb是一个网关的协议
          predicates: # 断言
            - Path=/**
#            - Path=/api/** # 请求url 多个使用逗号 即所有的/provider/**请求会交由uri中写的服务provider来处理
          # 添加filter后,会在predicates上的所有请求上添加/provider
          # 所以实际请求就不需要再写/provider了
          filters:
#            - StripPrefix=1
            - PrefixPath=/provider
            - Auth=name  #过滤器的名称取得是AuthGatewayFilterFactory去掉GatewayFilterFactory的部分

其实只是多了一个我们自己增加的-Auth

通过网关访问接口后控制台的输出
在这里插入图片描述

PS:当我们想要配置多个路由及每个路由配置自己的过滤器时
从配置中可以看出route下的-id是个列表的格式
在这里插入图片描述

彩蛋:GateWay自带的过滤器
gateway自带的过滤器有几十个,常见的有

过滤器名称说明
AddRequestHeader对匹配上的请求加上Header
AddRequestParameters对匹配上的请求路由
AddResponseHeader对从网关返回的响应添加Header
StripPrefifix对匹配上的请求路径去除前缀
PrefifixPath对匹配上的请求路径添加前缀

例如配置文件中配置

spring:
  application:
    name: gateway
  cloud:
    gateway:
      default-filters:
        - AddResponseHeader=ilove,web

请求后就可以看到响应头添加了ilove=web了

感兴趣的话,可以去官方文档看看

以上。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值