SpringCloud组件(2)-简单测试及解析

251 阅读12分钟

10 Gateway(新一代API网关服务)

10.1 概述

  • Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和 Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等

  • 为了提高网关的性能,Spring Cloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层使用了高性能的Reactor模式通信框架Netty

  • Spring Cloud Gateway的目标统一的路由的方式并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标和限流

  • 源码架构

    • 可以看出Spring Cloud Gateway集成了webflux,而webflux又集成了netty

    image-20201125220853549

  • Spring Cloud Gateway特性

    • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建
    • 动态路由:能够匹配任何请求属性
    • 可以对路由指定 Predicate(断言)和 Filter(过滤器)
    • 集成Hystrix的断路器功能
    • 集成 Spring Cloud 服务发现功能
    • 易于编写的 Predicate(断言)和 Filter(过滤器)
    • 请求限流功能
    • 支持路径重写
  • 相关概念

    • Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
    • Predicate(断言):指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。 这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由
    • Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改

10.2 构建API网关

10.2.1 构建api-gateway模块演示常用功能

  • 依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
  • 配置文件

    server:
      port: 9201
    service-url:
      user-service: http://localhost:8201
    spring:
      cloud:
        gateway:
          routes:
            - id: path_route #路由的ID
              uri: ${service-url.user-service}/user/{id} #匹配后路由地址
              predicates: # 断言,路径相匹配的进行路由
                - Path=/user/{id}
    
  • 启动eureka-server,user-service和api-gateway服务,并调用地址测试:http://localhost:9201/user/1

    • 该地址是通过Gateway路由到匹配的地址

    image-20201125223856235

10.3 Route Predicate的使用

  • Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分, Spring Cloud Gateway包括许多内置的Route Predicate工厂, 所有这些Predicate都与HTTP请求的不同属性匹配,多个Route Predicate工厂可以进行组合

10.3.1 After Route Predicate

  • 在指定时间之后的请求会匹配该路由

    spring:
      cloud:
        gateway:
          routes:
            - id: after_route
              uri: ${service-url.user-service}
              predicates:
                - After=2019-09-24T16:30:00+08:00[Asia/Shanghai]
    

10.3.2 Before Route Predicate

  • 在指定时间之前的请求会匹配该路由

    spring:
      cloud:
        gateway:
          routes:
            - id: before_route
              uri: ${service-url.user-service}
              predicates:
                - Before=2019-09-24T16:30:00+08:00[Asia/Shanghai]
    

10.3.3 Between Route Predicate

  • 在指定时间区间内的请求会匹配该路由

    spring:
      cloud:
        gateway:
          routes:
            - id: before_route
              uri: ${service-url.user-service}
              predicates:
                - Between=2019-09-24T16:30:00+08:00[Asia/Shanghai], 2019-09-25T16:30:00+08:00[Asia/Shanghai]
    
    

10.3.4 Cookie Route Predicate

  • 带有指定cookie的请求会匹配该路由

    • 逗号前后分别代表key和value
    • 多个cookie可以使用多个- Cookie
    spring:
      cloud:
        gateway:
          routes:
            - id: cookie_route
              uri: ${service-url.user-service}
              predicates:
                - Cookie=username,macro
                - Cookie=password,123456
    
  • 使用curl工具发送带有cookie为username=macro的请求可以匹配该路由

    curl http://localhost:9201/user/1 --cookie "username=macro"
    

10.3.5 Header Route Predicate

  • 带有指定请求头的请求会匹配该路由

    • 逗号前后分别代表key和value
    spring:
      cloud:
        gateway:
          routes:
          - id: header_route
            uri: ${service-url.user-service}
            predicates:
            - Header=X-Request-Id, \d+
    
  • 使用curl工具发送带有请求头为Host:www.macrozheng.com的请求可以匹配该路由

    curl http://localhost:9201/user/1 -H "Host:www.macrozheng.com" 
    

