feign 第一次调用超时_Spring Cloud:Feign,GateWay,Config

a4ea936fa6f3163c990cf6bf8549aa17.png

Feign 远程调用组件

在之前的案例中,服务消费者调用服务提供者的时候使用 RestTemplate 技术。

Feign 简介

Feign 是 Netflix 开发的一个轻量级 RESTful 的 HTTP 服务客户端(用它来发起请求,远程调用的),是以 Java 接口注解的方式调用 Http 请求,而不用像 Java 中通过封装 HTTP 请求报文的方式直接调用,Feign 被广泛应用在 Spring Cloud 的解决方案中。

  • 类似于 Dubbo,服务消费者拿到服务提供者的接口,然后像调用本地接口方法一样去调用,实际发出的是远程的请求。

Feign 更加便捷,优雅的调用 HTTP API:不需要去拼接 url 然后调用 restTemplate 的 api,在 Spring Cloud 中,使用 Feign 非常简单,创建一个接口(在消费者 - 服务调用方这一端),并在接口上添加一些注解,代码就完成了。

Spring Cloud 对 Feign 进行了增强,使 Feign 支持了 Spring MVC 注解 - OpenFeign。

本质:封装了 Http 调用流程,更符合面向接口化的编程习惯,类似于 Dubbo 的服务调用。

Feign 配置应用

在服务调用者工程(消费)创建接口(添加注解)。

效果:Feign = RestTemplate + Ribbon + Hystrix。

1)服务消费者工程(页面静态化微服务)中引入Feign依赖(或者父类工程):

<!-- 引入 openFeign 远程调用组件 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2)服务消费者工程(静态化微服务)启动类使用注解 @EnableFeignClients 添加 Feign 支持:

@SpringBootApplication
@EnableDiscoveryClient
// @EnableCircuitBreaker // 启用熔断服务
@EnableFeignClients // 开启 Feign 客户端
public class PageApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(PageApplication.class,args);
    }
​
}

注意:此时去掉 Hystrix 熔断的支持注解 @EnableCircuitBreaker,因为 Feign 会自动引入熔断服务。

3)在消费者微服务中创建 Feign 接口:

com.renda.page.feign.ProductFeign

/**
 * 自定义 Fegin 接口,
 * 调用 Product 微服务的所有接口方法都在此进行定义
 *
 * @author Renda Zhang
 * @since 2020-11-02 17:01
 */
@FeignClient(name = "lagou-service-product")
public interface ProductFeign {
​
    @GetMapping("/product/query/{id}")
    public Products queryById(@PathVariable Integer id);
​
    @GetMapping("/service/port")
    public String getPort();
​
}

注意:

1)@FeignClient 注解的 name 属性用于指定要调用的服务提供者名称,和服务提供者 yml 文件中 spring.application.name 保持一致。

2)接口中的接口方法,就好比是远程服务提供者 Controller 中的 Handler 方法(只不过如同本地调用了),那么在进行参数绑定的时,可以使用 @PathVariable@RequestParam@RequestHeader 等,这也是 OpenFeign 对 SpringMVC 注解的支持,但是需要注意 value 必须设置,否则会抛出异常。

3) @FeignClient(name = "lagou-service-product"),name 在消费者微服务中只能出现一次。升级 Spring Boot 2.1.0,Spring Cloud Greenwich.M1 版本后,在 2 个 Feign 接口类内定义相同的名字,@FeignClient(name = "相同的名字") 就会出现报错,在之前的版本不会提示报错。所以最好将调用一个微服务的信息都定义在一个 Feign 接口中。

改造 PageController 中原有的调用方式:

@RestController
@RequestMapping("/page")
public class PageController {
​
    @Autowired
    private ProductFeign productFeign;
​
    @GetMapping("/getProduct/{id}")
    public Products getProduct(@PathVariable Integer id) {
        return productFeign.queryById(id);
    }
​
    @GetMapping("/loadProductServicePort")
    public String getProductServerPort() {
        return productFeign.getPort();
    }
​
    ...
​
}

Feign 对负载均衡的支持

