Gateway:网关
主要内容
1.API网关
2.Spring Cloud Gateway介绍
3.Gateway入门案例
4.谓词
5.Filter
6.使用Gateway实现服务降级
一.API网关
1.什么是API网关
API网关作用就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的接口.同时也乐意在网关中提供额外的功能
总结:网关就是所有项目的一个统一接口
2.网关组成
网关=路由转发+过滤器(编写额外功能)
2.1路由转发
接收外界请求,通过网关的路由转发,转发到后端的服务上
如果只有这一个功能看起来和之前学习的Nginx反向代理服务器很像,外界访问nginx,由nginx做负载均衡,后把请求转发到对应服务器上
2.2过滤器
网关非常重要的功能就是过滤器
过滤器中默认提供了2s内置工鞥呢还自持额外的自定义功能
对于我们来说比较常用的功能有网关的容错,限流以及请求及相应的额外处理
3.Spring Cloud中提供的网关解决方案
3.1Spring Cloud Netflix Zuul
属于Spring Cloud Netflix下一个组件,具有灵活,简单的特点.在早期Spring Cloud中使用的比较多.
其版本更新都依赖与Netflix Zuul
3.2Spring Cloud Gateway
由Spring自己推出的网关产品,完全依赖Spring自家产品.符合Spring战略意义,器更新版本等都由Spring自己把控
目前很多项目中都是使用Gateway替代Zuul
在本套课程中讲解的也是Gateway
二.Spring Cloud Gateway介绍
1.简介
Spring Cloud Gateway是Spring Cloud的二级子项目,提供了微服务网关功能,包含:权限安全,监控/指标等功能
2.名词解释
在学习Gateway时里面有一些名词需要提前了解,这对于后面的学习是很有帮助的
2.1Route
Route中文称为路由,Gateway里面的Route是主要学习内容,一个Gateway项目可以包含多个Route
一个路由包含ID,URI,Predicate集合,Filter集合
在Route中ID是自定义的,URL就是一个地址.剩下的Predicate和Filter学习明白了,Route就学习清楚了.
2.2Predicate
中文:谓词
谓词是学习Gateway比较重要的一点,简单点理解谓词就是一些附加条件和内容
2.3Filter
所有生效得Filter都是Gateway的实例.在Gateway运行过程中Fileter负责在代理服务"之前"或"之后"去做一些事情
2.4流程
2.4.1流程文字解释
网关客户端访问Gateway网关,Gateway中Handler Mapping对请求URL进行处理.处理完成后交换Web Handler,Web Handler会被Filter进行过滤.Filter中前半部分代码是处理请求的代码.处理完成后调用真实被代理的服务.被代理服务响应结果,结果会被Filter中后半部分代码进行操作,操作完成后把结果返回给Web Handler,在返回给Handler Mapping,最终响应给客户端
三.Gateway入门案例
1.准备内容
1.1Eureka Server
因为Gateway依赖Eureka,需要Eureka中获取真实代理项目地址后,在进行访问.所以需要准备一个Eureka Server(实例中是非集群Eureka),端口设置为8761
2.新建项目DemoOne
一个普通的Eureka Client项目,重点关注应用程序名和控制器即可,其他代码省略
2.1配置文件
spring:
application:
name:demo-one
2.2控制器
@Controller
public class DemoController{
@RequestMapping("/one")
@ResponseBody
public String demo(){
System.out.println("执行one控制器");
return "one";
}
@RequestMapping("/demo/one")
@ResponseBody
public String demo2(){
System.out.println("执行demo-one控制器");
return "demo-one";
}
}
3.新建项目GatewayDemo
3.1编写pom.xml
3.2新建配置文件
谓词中Path一般会给每个项目前面起一个无意义的别名
如Path=/abc/**当看到abc是知道路由转发到项目abc,后面的内容才是真实要访问abc的内容
如Path=/jqk/**表示需要转发到jql项目
要明白:uri中第一部分就是为了防止各个项目中出现重名路径导致路由转发失败
server:
port:9000
spring:
application:
name:gateway-demo
cloud:
gateway:
discovery:
locator:
enabled: true # 开启当前项目服务注册于发现功能
lower-case-service-id:true # 把服务名称转换为小写,Eureka中默认都是大写
- id:demo # 自定义唯一标识,只要不重复即可
url:lb://EUREKA-CLIENT # Lb:LoadBalance EUREKA-CLIENT代理项目的名
predicates:Path=/demo/** # 路径规则
filters: StripPrefix=1 # 转发后忽略第一层
3.新建启动器
新建com.bjsxt.GatewayApplication
@SpringBootApplication
public class GatewayApplication(){
public static void main(String[] args){
SpringApplication.run(GatewayApplication.class,args);
}
}
4.访问测试
访问GatewayDemo项目
http://localhost:9000/demo/one
页面会显示
通过喝过可以看出,用户虽然访问的GatewayDemo网关项目,但是最后真是访问的是DemoOne项目中/one控制器
5.修改配置参数
修改GatewayDemo项目中配置文件的路由参数,把StripPrefix的值由1改成0后,重启项目
filters:StripPrefix=0
再次访问http://localhost:9000/demo/one会发现
四.谓词
谓词:当满足条件在进行路由转发
在Spring Cloud Gateway中谓词实现GatewayPredicate接口.其中类名符合:XXXRoutepredicateFactory,其中XXX就是在配置文件中谓词名称.在上面实例中Path=/demo/**实际上使用的就是PathRoutePredicateFactory
所有的谓词都设置在predicates属性中,当设置多个谓词时区逻辑与条件,且一个谓词只能设置一组条件,如果需要有多个条件,添加多个相同谓词
1.Query
1.1.设置必须包含的参数名
下面两种写法等效.都表示路径满足/demo/**同时包含参数abc
Path和Query是谓词.abc是请求参数名称
在浏览器中输入:http://localhost:9000/demo/one?abc=jdk
predicates:Path=/demo/**,Query=abc
predicates:
-Path=/demo/**
- Query=abc
1.2设置参数的值
abc请求参数名称.jdk是abc的值,是一个正则表达式.在正则表达式中点(.)表示匹配任意一个字符.所以当请求参数abc=jqka或abc=jqkw能满足谓词条件
在谓词中赋值使用逗号(,)赋值因为Qurey后面已经有等号,在值内容值在出现等号语法说不过去了
predicates:
- Path=/demo/**
- Query=abc,jqp.
2.Header
表示请求头中必须包含的内容
注意:
参数名和参数值主键依然使用逗号
参数值要使用正则表达式
如果Header只有一个值表示请求头中必须包含的参数.如果有两个值,第一个表示请求头必须包含的参数名,第二个表示请求头参数的对应值
predicates:
- Path=/demo/**
- Query=abc,jqk+
- Header=Connection,keep-alive
如果设定请求头中需要包含多个参数设置.设置多个Header.在此处演示多个相同谓词配置,其他谓词中就不在强调可以配置多个谓词
predicates:
- Path=/demo/**
- Query=abc,jqk+
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
3.Method
Method表示请求方式.支持多个值,使用逗号分隔,多个值之间为or条件
predicates:
- Path=/demo/**
- Method=GET,POST
4.RemoteAddr
允许访问的客户端地址
要注意使用127.0.0.1而不要使用localhost
predicates:
- Path=/demo/**
- RemoteAddr=127.0.0.1
5.Host
匹配请求参数中Host参数的值.满足Ant模式(之前在Spring Security中学习过)可以使用
?匹配一个字符
#匹配0个或多个字符
##匹配0个或多个目录
predicates:
- Path=/demo/**
- Host=127.0.0.1:9000
6.Cookie
要求请求中包含指定Cookie名和满足特定正则要求的值
Cookie必须有两个值,第一个Cookie包含的参数名,第二个表示参数对应的值,正则表达式.不支持一个参数写法
predicates:
- Path=/demo/**
- Cookie=age,.*
7.Before
在指定时间点之前
predicates:
- Path=/demo/**
- Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
8.After
在指定时间之前
predicates:
- Path=/demo/**
- After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
9.Between
请求时必须在设定的时间范围内,才进行路由转发
注意:时间的格式
predicates:
- Path=/demo/**
- Between=2020-01-31T18:00:00.000+08:00[Asia/Shanghai],2020-02-01T00:00:00.000+08:00[Asia/Shanghai]
10.Weight
负载均衡中权重.同一个组中URI进行负载均衡
语法:Weight=组名,负载均衡权重
在Eureka中注册两个服务,这个服务(项目)是相同的,应用程序名分别叫做demo-one和demo-two
Gateway在路由匹配时demo-one将占20%,demo-two将占80%
routes:
- id:xiaoming
uri: lb://demo-one
prdicates:
- Path=/demo/**
- Weight=group,2
filters: StripPrefix=1
- id: xiaoming2
uri: lb://demo-two
predicates:
- Path=/demo/**
- Weight=group,8
filters: StripPrefix=1
五.Filter
Filter作用:在路由转发到代理服务器之前和代理服务返回结果之后额外做的事情.Filter执行了说明谓词条件通过了
在Spring Cloud Gateway的路由中Filter分为:
内置Filter:都是GatewayFilter实现类
自定义GlobalFilter
1.内置Filter
之前使用StripPrefix就是内置Filter,所有内置Filter都实现GatewayFilter接口.使用时filters属性中过滤器名为XXXGatewayFilterFactory的类对应的名称为XXX.
当过滤器包含参数
下面所有Filter基本都是支持单个值,如果需要设置多个,需要写多个对应Filter
1.1.AddRequestHeader
添加请求头参数,参数和值之间使用逗号分隔
filters:
- StripPrefix=1
- AddRequestHeader=MyHeader,jqk
1.2AddRequestParameter
添加请求表单参数,多个参数需要有多个过滤器
filters:
- StripPrefix=1
- AddRequestParameter=name,bjsxt
- AddRequestParameter=age,123
1.3AddResponseHeader
添加响应头
1.4DedupeResponseHeader
对指定响应头去重复
语法:DedupeResponseHeader=响应头参数 响应头参数strategy
可选参数strategy可取值:
RETAIN_FIRST默认值,保留第一个
RETAIN_LAST保留最后一个
RETAIN_UNIQUE保留唯一的,出现重复的属性值,会保留一个.例如有两个My:bbb的属性,最后会只留一个
- DedupeResponseHeader=My Content-Type,RETAIN_UNIQUE
1.5CircuitBreaker
实现熔断时使用,支持CircuitBreaker和Hystrix两种
1.6FallbackHeaders
可以添加降级时的异常信息
1.7PrefixPath
匹配所有前缀满足条件的URI
1.8RequestRateLimiter
限流过滤器
1.9RedirectTo
重定向.有两个参数,status和url.其中status应该300系列重定向状态码
1.10RemoveRequestHeader
删除请求头参数
1.11RemoveResponseHeader
删除响应头参数
1.13RewritePath
重写请求路径
1.14RewriteResponseHeader
重写响应头参数
1.15SaveSession
如果项目中使用SpringSecurity和SpringSession整合时,此属性特别重要
1.16SecureHeaders
具有权限验证时,建议的头信息内容
1.17SetPath
功能和StripPrefix有点类似.语法更贴近restful
当前请求路径为/red/blue时会将/blue发送给下游
spring:
cloud:
gateway:
routes:
- id:setpath_route
uri:https://example.org
predicates:
- Path=/red/{segment}
filters:
- SetPath=/{segment}
1.18SetRequestHeader
替换请求参数头.不是添加
1.19SetResponseheader
替换响应头参数
1.20SetStatus
设置响应状态码
1.21StripPrefix
跳过路由uri中前几段后发送给下游
1.22Retry
设置重试次数
1.23RequestSize
请求最大大小.包含maxSize参数,可以有单位"KB"或"MB"默认为"B"
1.24ModifyRequestBody
修改请求体内容
1.25ModifyResponseBody
修改响应体
六.使用Gateway实现限流
可以利用Gateway中RequestRateLimiter实现限流
1.常见的限流算法
1.1计数器算法
以QPS(每秒查询率Queries-per-second)为100举例
从第一个请求开始计时.每个请求让计数器加一.当达到100以后,其他的请求都拒绝.如果1秒钟内前200ms请求数量已经达到了100,后面800ms中500次请求都被拒绝了,这种情况称为"突刺现象"
1.2漏桶算法
漏桶算法可以解决突刺现象
和生活中漏桶一样,有一个水桶,下面有一个"露眼"往出漏水,不管桶里有多少水,漏水的速率都是一样的.但是既然是一个桶,桶里装的水都是有上限的.当到达了上限新进来的首就装不了(主要出现在突然进来大量水的情况)
1.3令牌桶算法
令牌桶算法可以说是对漏桶算法的一种改进
在桶中放令牌,请求获取令牌后才能继续执行.如果桶中没有令牌,请求可以选择进行等待或者直接拒绝
由于桶中令牌是按照一定速率放置的,所以可以一定程度解决突发访问.如果桶中令牌最多有100个,qps最大为100
2.Gateway中限流
RequestRateLimiter是基于Redis和Lua脚本实现的令牌桶算法
2.1添加依赖
2.2编写配置文件
新建配置文件application.yml
参数说明:
key-resolver:使用SqEL获取Spring容器中实例
replenishRate:每秒放入令牌的数量
burstCapacity:容量
server:
port:9000
spring:
application:
name:gateway-demo
cloud:
gateway:
discovery:
locator:
enabled: true # 开启当前项目服务注册与发现功能
lower-case-service-id:true # 把服务名转换为小写,Eureka中默认都是大写
routes;
- id: demo # 自定义唯一标识,只要不重复即可
uri:lb://application-client # Lb:LoadBalance EUREKA-CLIENT 代理项目的名
predicates:Path=/abc # 路径规则
filters: # 转发后忽略第一层
- name: RequestRateLimiter
args:
key-resolver: '#{@abcComponet}'
redis-rate-limiter.replenishRate:1
redis-rate-limiter.burstCapacity:3
redis:
host:192.168.232.132
2.3新建实例
新建com.bsjxt.component.AbcComponet
just()方法参数表示根据什么进行限流.实例中是根据主机名进行限流.参数值影响Redis中key中红色部分
@Component
public class AbcComponet implements KeyResolver{
@Override
public Mono
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}
2.4新建启动类
@SpringBootApplication
public class AbcApplication{
public static void main(String[] args){
SpringApplication.run(AbcApplication.class,args);
}
}
2.5使用JMeter测试
会发现3个请求时成功的,后面很多失败的,1秒后的请求时成功了
七.使用Gateway实现服务降级
Spring Cloud Gateway可以利用Hystrix实现服务降级等功能
当Gateway进行路由转发时,如果发现下游服务连接超时允许进行服务降级
实现原理:当连接超时时,使用Gateway自己的一个降级接口返回托底数据,保证程序继续运行
1.添加依赖
在GatewayDemo中额外添加
2.新建控制器
新建com.bjsxt.controller.MyFailbackController添加降级控制器方法,方法中根据需求编写自己的逻辑
@Controller
public class MyFallbackController{
@RequestMapping("/myfallback")
@ResponseBody
public String fallback(){
return "托底数据";
}
}
3.修改配置文件
在配置文件中filters属性添加
其中args.name取值是任意的,最终会被设置为Hystrix的commandKey.但是不能省略,省略会导致prg.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory.Config的Setter为null,因为没有设置Hystrix的commandKey等内容时就没有执行Setter的构造方法
routes:
- id:demo
uri:lb://EUREKA-CLIENT
predicates:
- Path=/demo/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/myfallback
八.GlobalFilter
全局过滤器不需要工厂,也不需要配置,直接对所有的路由都生效
可以使用GlobalFilter实现统一的权限验证,日志记录等希望对所有代理的项目都生效的内容都可以配置在全局过滤器中
且在项目中可以配置多个GlobalFilter的实现类.都可以自动执行
@Component
public class MyGlobalFilter implements GlobalFilter{
private Logger = LoggerFactory.getLogger(MyGlobalFilter.class);
@Override
public Mono
logger.info("执行全局过滤器-logger");
return chain.filter(exchange);
}
}
九.自定义FilterFactory
可以定义针对于Router的Filter
注意:
1.类名必须叫做XXXGatewayFilterFactory注入到Spring容器后使用时的名称就叫做XXX.
2.类必须继承AbstractGatewayFilterFactory
3.所有需要传递进来的参数都配置到当前类的内部类Config中
1.新建类
@Component
public class MyRouteGatewayFilterFactory extends AbstractGatewayFilterFactory
public MyRouteGatewayFilterFactory(){
super(MyRouteGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(Config config){
return new GatewayFilter(){
@Override
public Mono
System.out.println("在这个位置鞋垫东西传递过来的name:+"+config.getName()+",age:"+config.getAge());
return chain.filter(exchange);
}
@Override
public String toString(){
return GatewayToStringStyler.filterToStringCreator(MyRouteGatewayFilterFactory.this).append("name",config.getName()).toString();
}
};
}
@Override
public List
return Arrays.asList("name");
}
public static class Config(){
private String name;
private int age;
public int getAge(){
return age;
}
public void setAge(int age){
this.age=age;
}
public Config(){}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
}
}
2.在配置文件中配置
filters.name:过滤器的名称
filters.args.name为MyRouteGatewayFilterFactory.Config配置的属性
routes:
- id: bjsxt
uri: lb://JQK
predicates:
- Path=/project1/**
filters:
- StripPrefix=1
- name: MyRoute
args:
name: hello
age: 12