概念
网关作为系统的唯一流量入口。
1.跟客户端交互只有一个地址
2.路由转发
3.认证安全
4.限流熔断
5.日志监控
Spring Cloud Gateway 配置项的说明
断言(Predicate):参照 Java8 的新特性Predicate,允许开发人员匹配 HTTP 请求中的任何内容,比如请求头或请求参数,最后根据匹配结果返回一个布尔值。
路由(route):由ID、目标URI、断言集合和过滤器集合组成。如果聚合断言结果为真,则转发到该路由。
过滤器(filter):可以在返回请求之前或之后修改请求和响应的内容。
路由 Route:
Route 主要由 路由id、目标uri、断言集合和过滤器集合组成,那我们简单看看这些属性到底有什么作用。
(1)id:路由标识,要求唯一,名称任意(默认值 uuid,一般不用,需要自定义)
(2)uri:请求最终被转发到的目标地址
(3)order: 路由优先级,数字越小,优先级越高
(4)predicates:断言数组,即判断条件,如果返回值是boolean,则转发请求到 uri 属性指定的服务中
(5)filters:过滤器数组,在请求传递过程中,对请求做一些修改
断言 Predicate:
Predicate 来自于 Java8 的接口。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
Predicate 可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。Spring Cloud Gateway 内置了许多 Predict,这些 Predict 的源码在 org.springframework.cloud.gateway.handler.predicate 包中。
过滤器 filter:
Gateway 过滤器的生命周期:
PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
Gateway 过滤器从作用范围可分为两种:
GatewayFilter:应用到单个路由或者一个分组的路由上(需要在配置文件中配置)
GlobalFilter:应用到所有的路由上(无需配置,全局生效)
在org.springframework.cloud.gateway.filter中
入门
使用spring init工具加入gateway、 eureka-client
pom.xml配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>service-a</artifactId> <version>0.0.1-SNAPSHOT</version> <name>service-a</name> <description>Demo project for Spring Boot</description>
<properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASE</spring-boot.version> <spring-cloud.version>Hoxton.SR9</spring-cloud.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.5</version> </dependency> </dependencies>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>com.example.service_a.ServiceAApplication</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
</project>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| spring: application: name: gateway cloud: gateway: routes: - id: service_a uri: http://localhost:8762 predicates: - Path=/gateway/servicea/** - Weight=group1, 8 filters: - StripPrefix=2
server: port: 9000 servlet: context-path: /${spring.application.name}
|
启动 eureka、gateway、service-a项目
这里eureka只是让gateway注册到eureka-server上不报错。
访问http://localhost:9000/gateway/servicea/test2 可以收到请求返回
整合eureka
Eureka-client 早加进来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| spring: application: name: gateway cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true server: port: 9000
eureka: client: service-url: defaultZone: http://localhost:8761/eureka/
|
这里去掉了网关项目的根路径。
至此惟一地址、路由转发已完成了。
权限安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @Component public class TokenGatewayFilter implements GlobalFilter, Ordered {
@Override public int getOrder() { return 0; }
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = null; token = exchange.getRequest().getHeaders().getFirst("Authorization"); if(StringUtils.isEmpty(token)){ token = exchange.getRequest().getQueryParams().getFirst("token"); } if(StringUtils.isEmpty(token)){ HttpCookie cookies = exchange.getRequest().getCookies().getFirst("token"); if(cookies!=null){ token = cookies.getValue(); } } if(StringUtils.isEmpty(token)){
return responseFailRs(exchange, "token参数未传入"); }
return chain.filter(exchange); } private Mono<Void> responseFailRs(ServerWebExchange exchange, String str) { ServerHttpResponse serverHttpResponse = exchange.getResponse(); serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED); serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8); byte[] bytes = str.getBytes(StandardCharsets.UTF_8); DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes); return serverHttpResponse.writeWith(Flux.just(buffer)); }
}
|
这里使用的全局的过滤器。
限流
1.可以使用自带的限流,RequestRateLimiterGatewayFilterFactory
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
|
1 2 3 4 5 6 7 8 9 10
| spring: cloud: gateway: default-filters: - name: RequestRateLimiter args: key-resolver: "#{@hostAddrKeyResolver}" redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 1 redis-rate-limiter.requestedTokens: 1
|
2.自定义局部限流
3.全局限流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
@Component @Slf4j @Order(-1) public class RequestRateLimitFilter implements GlobalFilter { private static final Cache<String, RateLimiter> RATE_LIMITER_CACHE = CacheBuilder .newBuilder() .maximumSize(1000) .expireAfterAccess(1, TimeUnit.HOURS) .build();
private static final double DEFAULT_PERMITS_PER_SECOND = 1;
@SneakyThrows @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String remoteAddr = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress(); RateLimiter rateLimiter = RATE_LIMITER_CACHE.get(remoteAddr, () -> RateLimiter.create(DEFAULT_PERMITS_PER_SECOND)); if (rateLimiter.tryAcquire()) { return chain.filter(exchange); } ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS); response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); DataBuffer dataBuffer = response.bufferFactory().wrap("Too Many Request!!!".getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(dataBuffer)); } }
|
计数器算法、固定窗口算法、滑动窗口算法、漏桶算法、令牌桶算法
熔断
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
|
1 2 3 4 5 6 7 8
| spring: cloud: gateway: default-filters: - name: Hystrix args: name: fallback fallbackUri: forward:/fallback
|
1 2 3 4 5 6 7 8 9
| @RestController public class FallBackController {
@GetMapping("/fallback") public String fallback() { return "Error:fallback"; }
}
|
启动 eureka、service-a、gateway,这时gateway已经获取到service-a服务,
这时关闭service-a服务,访问http://localhost:9000/service-a/test2 时返回Error:fallback
日志监控
可以调整日志级别,
1 2 3
| logging: level: org.springframework.cloud.gateway: trace
|
可以使用elk收集日志。
可以加入actuator监控
如何对外提供
使用nginx配置负载均衡。使用nginx的负载均衡剔除不可用的服务。