Feign 本身已经集成了 Ribbon 依赖和自动配置,因此不需要额外引入依赖,可以通过 ribbon.xx 来进行全局配置,也可以通过服务名 .ribbon.xx 来对指定服务进行细节配置配置(参考之前 Ribbon 的配置,此处略)。

Feign 默认的请求处理超时时长 1s,有时候业务确实执行的需要一定时间,那么这个时候,就需要调整请求处理超时时长,Feign 自己有超时设置,如果配置 Ribbon 的超时,则会以 Ribbon 的为准。

# 针对的被调用方微服务名称,不加就是全局生效
lagou-service-product:
  ribbon:
    # 请求连接超时时间
    ConnectTimeout: 2000
    # 请求处理超时时间
    ReadTimeout: 15000
    # 对所有操作都进行重试
    OkToRetryOnAllOperations: true
    ## 根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由 MaxAutoRetries 配置),
    ## 如果不行,就换一个实例进行访问,如果还不行,再换一次实例访问(更换次数由 MaxAutoRetriesNextServer 配置),
    ## 如果依然不行,返回失败信息。
    # 对当前选中实例重试次数,不包括第一次调用
    MaxAutoRetries: 2
    # 切换实例的重试次数
    MaxAutoRetriesNextServer: 2
    # 负载策略调整
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule

Feign 对熔断器的支持

1)在 Feign 客户端工程配置文件 application.yml 中开启 Feign 对熔断器的支持:

feign:
  # 开启 Feign 对熔断器支持
  hystrix:
    enabled: true

Feign 的超时时长设置那其实就上面 Ribbon 的超时时长设置。

Hystrix 超时设置就按照之前 Hystrix 设置的方式就 OK 了。

注意:

1)开启 Hystrix 之后,Feign 中的方法都会被进行一个管理了,一旦出现问题就进入对应的回退逻辑处理

2)针对超时这一点,当前有两个超时时间设置(Feign / Hystrix),熔断的时候是根据这两个时间的最小值来进行的,即处理时长超过最短的那个超时时间了就熔断进入回退降级逻辑。

# 配置熔断策略:
hystrix:
  command:
    default:
      circuitBreaker:
        # 强制打开熔断器,如果该属性设置为 true,强制断路器进入打开状态,将会拒绝所有的请求。 默认 false 关闭的
        forceOpen: false
        # 触发熔断错误比例阈值,默认值 50%
        errorThresholdPercentage: 50
        # 熔断后休眠时长,默认值 5 秒
        sleepWindowInMilliseconds: 3000
        # 熔断触发最小请求次数,默认值是 20
        requestVolumeThreshold: 2
      execution:
        isolation:
          thread:
            # 熔断超时设置,默认为 1 秒
            timeoutInMilliseconds: 2000

2)自定义 FallBack 处理类(需要实现 FeignClient 接口)

com.renda.page.feign.ProductFeignFallBack

@Component
public class ProductFeignFallBack implements ProductFeign {

    @Override
    public Products queryById(Integer id) {
        return null;
    }

    @Override
    public String getPort() {
        return "-1";
    }
}

com.renda.page.feign.ProductFeign

@FeignClient(name = "lagou-service-product", fallback = ProductFeignFallBack.class)
public interface ProductFeign {
​
    @GetMapping("/product/query/{id}")
    public Products queryById(@PathVariable Integer id);
​
    @GetMapping("/service/port")
    public String getPort();
​
}

Feign 对请求压缩和响应压缩的支持

Feign 支持对请求和响应进行 GZIP 压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:

feign:
  # 开启 Feign 对熔断器支持
  hystrix:
    enabled: true
  # 开启请求和响应的压缩设置,默认是不开启的
  compression:
    request:
      enabled: true
      # 默认值
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true

GateWay 网关组件

网关:微服务架构中的重要组成部分。

局域网中就有网关这个概念,局域网接收或者发送数据出去通过这个网关,比如用 Vmware 虚拟机软件搭建虚拟机集群的时候,往往需要选择 IP 段中的一个 IP 作为网关地址。

Spring Cloud GateWay 只是众多网关解决方案中的一种。

GateWay 简介

