路由配置
搭建环境
- 导入依赖
<dependency>
<groupId>org.springframewoark.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
- 配置启动类
@SpringBootApplication
public class GatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication.class,args);
}
}
- 编写配置文件
server:
port: 8888 #端口
spring:
application:
name: api-gateway-service #服务名称
注意:启动的时候会报错,那是因为父工程中spring-boot-starter-web包与gateway工程中的包重复了,将gateway中的依赖包去除或者将父工程中的包去除(如果将父工程的包去除需要在用到这个包的所有子工程中重新引入),就可以了
路由规则
断言:路由条件
参考:https://blog.csdn.net/qq_38233650/article/details/98038225
动态路由
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 配置文件
#eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/,http://localhost:8999/eureka/
instance:
prefer-ip-address: true #使用ip地址注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册ip
- 修改配置文件
spring:
application:
name: api-gateway-service #服务名称
#配置springcloudGateway的路由
cloud:
gateway:
routes:
#配置路由:路由id,路由到微服务的uri,断言(判断条件)
- id: product-service
#uri: http://127.0.0.1:9001
uri: lb://product-service #lb:// 根据微服务名称从注册中心拉取服务请求路径
predicates:
#product不能随便写,一定要能接收到的接口,如:有一个服务提供者的uri为:
#http://127.0.0.1:9001/product/getProduct/1,下面- Path的参数就得写,/product/**
- Path=/product/**
其实只修改了uri
- 如果喜欢http://127.0.0.1:8888/product-service/product/getProduct/1的形式,可以这样配置
predicates:
- Path=/product-service/**
filters: #配置路由过滤器
- RewritePath=/product-service/(?<segment>.*),/$\{segment} #路径重写过滤器,在yml中 $ 写为 $\
- 配置自动的根据微服务名称进行路由转发
spring:
cloud:
gateway:
discovery:
locator:
enabled: true #开启根据服务名称自动转发
lower-case-service-id: true #微服务名称以小写形式呈现
访问的时候就可以:http://127.0.0.1:8888/product-service/product/getProduct/1
过滤器
过滤器的生命周期
Spring Cloud Gateway的Fliter的声明周期不像Zuul的那么丰富,它只有两个:“pre"和"post”(和zuul一样)
过滤器类型
- GatewayFilter:应用到单个路由或者一个分组的路由上。
参考:https://blog.csdn.net/beishuibo1517/article/details/100963839 - GlobalFilter:应用到所有的路由上。
自定义全局过滤器:
/**
* 自定义全局过滤器
* 实现 globalfilter,ordered接口
* */
@Component
public class LoginFilter implements GlobalFilter,Ordered {
/**
* 执行过滤器中的业务逻辑
* */
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行了自定义的全局过滤器");
return chain.filter(exchange);//继续向下执行
}
/**
* 指定过滤器的执行顺序,返回值越小,执行优先级越高
* */
@Override
public int getOrder() {
return 0;
}
}
统一鉴权
/**
* 执行过滤器中的业务逻辑
* 对请求桉树中的access-token进行判断
* 如果存在此参数:代表已经认证成功
* 如果不存在此参数:代表认证失败
* ServerWebExchange:相当于请求和响应上下文(zuul中的RequestContext)
* */
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求参数access-token
String token = exchange.getRequest().getQueryParams().getFirst("access-token");
//2.判断是否存在
if(token == null) {
//3.如果不存在:认证失败
System.out.println("没有登录");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();//请求结束
}
//4.如果存在:继续执行
return chain.filter(exchange);//继续向下执行
}
网关限流
计数器算法
其实就是维护一个单位时间内的访问计数器,如果访问量超过阈值,则限流
缺点:假如我时按照一个小时间隔算的,如果我在10分钟的时候就达到了上限,那么剩下的时间将会全部访问都被拒绝
漏桶算法
队列大小,假如队列设置的容量为100,当第101个到来时,就会直接拒绝
令牌桶算法
令牌桶是对漏桶算法的改进,增加了允许一定程度的突发调用
原理:每隔一段时间向桶中存放一个令牌,当请求过来的时候,必须拿到一个令牌才可以进行处理,否则直接拒绝或者等待,另外桶设置一个最大容量
限流方法
基于filter的限流
SpringCloudGateway官方提供了基于令牌桶的限流支持。基于其内置的过滤工厂RequestRateLimiterGatewayFilterFactory实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。
(1) 环境搭建
- 安装redis(略)
- 导入redis的依赖:基于reactive的redis依赖
<!-- 监控依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
(2)修改网关中的application.yml
spring:
redis:
host: localhost
pool: 6379
database: 0
cloud:
gateway:
routes:
filters:
- name: RequestRateLimiter
args:
#使用SpEL从spring容器中获取对象
key-resolver: '#{@pathKeyResolver}'
#令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
#令牌桶上限
redis-rate-limiter.burstCapacity: 3
(3)配置redis中key的解析器KeySesolver
接下来我们就要在spring中注入一个 pathKeyResolver 对象
@Configuration
public class GatewayPathKeyResolver {
/**
* 编写基于请求路径的限流规则
* //基于请求路径
* //基于请求ip
* //基于参数
* */
@Bean
public KeyResolver pathKeyResolver(){
return exchange -> Mono.just(
//基于请求路径
//exchange.getRequest().getPath().toString()
//基于参数
exchange.getRequest().getQueryParams().getFirst("userId")
//基于ip
//exchange.getRequest().getHeaders().getFirst("X-Forwarded-For")
);
}
}
基于Sentinel的限流
Sentinel1.6.0引入了Sentinel API Gateway Adapter Common模块,此模块中包含网关限流的规则和自定义API的实现体和管理逻辑
- GatewayFlowRule:网关限流规则,针对API Gateway的场景定制的限流规则,可以针对不同route或者自定义的API分组进行限流,支持针对请求中的参数、Header、来源IP等进行定制化的限流。
- ApiDefinition:用户自定义的API定义分组,可以看座是一些URL匹配组合。比如我们可以定义一个API叫my_api,请求path模式为/foo/**和/baz/**的都归到my_api这个API分组下面。限流的时候可以针对这个自定义的API分组维度进行限流。
(1)环境搭建
导入Sentinel的响应依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
(2)编写配置类
/**
* sentinel的限流配置
* */
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer){
this.serverCodecConfigurer = serverCodecConfigurer;
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
}
/**
* 配置限流异常处理器:SentinelGatewayBlockExceptionHandler
* */
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
return new SentinelGatewayBlockExceptionHandler(viewResolvers,serverCodecConfigurer);
}
/**
* 配置限流过滤器
* */
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter(){
return new SentinelGatewayFilter();
}
/**
* 配置初始化的限流参数
* 用于指定资源的限流规则
* 1.资源名称(路由id)
* 2.配置统计时间
* 3.配置限流的阈值
* */
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
// rules.add(
// new GatewayFlowRule("product-service")//资源名称
// .setCount(1)//限流阈值
// .setIntervalSec(1L)//统计时间窗口,单位是秒,默认为1秒
// );
rules.add(new GatewayFlowRule("product_api")//分组名称
.setCount(1)//限流阈值
.setIntervalSec(1L)//统计时间窗口,单位是秒,默认为1秒
);
GatewayRuleManager.loadRules(rules);
}
/**
* 限流降级
* */
@PostConstruct
public void initBlockHandlers(){
GatewayCallbackManager.setBlockHandler((serverWebExchange,throwable) ->{
Map map = new HashMap();
map.put("code",001);
map.put("message","对不起,接口限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(map));
});
}
/**
* 自定义API限流分组
* 1.定义分组
* 2.对小组配置限流规则
* */
@PostConstruct
public void initCustomizedApis(){
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product_api")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
//以/product-service/product/开头的所有url
add(new ApiPathPredicateItem().setPattern("/product-service/product/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("comsumer_api")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
//完全匹配/comsumer-service/demo的所有url
add(new ApiPathPredicateItem().setPattern("/comsumer-service/demo")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
这些配置也可以直接在sentinel后台管理界面设置
网关的高可用
结构:
所以,我们这里需要的工具是一个ngnix,两到三台gateway
- 启动两台gateway
我这里使用上面的gateway,然后复制以下,修改一下端口 - 下载,配置ngnix
(1)下载:http://nginx.org/en/download.html
(2)配置,找到解压目录下的/conf/nginx.conf
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
#集群配置
upstream gateway{
server 127.0.0.1:8888;
server 127.0.0.1:8889;
}
server {
listen 80;
server_name localhost;
#127.0.0.1
location / {
proxy_pass http://gateway;
}
}
}
访问:http://localhost/product-service/product/getProduct/2
大功告成