Spring Cloud Gateway是Spring官方基于Spring 5.0、Spring Boot 2.0和Project Reactor等技术开发的网关,旨在为微服务架构提供一种简单而有效的统一的API路由管理方式,统一访问接口,Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替换Zuul,其不仅提供统一的路由方式,并基于Filter链的方式提供了网关基本的功能,例如:安全、监控\埋点、限流等。它是基于Netty的响应式开发模式
(1)创建工程导入坐标
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
(2)配置启动类
@SpringBootApplication
public class ShopGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ShopGatewayApplication.class, args);
}
}
(3)编写配置文件
server:
port: 8089
spring:
application:
name: api-gateway
#配置gateway的路由
cloud:
gateway:
routes:
#路由ID\URI\断言
- id: product-api
uri: http://127.0.0.1:9001
predicates:
- Path=/product/**
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
predicates:
- Cookie=chocolate, ch.p
predicates:
- Header=X-Request-Id, \d+
predicates:
- Host=**.somehost.org,**.anotherhost.org
predicates:
- Method=GET # 所有GET请求都将被路由
predicates:
# segment为Map类型变量
- Path=/foo/{segment},/bar/{segment}
# 也可以直接写出 **代表所有请求都通过
- Path=/consumer/**
predicates:
- Query=baz
predicates:
- Query=foo, ba.
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
@EnableEurekaClient //可以省略
@SpringBootApplication
public class ShopGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ShopGatewayApplication.class, args);
}
}
spring:
application:
name: shop-gateway-server
#配置gateway的路由
cloud:
gateway:
routes:
#路由ID\URI\断言
- id: product-api
#uri: http://127.0.0.1:9001 #不使用注册中心
uri: lb://shop-product-server #使用注册中心;格式需满足 lb:// 注册中心获取微服务的名称
predicates:
- Path=/product/**
#eureka注册中心配置
eureka:
client:
service-url:
defaultZone: http://localhost:10000/eureka/
instance:
prefer-ip-address: true #使用IP地址注册
spring:
application:
name: shop-gateway-server
#配置gateway的路由
cloud:
gateway:
routes:
#路由ID\URI\断言
- id: product-api
#uri: http://127.0.0.1:9001 #不使用注册中心
uri: lb://shop-product-server #使用注册中心;格式需满足 lb:// 注册中心获取微服务的名称
predicates:
- Path=/product-service/product/**
filters: #配置过滤器,实现路径重写转发
- RewritePath=/product-service/(?>.*),/$\$(segment) #路径重写过滤器
spring:
application:
name: shop-gateway-server
#配置gateway的路由
cloud:
gateway:
routes:
#路由ID\URI\断言
- id: product-api
#uri: http://127.0.0.1:9001 #不使用注册中心
uri: lb://shop-product-server #使用注册中心;格式需满足 lb:// 注册中心获取微服务的名称
predicates:
- Path=/product-service/product/**
filters: #配置过滤器,实现路径重写转发
- RewritePath=/product-service/(?>.*),/$\$(segment) #路径重写过滤器
#自动从注册中心获取服务 http://localhost:8089/shop-product-server/product/query
discovery:
locator:
enabled: true #开启根据服务名称自动转发
lower-case-service-id: true #微服务名称以小写呈现
GatewayFilter(局部过滤器):应用到单个路由或者一个分组的路由上
routes:
- id: add_request_header_route
uri: http://example.org
filters:
- AddRequestHeader=X-Request-Foo, Bar
routes:
- id: add_request_parameter_route
uri: http://example.org
filters:
- AddRequestParameter=foo, bar
routes:
- id: add_request_header_route
uri: http://example.org
filters:
- AddResponseHeader=X-Response-Foo, Bar
routes:
- id: prefixpath_route
uri: http://example.org
filters:
- PrefixPath=/mypath
routes:
- id: preserve_host_route
uri: http://example.org
filters:
- PreserveHostHeader
routes:
- id: removerequestheader_route
uri: http://example.org
filters:
- RemoveRequestHeader=X-Request-Foo
routes:
- id: removeresponseheader_route
uri: http://example.org
filters:
- RemoveResponseHeader=X-Response-Foo
routes:
- id: rewritepath_route
uri: http://example.org
predicates:
- Path=/foo/**
filters:
- RewritePath=/foo(?>/?.*), $\{segment}
#/42?user=ford&password=omg!what&flag=true 重写后
#/42?user=ford&password=***&flag=true
routes:
- id: rewriteresponseheader_route
uri: http://example.org
filters:
- RewriteResponseHeader=X-Response-Foo, , password=[^&]+, password=***
routes:
- id: save_session
uri: http://example.org
predicates:
- Path=/foo/**
filters:
- SaveSession
#通过网关发出/name/bar/foo请求时
#向nameservice发出的请求将是http://nameservice/foo
routes:
- id: nameRoot
uri: http://nameservice
predicates:
- Path=/name/**
filters:
- StripPrefix=2
routes:
- id: request_size_route
uri: http://localhost:8080/upload
predicates:
- Path=/upload
filters:
- name: RequestSize
args:
maxSize: 5000000
GlobalFilter(全局过滤器):应用到所有的路由上
通过全局过滤器实现权限认证操作
@Component
public class LoginFilter implements GlobalFilter, Ordered {
/**
* @Description 执行过滤器中的业务逻辑
* @Date 2021/6/30 14:58
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("全局认证过滤器");
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(null == token){
System.out.println("没有认证");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); //请求结束
}
System.out.println("认证成功");
return chain.filter(exchange); //继续向下执行
}
/**
* @Description 指定过滤器的执行顺序,返回值越小,执行优先级越高
* @Date 2021/6/30 14:59
*/
@Override
public int getOrder() {
return 0;
}
}
计数器算法
是最简单的一种限流算法,其本质是通过维护一个单位时间内的计数器,每次请求计数器+1,当单位时间内计数器累加到阈值,之后的请求都会被拒绝,直到单位时间结束,将计数器清零。
漏桶算法
可以很好的限制容量池的大小,从而防止流量暴增,漏桶可以看作一个带有常量时间的单服务器队列,如果漏桶溢出,那么数据包会被丢弃,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。
令牌桶算法
令牌桶算法是对漏桶算法的改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时允许一定程度的突发调用,在令牌桶算法中,存在一个桶,用来存放固定数量的令牌,算法中存在一种机制,以一定的速率往桶中放令牌,每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择等待可用的令牌,或者直接拒绝,放令牌这个动作是持续不断的进行,如果统中令牌达到上限,就丢弃令牌。
Spring Cloud Gateway官方提供了基于令牌桶的限流支持,基于其内置的过滤器工厂RequestRateLimiterGatewayFilterFactory实现,在过滤器工厂中是通过Redis和Lua脚本结合的方式进行流量控制。
(1)环境搭建
导入redis的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redis-reactiveartifactId>
dependency>
部署Redis
(2)修改配置文件
spring:
application:
name: shop-gateway-server
#配置Redis
redis:
host: 127.0.0.1
port: 6379
password: 123456
database: 0
#配置gateway的路由
cloud:
gateway:
routes:
#路由ID\URI\断言
- id: product-api
#uri: http://127.0.0.1:9001 #不使用注册中心
uri: lb://shop-product-server #使用注册中心;格式需满足 lb:// 注册中心获取微服务的名称
predicates:
- Path=/product-service/**
filters: #配置过滤器,实现路径重写转发
- RewritePath=/product-service/(?>.*), /$\{segment} #路径重写过滤器
#配置限流
- name: RequestRateLimiter
args:
key-resolver: '#{@myKeyResolver}' #使用SpEL从容器获取对象
redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
redis-rate-limiter.burstCapacity: 3 #令牌桶上限
(3)编写自定义KeyResolver
/**
* @Description 基于请求路径限流
* 基于请求路径: /abc
* 基于请求IP: 127.0.0.1
* 基于参数:
*/
@Configuration
public class MyKeyResolverConfig {
@Bean
public KeyResolver myKeyResolver(){
//实现基于请求路径自定义的KeyResolver
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
}
@Bean
public KeyResolver ipKeyResolver(){
//实现基于请求ip自定义的KeyResolver
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostString());
}
@Bean
public KeyResolver paramKeyResolver(){
//实现基于请求参数自定义的KeyResolver
return exchange -> Mono.just(exchange.getRequest().getQueryParams().toString());
}
}
Sentinel支持对Gateway|Zuul等主流API网关的限流,其提供了两种资源维度的限流。
Sentinel(1.6.0+)提供了网关限流规则和自定义API的实体和管理逻辑
(1)环境搭建
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-spring-cloud-gateway-adapterartifactId>
<version>1.8.1version>
dependency>
@Configuration
public class MySentinelConfig {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public MySentinelConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
return new SentinelGatewayBlockExceptionHandler(viewResolvers,serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter(){
return new SentinelGatewayFilter();
}
@PostConstruct
private void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("product-api") //资源名称,这里为路由router的ID
//路由模式
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID)
.setCount(1) //QPS即每秒钟允许的调用次数
.setIntervalSec(1)); //每隔多少时间统计一次汇总数据
GatewayRuleManager.loadRules(rules);
}
}
@PostConstruct
public void initBlockHandlers(){
//声明自定义异常信息
BlockRequestHandler handler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map<String,String> result = new HashMap<>();
result.put("code","403");
result.put("message","对不起,请稍后再试");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(result));
}
};
GatewayCallbackManager.setBlockHandler(handler); //设置自定义异常信息
}
@PostConstruct
private void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
//配置分组限流
rules.add(new GatewayFlowRule("product-api") //分组名称
.setCount(1) //QPS即每秒钟允许的调用次数
.setIntervalSec(1)); //每隔多少时间统计一次汇总数据
rules.add(new GatewayFlowRule("order-api") //分组名称
.setCount(1) //QPS即每秒钟允许的调用次数
.setIntervalSec(1)); //每隔多少时间统计一次汇总数据
GatewayRuleManager.loadRules(rules);
}
//自定义分组限流,基于API
@PostConstruct
private void initCustomizedApis(){
Set<ApiDefinition> apiDefinitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product-api") //分组名称
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem()
.setPattern("/product-service/product/**")); //配置匹配的连接API(模糊匹配)
}});
ApiDefinition api2 = new ApiDefinition("order-api")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem()
.setPattern("/product-service/order")); //配置匹配的连接API(完全匹配)
}});
apiDefinitions.add(api1);
apiDefinitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);
}
该可用HA是分布式系统架构设计中必须考虑的因素之一,它通常指,通过设计减少系统不能提供服务的时间。众所周知,单点是系统高可用的大敌,单点往往是系统高可用最大的风险和敌人,应该尽量在设计系统的过程中避免单点,方法论上,高可用保证的原则是“集群化”,或者交“冗余”,只有一个单点,挂了服务会受影响,如果有冗余备份,挂了还有其他backup能够顶上。
配置启动多个网关
网关一配置
server:
port: 8001
网关二配置
server:
port: 8002
网关三配置
server:
port: 8003
配置Nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
#网关集群配置
upstream gateway {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
}
server {
listen 80;
server_name localhost;
#127.0.0.1
location / {
proxy_pass http://gateway
}
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}