Spring Cloud GateWay 是 Spring Cloud 的一个全新项目,目标是取代 Netflix Zuul,它基于 Spring 5.0 + SpringBoot 2.0 + WebFlux(基于高性能的 Reactor 模式响应式通信框架 Netty,异步非阻塞模型)等技术开发,性能高于 Zuul(Zuul 1 是阻塞模型,Zuul 2 是非阻塞模型,但是 Zuul 2 已经停止维护)。官方测试,GateWay 是 Zuul 的 1.6 倍,旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

Spring Cloud GateWay 不仅提供统一的路由方式(反向代理)并且基于 Filter(定义过滤器对请求过滤,完成一些功能)链的方式提供了网关基本的功能,例如:鉴权、流量控制 - 流量削峰、熔断、路径重写、日志监控等。

网关在架构中的位置:

客户端群 ----> 负载均衡 ----> 网关集群

客户端群:[浏览器,移动端,IOT,外部接口]

网关集群:[GateWay, GateWay]

网关集群 ----> [微服务A,微服务B,微服务C,微服务D]

GateWay 核心概念

Spring Cloud GateWay 天生就是异步非阻塞的,基于 Reactor 模型(同步非阻塞的 I/O 多路复用机制)。

一个请求 --> 网关根据一定的条件匹配 -- 匹配成功之后可以将请求转发到指定的服务地址;而在这个过程中,可以进行一些比较具体的控制(限流、日志、黑白名单)。

  • 路由 - route: 网关最基础的部分,也是网关比较基础的工作单元。路由由一个 ID、一个目标 URL(最终路由到的地址)、一系列的断言(匹配条件判断)和 Filter 过滤器(精细化控制)组成。如果断言为 true,则匹配该路由。
  • 断言 - predicates:参考了 Java 8 中的断言 java.util.function.Predicate,开发人员可以匹配 Http 请求中的所有内容,包括请求头、请求参数等(类似于 nginx 中的 location 匹配一样),如果断言与请求相匹配则路由。
  • 过滤器 - filter:一个标准的 Spring webFilter,使用过滤器,可以在请求之前或者之后执行业务逻辑。

GateWay 如何工作

GateWay Client 客户端 
<----> Gateway Handler Mapping
<----> Gateway Web Handler
<----> Filter-Filter-Filter-ProxyFilter
<----> Proxied Service

Spring Cloud Gateway: 
[
  Gateway Handler Mapping, 
  Gateway Web Handler, 
  Filter-Filter-Filter-ProxyFilter
]

Gateway Handler Mapping:
网关控制器映射,
找到与请求相匹配的路由,
将其发送到 Gateway Web Handler。

Gateway Web Handler:
网关 Web 控制器,
通过指定的过滤器将请求发送到实际的服务,
执行业务逻辑,然后返回。

Filter-ProxyFilter:
执行之前 pre,执行之后 post;
pre - 鉴权、参数校验、流量限制、日志记录
post - 响应内容、修改响应头、日志

客户端向 Spring Cloud GateWay 发出请求,然后在 GateWay Handler Mapping 中找到与请求相匹配的路由,将其发送到 GateWay Web Handler;Handler 再通过指定的过滤器链来将请求发送到实际的服务执行业务逻辑,然后返回。过滤器可能会在发送代理请求之前(pre)或者之后(post)执行业务逻辑。

Filter 在 pre 类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在 post 类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等。

GateWay 应用

使用网关对静态化微服务进行代理(添加在它的上游,相当于隐藏了具体微服务的信息,对外暴露的是网关)。

GateWay 不需要使用 web 模块,它引入的是 WebFlux(类似于 SpringMVC)

创建工程 lagou-cloud-gateway-server。

导入依赖,不需要依赖父工程 lagou-parent,独立一个工程:

<?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>com.renda</groupId>
    <artifactId>lagou-cloud-gateway-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- spring boot 父启动器依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <dependencyManagement>
        <!-- spring cloud 依赖版本管理 -->
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-commons</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- GateWay 网关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- 引入 webflux -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <!-- 日志依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- lombok 工具 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
        <!-- 引入 Jaxb,开始 -->
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.2.10-b140310.1920</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!-- 引入 Jaxb,结束 -->
        <!-- Actuator 可以帮助你监控和管理 Spring Boot 应用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 链路追踪 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
            <!-- 打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

注意:不要引入 starter-web 模块,需要引入 web-flux。