10.3.6 Method Route Predicate

  • 发送指定方法的请求会匹配该路由

    spring:
      cloud:
        gateway:
          routes:
          - id: method_route
            uri: ${service-url.user-service}
            predicates:
            - Method=GET
    
  • 使用curl工具发送POST请求无法匹配该路由

    curl -X POST http://localhost:9201/user/1
    

10.3.7 Path Route Predicate

  • 发送指定路径的请求会匹配该路由

    spring:
      cloud:
        gateway:
          routes:
            - id: path_route
              uri: ${service-url.user-service}/user/{id}
              predicates:
                - Path=/user/{id}
    
  • 使用curl工具发送/user/1路径请求可以匹配该路由

    curl http://localhost:9201/user/1
    
  • 使用curl工具发送/abc/1路径请求无法匹配该路由

    curl http://localhost:9201/abc/1
    

10.3.8 Query Route Predicate

  • 带指定查询参数的请求可以匹配该路由

    spring:
      cloud:
        gateway:
          routes:
          - id: query_route
            uri: ${service-url.user-service}/user/getByUsername
            predicates:
            - Query=username
    
  • 使用curl工具发送带username=macro查询参数的请求可以匹配该路由

    curl http://localhost:9201/user/getByUsername?username=macro
    复制代码
    
  • 使用curl工具发送带不带查询参数的请求无法匹配该路由

    curl http://localhost:9201/user/getByUsername
    

10.3.9 RemoteAddr Route Predicate

  • 从指定远程地址发起的请求可以匹配该路由
spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: ${service-url.user-service}
        predicates:
        - RemoteAddr=192.168.1.1/24
  • 使用curl工具从192.168.1.1发起请求可以匹配该路由
curl http://localhost:9201/user/1

10.3.10 Weight Route Predicate

  • 使用权重来路由相应请求,以下表示有80%的请求会被路由到localhost:8201,20%会被路由到localhost:8202
    • 可以看出- weight两个不同的route同属于一个组group1,后面的整数代表分配的权重
spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: http://localhost:8201
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: http://localhost:8202
        predicates:
        - Weight=group1, 2

10.4 Route Filter的使用

  • 路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用,Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

10.4.1 AddRequestParameter GatewayFilter

  • 给请求添加参数的过滤器

    spring:
      cloud:
        gateway:
          routes:
            - id: add_request_parameter_route
              uri: http://localhost:8201
              filters:
                - AddRequestParameter=username, macro
              predicates:
                - Method=GET
    
  • 以上配置会对GET请求添加username=macro的请求参数,通过curl工具使用以下命令进行测试

    curl http://localhost:9201/user/getByUsername
    
  • 相当于发起该请求:

    curl http://localhost:8201/user/getByUsername?username=macro
    

