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
-
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>
-
配置文件
- spring.cloud.gateway.routes配置路由规则和路径等
- 如果断言没有指定匹配路径,则表示到9201端口的请求都会被转发到8201,
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路由到匹配的地址
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 ,发现已经返回了服务降级的处理信息
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的错误
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
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
-
点击应用墙,选择admin-client查看监控信息
-
监控信息概览
-
度量指标信息,比如JVM、Tomcat及进程信息
-
环境变量信息,比如系统属性、系统环境变量以及应用配置信息
-
查看所有创建的Bean信息
-
查看日志信息,需要添加以下配置才能开启
logging: file: name: admin-client.log #添加开启admin的日志监控
-
查看JVM信息
-
查看映射信息
-
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
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
个人公众号目前正初步建设中,如果喜欢可以关注我的公众号,谢谢!