application.yml 配置文件内容:

server:
  port: 9300
eureka:
  client:
    serviceUrl: # eureka server的路径
      defaultZone: http://LagouCloudEurekaServerA:9200/eureka,http://LagouCloudEurekaServerB:9201/eureka
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
spring:
  application:
    name: lagou-cloud-gateway
  # 网关的配置
  cloud:
    gateway:
      routes: # 配置路由
        - id: service-page-router
          # 动态路由:从注册中心获取对应服务的实例
          # lb - load balance 负载均衡
          uri: lb://lagou-service-page
          # 当断言成功后,交给某一个微服务处理时使用的是转发
          predicates:
            - Path=/page/**
        - id: service-product-router
          uri: lb://lagou-service-product
          predicates:
            - Path=/product/**
          filters:
            # 断言成功后,交给具体的 uri 对应的微服务处理,将 uri 的第一个参数去掉
            - StripPrefix=1

- StripPrefix=1 将 uri 第一段去掉,product/service/port --> service/port

启动类 com.renda.gateway.GateWayServerApplication

@SpringBootApplication
@EnableDiscoveryClient
public class GateWayServerApplication {

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

}

使用 Postman 进行测试:

通过网关访问商品微服务 - GET http://localhost:9300/product/product/query/1

通过网关访问商品微服务实例的端口 - GET http://localhost:9300/product/service/port

通过网关访问页面静态化服务 - GET http://localhost:9300/page/getProduct/1

GateWay 路由规则详解

Spring Cloud GateWay 内置了很多 Predicates 功能,实现了各种路由匹配规则(通过 Header、请求参数等作为条件)匹配到对应的路由。

RoutePredicateFactory 路由断言工厂:
[
DateTime 时间类断言 - 根据请求时间在配置时间之前/之后/之间,
Cookie 类断言 - 指定 Cookie 正则匹配指定值,
Header 请求头类断言 - 指定 Header 正则匹配指定值/请求头中是否包含某个属性,
Host 请求主机类断言 - 请求 Host 匹配指定值,
Method 请求方式类断言 - 请求 Method 匹配指定请求方式,
Path 请求路径类断言 - 请求路径正则匹配指定值,
QueryParam 请求参数类断言 - 查询参数正则匹配指定值,
RemoteAddr 远程地址类断言 - 请求远程地址匹配指定值
]

时间点后匹配

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]

时间点前匹配

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

时间区间匹配

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

指定 Cookie 正则匹配指定值

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - Cookie=chocolate, ch.p

指定 Header 正则匹配指定值

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - Header=X-Request-Id, d+

请求 Host 匹配指定值

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - Host=**.somehost.org,**.anotherhost.org

请求 Method 匹配指定请求方式

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - Method=GET,POST

请求路径正则匹配

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - Path=/red/{segment},/blue/{segment}

请求包含某参数

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - Query=green

请求包含某参数并且参数值匹配正则表达式

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - Query=red, gree.

远程地址匹配

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - RemoteAddr=192.168.1.1/24

GateWay 动态路由详解

GateWay 支持自动从注册中心中获取服务列表并访问,即所谓的动态路由。

实现步骤如下:

1)pom.xml 中添加注册中心客户端依赖(因为要获取注册中心服务列表,eureka 客户端已经引入)。

2)动态路由配置:

spring:
  application:
    name: lagou-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: service-page-router
          # 动态路由:从注册中心获取对应服务的实例
          # lb - load balance
          uri: lb://lagou-service-page
          predicates:
            - Path=/page/**
        - id: service-product-router
          uri: lb://lagou-service-product
          predicates:
            - Path=/product/**
          filters:
            - StripPrefix=1

注意:动态路由设置时,uri 以 lb:// 开头(lb 代表从注册中心获取服务),后面是需要转发到的服务名称。

GateWay 过滤器

GateWay 过滤器简介

从过滤器生命周期(影响时机点)的角度来说,主要有两个 pre 和 post:

  • pre - 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择 请求的微服务、记录调试信息等。
  • post - 这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

从过滤器类型的角度,Spring Cloud GateWay 的过滤器分为 GateWayFilter 和 GlobalFilter 两种:

  • GateWayFilter - 应用到单个路由路由上。
  • GlobalFilter - 应用到所有的路由上。

Gateway Filter 可以去掉 url 中的占位后转发路由,比如:

predicates:
  - Path=/product/**
filters:
  - StripPrefix=1

注意:GlobalFilter 全局过滤器是使用比较多的过滤器。

自定义全局过滤器实现IP访问限制(黑白名单)

请求过来时,判断发送请求的客户端的ip,如果在黑名单中,拒绝访问。

自定义 GateWay 全局过滤器时,实现 Global Filter 接口即可,通过全局过滤器可以实现黑白名单、限流等功能。

package com.renda.gateway.filter;
​
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
import java.util.ArrayList;
import java.util.List;
​
/**
 * 通常情况下进行网关自定义过滤器时,
 * 需要实现两个接口:
 * GlobalFilter,
 * Ordered (指定过滤器的执行顺序)
 *
 * @author Renda Zhang
 * @since 2020-11-02 22:23
 */