10.4.2 StripPrefix GatewayFilter

  • 对指定数量的路径前缀进行去除的过滤器

    spring:
      cloud:
        gateway:
          routes:
          - id: strip_prefix_route
            uri: http://localhost:8201
            predicates:
            - Path=/user-service/**
            filters:
            - StripPrefix=2
    
  • 以上配置会把以/user-service/开头的请求的路径去除两位,通过curl工具使用以下命令进行测试

    • 路径是通过多级目录进行请求,也就是把前面的两位目录去除
    curl http://localhost:9201/user-service/a/user/1
    
  • 相当于发起请求

    curl http://localhost:8201/user/1
    

10.4.3 PrefixPath GatewayFilter

  • 与StripPrefix过滤器恰好相反,会对原有路径进行增加操作的过滤器

    spring:
      cloud:
        gateway:
          routes:
          - id: prefix_path_route
            uri: http://localhost:8201
            predicates:
            - Method=GET
            filters:
            - PrefixPath=/user
    
  • 以上配置会对所有GET请求添加/user路径前缀,通过curl工具使用以下命令进行测试

    curl http://localhost:9201/1
    
  • 相当于发起该请求

    curl http://localhost:8201/user/1
    

10.4.4 Hystrix GatewayFilter

  • Hystrix过滤器允许将断路器功能添加到网关路由中

  • 开启断路器功能,添加Hystrix相关依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  • 添加相关服务降级处理类

    spring:
      cloud:
        gateway:
          routes:
            - id: hystrix_route
              uri: http://localhost:8201
              predicates:
                - Method=GET
              filters:
                - name: Hystrix
                  args:
                    name: fallbackcmd
                    fallbackUri: forward:/fallback
    
  • 关闭user-service,调用该地址进行测试:http://localhost:9201/user/1 ,发现已经返回了服务降级的处理信息

    image-20201126114113334

10.4.5 RequestRateLimiter GatewayFilter

  • RequestRateLimiter过滤器可以用于限流,使用RateLimiter实现来确定是否允许当前请求继续进行,如果请求太大默认会返回HTTP 429-太多请求状态

  • 添加相关依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    
  • 添加限流策略的配置类,这里有两种限流策略;一种是根据请求参数中的username进行限流,另一种方式是根据访问IP进行限流

    @Configuration
    public class RedisRateLimiterConfig {
    
        @Bean
        public KeyResolver userKeyResolver() {
            return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("username")));
        }
    
        @Bean
        @Primary
        public KeyResolver ipKeyResolver() {
            return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostName());
        }
    }
    
  • 使用Redis进行限流,需要添加Redis和RequestRateLimiter的配置,这里对所有的GET请求都进行了按IP来限流的操作

    server:
      port: 9201
    spring:
      redis:
        host: localhost
        password: 123456
        port: 6379
      cloud:
        gateway:
          routes:
            - id: requestratelimiter_route
              uri: http://localhost:8201
              filters:
                - name: RequestRateLimiter
                  args:
                    redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
                    redis-rate-limiter.burstCapacity: 2 #令牌桶总容量
                    key-resolver: "#{@ipKeyResolver}" #用于限流的键的解析器的Bean对象的名字,它使用SpEL表达式根据#{@beanName}从Spring容器中获取Bean对象
              predicates:
                - Method=GET
    logging:
      level:
        org.springframework.cloud.gateway: debug
    
    
  • 限流算法

  • 多次请求该地址:http://localhost:9201/user/1 ,会返回状态码为429的错误

    image-20201126121352962

10.4.6 Retry GatewayFilter

  • 对路由请求进行重试的过滤器,可以根据路由请求返回的HTTP状态码来确定是否重试

    spring:
      cloud:
        gateway:
          routes:
          - id: retry_route
            uri: http://localhost:8201
            predicates:
            - Method=GET
            filters:
            - name: Retry
              args:
                retries: 1 #需要进行重试的次数
                statuses: BAD_GATEWAY #返回哪个状态码需要进行重试,返回状态码为5XX进行重试
                backoff:
                  firstBackoff: 10ms
                  maxBackoff: 50ms
                  factor: 2
                  basedOnPreviousValue: false
    
  • 当调用返回500时会进行重试,访问测试地址:http://localhost:9201/user/111 (不存在id为111的用户)

  • 可以发现user-service控制台报错2次,说明进行了一次重试

    2019-10-27 14:08:53.435 ERROR 2280 --- [nio-8201-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
    
    java.lang.NullPointerException: null
    	at com.macro.cloud.controller.UserController.getUser(UserController.java:34) ~[classes/:na]
    

10.5 配合注册中心使用

  • Gateway配合注册中心使用时,默认情况下Eureka会根据注册中心注册的服务列表,以服务名为路径创建动态路由,Gateway同样实现了该功能

10.5.1 整合Eureka使用动态路由

  • 在api-gateway服务下添加相关依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 添加application-eureka.yml配置文件

    • 这里spring.cloud.gateway.discovery.locator开启以服务名为路径创建动态路由
    server:
      port: 9201
    spring:
      application:
        name: api-gateway
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true #开启从注册中心动态创建路由的功能
              lower-case-service-id: true #使用小写服务名,默认是大写
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8001/eureka/
    logging:
      level:
        org.springframework.cloud.gateway: debug
    
  • 使用application-eureka.yml配置文件启动api-gateway服务,访问http://localhost:9201/user-service/user/1

    image-20201126132132632

10.5.2 整合Eureka使用动态路由和过滤器

  • 在结合注册中心使用过滤器的时候,我们需要注意的是uri的协议为lb,这样才能启用Gateway的负载均衡功能

  • 修改配置文件,使用PrefixPath过滤器,为所有Get请求路径添加/user路径并路由

    server:
      port: 9201
    spring:
      application:
        name: api-gateway
      cloud:
        gateway:
          routes:
            - id: prefixpath_route
              uri: lb://user-service #此处需要使用lb协议
              predicates:
                - Method=GET
              filters:
                - PrefixPath=/user
          discovery:
            locator:
              enabled: true
    eureka:
      client:
        service-url: 
          defaultZone: http://localhost:8001/eureka/
    logging:
      level:
        org.springframework.cloud.gateway: debug
    

11 Admin(微服务应用监控)

11.1 概述

Spring Boot Admin是一个开源社区项目,用于管理和监控SpringBoot应用程序,然后通过图形化界面呈现出来。Spring Boot Admin不仅可以监控单体应用,还可以和Spring Cloud的注册中心相结合来监控微服务应用

应用程序作为Spring Boot Admin Client向为Spring Boot Admin Server注册(通过HTTP)或使用SpringCloud注册中心(例如Eureka,Consul)发现, UI是的AngularJs应用程序,展示Spring Boot Admin Client的Actuator端点上的一些监控

  • Spring Boot Admin 可以提供应用的以下监控信息
    • 监控应用运行过程中的概览信息
    • 度量指标信息,比如JVM、Tomcat及进程信息
    • 环境变量信息,比如系统属性、系统环境变量以及应用配置信息
    • 查看所有创建的Bean信息
    • 查看应用中的所有配置信息
    • 查看应用运行日志信息
    • 查看JVM信息
    • 查看可以访问的Web端点
    • 查看HTTP跟踪信息

11.2 演示监控中心功能

11.2.1 创建模块作为监控中心(admin-server)

  • 依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
    </dependency>
    
  • 配置文件

    spring:
      application:
        name: admin-server
    server:
      port: 9301
    
  • 添加注解开启admin-server功能

    @SpringBootApplication
    @EnableAdminServer
    public class AdminServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(AdminServerApplication.class, args);
        }
    
    }
    

11.2.2 创建模块作为客户端注册到监控中心(admin-server)

  • 依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
    </dependency>
    
  • 配置文件

    • management配置了暴露的端口以及健康状况
    • logging.file.name将日志监控保存到文件中
    spring:
      application:
        name: admin-client
      boot:
        admin:
          client:
            url: http://localhost:9301 #配置admin-server地址
    server:
      port: 9305
    management:
      endpoints:
        web:
          exposure:
            include: '*'
      endpoint:
        health:
          show-details: always
    logging:
      file:
        name: admin-client.log #添加开启admin的日志监控
    

11.2.3 监控信息演示

  • 启动admin-server和admin-client服务,访问如下地址打开Spring Boot Admin的主页:http://localhost:9301

    image-20201126142122801

  • 点击应用墙,选择admin-client查看监控信息

    • 监控信息概览

      image-20201126142434524

    • 度量指标信息,比如JVM、Tomcat及进程信息

      image-20201126142758766

    • 环境变量信息,比如系统属性、系统环境变量以及应用配置信息

      image-20201126142909059

    • 查看所有创建的Bean信息

      image-20201126142947106

    • 查看日志信息,需要添加以下配置才能开启

      logging:
        file:
          name: admin-client.log #添加开启admin的日志监控
      

      image-20201126143111083

    • 查看JVM信息

      image-20201126143246929

    • 查看映射信息

      image-20201126143348372

11.3 结合注册中心使用

  • Spring Boot Admin结合Spring Cloud 注册中心使用,只需将admin-server和注册中心整合即可,admin-server 会自动从注册中心获取服务列表,然后挨个获取监控信息

11.3.1 修改admin-server模块

  • 添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 修改配置文件,添加注册中心配置

    spring:
      application:
        name: admin-server
    server:
      port: 9301
    
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8001/eureka/
    
  • 启动服务注册功能

    @EnableDiscoveryClient
    @EnableAdminServer
    @SpringBootApplication
    public class AdminServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(AdminServerApplication.class, args);
        }
    
    }
    

11.3.2 修改admin-client模块

  • 添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 修改配置文件,删除原来的admin-server地址配置,添加注册中心配置即可

    spring:
      application:
        name: admin-client
    server:
      port: 9305
    management:
      endpoints:
        web:
          exposure:
            include: '*'
      endpoint:
        health:
          show-details: always
    logging:
      file: admin-client.log #添加开启admin的日志监控
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8001/eureka/
    
  • 开启服务注册功能

    @EnableDiscoveryClient
    @SpringBootApplication
    public class AdminClientApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(AdminClientApplication.class, args);
        }
    
    }
    

11.3.3 功能演示

  • 启动eureka-server,使用application-eureka.yml配置启动admin-server,admin-client,user-service;Spring Boot Admin 主页发现可以看到服务信息:http://localhost:9301

    image-20201126145026182

11.4 添加登录验证

  • 通过给admin-server添加Spring Security支持来获得登录认证功能

11.4.1 创建模块演示带有登录验证的监控中心

  • 依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
        <version>2.1.5</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  • 配置文件

    spring:
      application:
        name: admin-security-server
      security: # 配置登录用户名和密码
        user:
          name: macro
          password: 123456
      boot:  # 不显示admin-security-server的监控信息
        admin:
          discovery:
            ignored-services: ${spring.application.name}
    server:
      port: 9301
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8001/eureka/
    
  • 添加配置类对SpringSecurity进行配置,便于admin-client可以注册

    @Configuration
    public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
        private final String adminContextPath;
    
        public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
            this.adminContextPath = adminServerProperties.getContextPath();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
            successHandler.setTargetUrlParameter("redirectTo");
            successHandler.setDefaultTargetUrl(adminContextPath + "/");
    
            http.authorizeRequests()
                    //1.配置所有静态资源和登录页可以公开访问
                    .antMatchers(adminContextPath + "/assets/**").permitAll()
                    .antMatchers(adminContextPath + "/login").permitAll()
                    .anyRequest().authenticated()
                    .and()
                    //2.配置登录和登出路径
                    .formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
                    .logout().logoutUrl(adminContextPath + "/logout").and()
                    //3.开启http basic支持,admin-client注册时需要使用
                    .httpBasic().and()
                    .csrf()
                    //4.开启基于cookie的csrf保护
                    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                    //5.忽略这些路径的csrf保护以便admin-client注册
                    .ignoringAntMatchers(
                            adminContextPath + "/instances",
                            adminContextPath + "/actuator/**"
                    );
        }
    }
    
  • 开启AdminServer及注册发现功能

    @EnableDiscoveryClient
    @EnableAdminServer
    @SpringBootApplication
    public class AdminSecurityServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(AdminSecurityServerApplication.class, args);
        }
    
    }
    
  • 启动eureka-server,admin-security-server,访问Spring Boot Admin 主页发现需要登录才能访问:http://localhost:9301

    img


个人公众号目前正初步建设中,如果喜欢可以关注我的公众号,谢谢!

二维码