网关之Gateway
网关的作用
无网关会有哪些问题?
- 增加客户端请求的复杂度;
- 服务端鉴权复杂,每个服务都要鉴权,安全性低;
- 无法适配各种协议;
有网关
- 针对所有请求进行统一鉴权、限流、熔断、日志;
- 协议转化。针对后端多种不同的协议,在网关层统一处理后以Http对外提供服务;
- 提供统一的错误码;
- 请求转发,并且可以基于网关实现内网和外网的隔离。
灰度发布
网关之Zuul
Zuul是Netflix开源的微服务网关。它的核心由一系列过滤器组成,定义了4种标准类型的过滤器,这些会对应请求的整个生命周期。
Zuul1.x采用的是传统的thread per connection方式来处理请求,也就是针对每一个请求,会为这个请求专门分配一个线程来处理,知道这个请求完成以后才会释放线程,一旦后台服务器响应较慢,就会使得该线程被阻塞,所以它的性能不好。
网关之Gateway
Spring Cloud Gateway是Spring官网团队研发的API网关技术,它的目的是取代Zuul为微服务提供一个简单有效的API网关。
Zuul基于servlet, 需要依赖servlet容器,使用BIO模式,不支持如websocket之类的长链接;而Spring Cloud Gateway通过使用Netty + webflux,使用NIO模式。
作用
- 负载均衡
- 协议转换
- 安全(认证,黑/白名单)
- 日志记录
- 流量控制 & 熔断
- APIfacade
- 缓存
- 逻辑路由
- 金丝雀部署
- 蓝绿部署
- A/B test
三个关键词
- (路由)Route,⽹关最基础的部分,也是⽹关⽐较基础的⼯作单元。路由由⼀个ID、⼀个⽬标URL(最终路由到的地址)、⼀系列的Predicate断⾔(匹配条件判断)和Filter过滤器(精细化控制)组成。如果断⾔为true,则匹配该路由。
- 1个predicate决定当前route是否匹配
- 1个uri决定要跳转的路径
- n个filter
- (断⾔)Predicate,参考了Java8中的断⾔java.util.function.Predicate,开发⼈员可以匹配Http请求中的所有内容(包括请求头、请求参数等)(类似于nginx中的location匹配⼀样),如果断⾔与请求相匹配则路由。
- (过滤器)Filter,⼀个标准的Spring webFilter,使⽤过滤器,可以在请求之前或者之后执⾏业务逻辑。
- 基于filter chain模式
- 根据代码写法分pre & post 两种形式
- 可以修改request & response
RoutePredicateFactory
RouteFilterFactory
Filter的类型:
- Pre类型过滤器;
- Post类型过滤器。
Filter实现方式:
3. GatewayFilter只会应用到单个路由或者一个分组路由上;
4. GlobalFilter会应用到所有的路由上。
Filter的顺序
使用
- 引入jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
- 定义路由规则
server:
port: 7000
spring:
application:
name: GatewayDemo
cloud:
gateway:
# 这样写直接将 http:localhost:7000/eurekaProducer/foo 转发到 http:localhost:7000/foo,并实现负载均衡lb
# discovery:
# locator:
# enabled: true
# lower-case-service-id: true
routes:
- id: lb_manual
uri: lb://eurekaProducer
predicates:
- Path=/api/**
filters:
- StripPrefix=1
######################################
- id: redirect
uri: no://op
predicates:
- Path=/notallowed/**
filters:
- RedirectTo=302, https://www.baidu.com
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://server1:10010/eureka
fetch-registry: true
register-with-eureka: true
Redis实现限流
在Gateway中限流的实现基于内置的限流过滤器配置实现, RequestRateLimiter该过滤器会对访问到当前网关的所有请求执行限流过滤,如果被限流,默认情况下会响应HTTP 429-TooMany Requests。默认提供了RedisRateLimiter的限流实现,它采用令牌桶算法来实现限流功能。
使用
- 增加redis支持的jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
- 修改yaml文件
在原来的基础上增加:
spring:
redis:
host: localhost
port: 6379
database: 0
cloud:
gateway:
# 所有请求都要进行的filter
default-filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶中令牌的填充速度,代表允许每秒执行的请求数。
redis-rate-limiter.burstCapacity: 3 # 令牌桶的容量。表示每秒用户最大能够执行的请求数量。
key-resolver: '#{@myKeyResolver}' # 每次消耗多少token
自定义GlobalFilter
/**
* 自定义GlobalFilter
*/
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (validateToken(token)) {
return chain.filter(exchange);
} else {
setResponseStatus(exchange, HttpStatus.FOUND);
final ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set(HttpHeaders.LOCATION, "http://www.baidu.com");
return response.setComplete();
}
}
private boolean validateToken(String token) {
return !StringUtils.isEmpty(token) && token.startsWith("demo-");
}
@Override
public int getOrder() {
return 0;
}
}