@Slf4j // lombok 自动生成 logger
@Component // 让容器扫描到,等同于注册了
public class BlackListFilter implements GlobalFilter, Ordered {
​
    /**
     * 加载黑名单列表
     * MySQL ->  Redis -> 加载到内存中
     */
    private static List<String> blackList = new ArrayList<>();
​
    static {
        // 将本机地址加入到黑名单中
        blackList.add("0:0:0:0:0:0:0:1");
        blackList.add("127.0.0.1");
    }
​
    /**
     * GlobalFilter 过滤器的核心逻辑:
     * 获取客户端 ip,判断是否在黑名单中,在的话就拒绝访问,不在的话就放行
     *
     * @param exchange 封装了 request 和 response 上下文
     * @param chain    网关过滤器链
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求和响应对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 获取来访者的 IP 地址
        String clientIP = request.getRemoteAddress().getHostString();
        // 判断是否在黑名单中
        if (blackList.contains(clientIP)) {
            // 如果是黑名单拒绝访问,设置状态码为没有授权
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            log.info("=====>IP:" + clientIP + " 在黑名单中,将被拒绝访问!");
            String data = "request has been denied";
            DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
            return response.writeWith(Mono.just(wrap));
        }
        // 合法请求,放行,执行后续的过滤器
        return chain.filter(exchange);
    }
​
    /**
     * Ordered, 定义过滤的顺序,
     * getOrder()返回值的大小决定了过滤器执行的优先级,
     * 越小优先级越高
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

以上代码使用了 Lombok 的 @Slf4j 来自动生成类的 logger,然后可以直接使用 log.info(...) 来把类的运行信息输出到控制台。

GateWay 高可用

网关作为非常核心的一个部件,如果挂掉,那么所有请求都可能无法路由处理,因此需要做 GateWay 的高可用。

GateWay 的高可用很简单:可以启动多个 GateWay 实例来实现高可用,在 GateWay 的上游使用 Nginx 等负载均衡设备进行负载转发以达到高可用的目的。

启动多个 GateWay 实例(假如说两个,一个端口 9002,一个端口 9003),剩下的就是使用 Nginx 等完成负载代理即可。示例如下:

# 配置多个 GateWay 实例
upstream gateway {
    server 127.0.0.1:9002;
    server 127.0.0.1:9003; 
} 
location / {
    proxy_pass http://gateway; 
}

Spring Cloud Config 分布式配置中心

分布式配置中心应用场景

往往使用配置文件管理一些配置信息,比如 application.yml

单体应用架构,配置信息的管理、维护并不会显得特别麻烦,手动操作就可以,因为就一个工程。

微服务架构,因为分布式集群环境中可能有很多个微服务,不可能一个一个去修改配置然后重启生效,在一定场景下还需要在运行期间动态调整配置信息,比如:根据各个微服务的负载情况,动态调整数据源连接池大小,希望配置内容发生变化的时候,微服务可以自动更新。

场景总结如下:

1)集中配置管理,一个微服务架构中可能有成百上千个微服务,所以集中配置管理是很重要的(一次修改、到处生效)。

2)不同环境不同配置,比如数据源配置在不同环境(开发 dev,测试 test,生产 prod)中是不同的。

3)运行期间可动态调整。例如,可根据各个微服务的负载情况,动态调整数据源连接池大小等配置修改后可自动更新

4)如配置内容发生变化,微服务可以自动更新配置。

那么就需要对配置文件进行集中式管理,这也是分布式配置中心的作用。

Spring Cloud Config

Config 简介

Spring Cloud Config 是一个分布式配置管理方案,包含了 Server 端和 Client 端两个部分。

[
微服务 A + config client,
微服务 B + config client,
微服务 C + config client,
]
-----> config server ----> Git/SVN

Server 端:提供配置文件的存储、以接口的形式将配置文件的内容提供出去,通过使用 @EnableConfigServer 注解在 Spring Boot 应用中非常简单的嵌入。

Client 端:通过接口获取配置数据并初始化自己的应用。

Config 分布式配置应用

说明:Config Server 是集中式的配置服务,用于集中管理应用程序各个环境下的配置。 默认使用 Git 存储配置文件内容,也可以 SVN。

比如,要对静态化微服务或者商品微服务的 application.yml 进行管理(区分开发环境 dev、测试环境 test、生产环境 prod)。

1)登录 GitHub 或者 Gitee,创建项目 lagou-config。

2)上传 yml 配置文件,命名规则:{application}-{profile}.yml 或者 {application}-{profile}.properties。 其中,application 为应用名称,profile 指的是环境(用于区分开发环境,测试环境、生产环境等)。示例:lagou-service-page-dev.ymllagou-service-page-test.ymllagou-service-page-prod.yml

application-dev.yml

mysql:
  user: root
person:
  name: renda

3)构建 Config Server 统一配置中心:

新建 SpringBoot 工程,引入依赖坐标(需要注册自己到 Eureka)。

<?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>lagou-parent</artifactId>
        <groupId>com.renda</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lagou-cloud-config</artifactId>

    <dependencies>
        <!-- eureka client 客户端依赖引入 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- config 配置中心服务端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
    </dependencies>

</project>

配置启动类,使用注解 @EnableConfigServer 开启配置中心服务器功能。

package com.renda.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;

/**
 * @author Renda Zhang
 * @since 2020-11-02 23:03
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer // 开启配置服务器功能
public class ConfigServerApplication {

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

}

application.yml 配置。

server:
  port: 9400
# 注册到 Eureka 服务中心
eureka:
  client:
    service-url:
      defaultZone: http://LagouCloudEurekaServerA:9200/eureka, http://LagouCloudEurekaServerB:9201/eureka
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
spring:
  application:
    name: lagou-service-config
  cloud:
    config:
      server:
        # git 配置:uri、用户名、密码、分支....
        git:
          # 配置 git 地址
          uri: https://YOUR_GIT_ADDRESS/lagou-config.git
          username: USERNAME
          password: PASSWORD
          search-paths:
            - lagou-config
      label: master

# springboot 中暴露所有的端口
management:
  endpoints:
    web:
      exposure:
        include: "*"

使用 Postman 进行测试:

GET http://127.0.0.1:9400/master/application-dev.yml

4)构建 Client 客户端(在已有页面静态化微服务基础上)

在 lagou-service-page 微服务中动态获取 config server 的配置信息。

已有工程中添加依赖坐标:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId>
</dependency>

application.yml 修改为 bootstrap.yml 配置文件。

bootstrap.yml 是系统级别的,优先级比 application.yml 高,应用启动时会检查这个配置文件,在这个配置文件中指定配置中心的服务地址,会自动拉取所有应用配置并且启用。

主要是把与统一配置中心连接的配置信息放到 bootstrap.yml

注意:需要统一读取的配置信息,从配置中心获取。

bootstrap.yml(部分):

server:
  port: 9100
Spring:
  application:
    name: lagou-service-page
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/renda01?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: password
  cloud:
    # config 客户端配置,和 ConfigServer 通信,并告知 ConfigServer 希望获取的配置信息在哪个文件中
    config:
      # application-dev.yml
      name: application
      # 后缀名称
      profile: dev
      # 分支名称
      label: master
      # ConfigServer 配置中心地址
      uri: http://localhost:9400

...

com.renda.page.controller.ConfigClientController

@RestController
@RequestMapping("/config")
public class ConfigClientController {

    @Value("${mysql.user}")
    private String user;

    @Value("${person.name}")
    private String name;

    @RequestMapping("/query")
    public String getRemoteConfig() {
        return user + ", " + name;
    }

}

使用 Postman 测试:

GET http://127.0.0.1:9100/config/query

Config 配置手动刷新

不用重启微服务,只需要手动的做一些其他的操作(访问一个地址 /refresh)刷新,之后再访问即可。

此时,客户端取到了配置中心的值,但当我们修改 GitHub 上面的值时,服务端(Config Server)能实时获取最新的值,但客户端(Config Client)读的是缓存,无法实时获取最新值。Spring Cloud 已经解决了这个问题,那就是客户端使用 post 去触发 refresh,获取最新数据。

1)Client 客户端添加依赖 springboot-starter-actuator(已添加)。

2)Client 客户端 bootstrap.yml 中添加配置(暴露通信端点)。

management:
  endpoints:
    web:
      exposure:
        include: refresh

# 也可以暴露所有的端口
management:
  endpoints:
    web:
      exposure:
        include: "*"

3)Client 客户端使用到配置信息的类上添加 @RefreshScope

@RestController
@RequestMapping("/config")
@RefreshScope // 手动刷新
public class ConfigClientController {
​
    @Value("${mysql.user}")
    private String user;
​
    @Value("${person.name}")
    private String name;
​
    @RequestMapping("/query")
    public String getRemoteConfig() {
        return user + ", " + name;
    }
​
}

4)手动向 Client 客户端发起 POST 请求,http://localhost:9100/actuator/refresh,刷新配置信息。

响应的信息:

[
    "config.client.version",
    "person.name",
    "mysql.user"
]

注意:手动刷新方式避免了服务重启。

思考:可否使用广播机制,一次通知,处处生效,方便大范围配置自动刷新。

Config 配置自动更新

实现一次通知,处处生效。

在微服务架构中,可以结合消息总线 Bus 实现分布式配置的自动更新 Spring Cloud Config + Spring Cloud Bus。

消息总线 Bus

所谓消息总线 Bus,即经常会使用 MQ 消息代理构建一个共用的 Topic,通过这个 Topic 连接各个微服务实例,MQ 广播的消息会被所有在注册中心的微服务实例监听和消费。换言之就是通过一个主题连接各个微服务,打通脉络。

Spring Cloud Bus(基于 MQ 的,支持 RabbitMq / Kafka) 是 Spring Cloud 中的消息总线方案,Spring Cloud Config + Spring Cloud Bus 结合可以实现配置信息的自动更新。

---bus-refresh---> config server 
----> RabbitMQ bus 
---AcceptMsg---> [微服务A, 微服务B]
​
[微服务A, 微服务B] ---RequestMsg---> config server

Spring Cloud Config + Spring Cloud Bus 实现自动刷新

MQ 消息代理,选择使用 RabbitMQ,ConfigServer 和 ConfigClient 都添加都消息总线的支持以及与 RabbitMQ 的连接信息。

1)Config Server 服务端 lagou-cloud-config 和客户端 lagou-service-page 都添加消息总线支持。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

2)Config Server 和客户端都添加配置

Spring:
  rabbitmq:
    host: 192.168.186.128
    port: 5672
    username: renda
    password: 123456

3)Config Server 微服务暴露端口

management:
  endpoints:
    web:
      exposure:
        include: bus-refresh
​
# 也可以暴露所有的端口
management:
  endpoints:
    web:
      exposure:
        include: "*"

4)重启各个服务,更改配置之后,向配置中心服务端发送 post 请求,各个客户端配置即可自动刷新。

使用 Postman 发起请求:POST http://127.0.0.1:9400/actuator/bus-refresh

5)Config Client 测试

使用 Postman 测试:GET http://localhost:9100/config/query

如此便在在广播模式下实现了一次请求,处处更新;

如果只想定向更新,在发起刷新请求的时候为最后面跟上要定向刷新的实例的服务名和端口号即可:POST http://localhost:9400/actuator/bus-refresh/lagou-service-page:9100

想了解更多,欢迎关注我的微信公众号:Renda_Zhang
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值