API网关作用就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的接口。同时也可以在网关中提供额外的功能
总结:网关就是所有项目的一个统一入口
请求响应流程:
nginx --> 前端项目集群 --> 网关 --> 后端项目集群
网关 = 路由转发+过滤器(编写额外功能)
接收外界请求,通过网关的路由转发,转发到后端的服务上
如果只有这个一个功能看起来和之前学习的Nginx方向代理服务器很像,外界访问nginx,由nginx做负载均衡,后把请求转发到对应服务器上
网关非常重要的功能就是过滤器
过滤器中默认提供了25个内置功能,还支持额外的自定义功能
对于我们来说比较常用的功能包括网关的容错、限流以及请求及相应的额外处理
属于Spring Cloud Netflix下一个组件,具有灵活、简单的特点。在早期Spring Cloud中使用的比较多
其版本更新都依赖Netflix Zuul
由Spring自己推出的网关产品,完全依赖Spring自家产品。符合Spring战略意义,其更新版本等都由Spring自己把控
Spring Cloud Gateway介绍Spring Cloud Gateway是Spring Cloud的二级子项目,提供了微服务网关功能,包含:权限安全、监控、指标等功能
在学习Gateway时里面有一些名词需要提前了解
路由,一个Gateway项目可以包含多个路由
一个路由包含ID、URI、Predicate集合、Filter集合
在Route中ID是自定义的,URI就是一个地址,剩下的Predicate和Filter
谓词,谓词是学习Gateway比较重要的一点,简单点理解谓词就是一些附加条件和内容,其实就是路由规则,和简单的校验逻辑
所有生效的Filter都是GatewayFilter的实例,在Gateway运行过程中Filter负责在代理服务之前或之后去做一些事情
Spring Cloud Gateway入门案例新建微服务,首先引入依赖
<dependency><groupId>org.springframework.cloudgroupId><artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
添加配置文件&#xff0c;gateway的配置都是在配置文件中实现的
server:port: 9999
spring:application:name: my-spring-cloud-gatewaycloud: #配置Spring Cloud相关属性gateway: #配置Spring Cloud Gateway相关属性discovery: #配置网关发现机制locator: #配置处理机制enable: true #开启网关自动映射处理逻辑&#xff0c;#只要请求地址符合规则&#xff1a;http://gatewayIP:gatewayPort/微服务名称/微服务请求地址#网关自动映射&#xff0c;把请求转发到http://微服务名称/微服务请求地址#如&#xff1a;有微服务&#xff0c;命名是ribbon-app-service#请求地址是&#xff1a;http://localhost:9999/ribbon-app-service/getArgs?name&#61;admin&age&#61;20#自动转发到&#xff1a;http://ribbon-app-service/getArgs?name&#61;admin&age&#61;20#商业开发中&#xff0c;enable一般不设置&#xff0c;使用默认值false。避免不必要的自动转发规则lower-case-service-id: true #开启服务名称小写转换。Eureka对服务名管理默认全是大写routes: #配置网关中的一个完整路由&#xff0c;包括命名&#xff0c;地址&#xff0c;谓词集合&#xff08;规则&#xff09;&#xff0c;过滤器集合- id: first #路由定义的命名&#xff0c;唯一即可。命名规则符合Java中的变量符合命名规则# lb - 代表loadbalanceuri: lb://ribbon-app-service #当前路由定义对应的微服务转发地址#谓词&#xff0c;命名是有规则的。是GatewayPredicate接口实现的命名前缀&#xff0c;XxxRoutePredicataFactorypredicates: #配置谓词集合- Path&#61;/api/** #定义一个谓词。格式&#xff1a;谓词名字&#61;参数 或者 name:名字 args:参数#过滤器命名&#xff0c;有规则。GatewayFilterFactory接口实现的命名前缀filters: #配置过滤器集合#过滤转发地址前缀&#xff0c;过滤一节#如&#xff1a;请求地址&#xff1a;http://localhost:9999/api/getArgs?name&#61;admin&age&#61;20#对应的谓词&#xff0c;规则是/api&#xff0c;符合#对应的uri是 lb:ribbon-app-service 转换成http://ribbon-app-service且包含负载均衡#转发地址是&#xff1a;http://ribbon-app-service/getArgs?name&#61;admin&age&#61;20#过滤器是 过滤转发地址前缀&#xff0c;过滤一节&#xff0c;即删除/api- StripPrefix&#61;1 #定义一个过滤器。格式&#xff1a;过滤器名字&#61;参数 或者 name:名字 args:参数- id:uri:predicates:- name: Pathargs:- patterns&#61;/api/**,/a/**filters:
eureka:client:service-url:defaultZone: http://localhost:8761/eureka/
谓词&#xff08;规则&#xff09;
# 检查请求头中是否有指定数据&#xff0c;key是my&#xff0c;值是bjsxt&#xff08;正则表达式&#xff09;
routes:- id: firsturi: lb://ribbon-api-servicepredicates:- Path&#61;/api/**,/a/**,/b/**name: Headerargs: #map类型&#xff0c;键值对header: myregexp: bjsxt.* #正则表达式filters:# 校验请求中的参数&#xff0c;key是name&#xff0c;值是一个正则表达式
routes:- id: firsturi: lb://ribbon-api-servicepredicates:- Query&#61;name,admin.* #谓词判断请求参数&#xff0c;值判断请求头传递的参数&#xff0c;请求体参数不判断filters:# 检查请求类型
routes:- id: firsturi: lb://ribbon-api-servicepredicates:- Method&#61;GET,POSTfilters:# 检查客户端ip地址是否符合要求
routes:- id: firsturi: lb://ribbon-api-servicepredicates:- sources&#61;192.168.0.1,192.168.0.2,192.168.0.*filters:# 检查请求中指定的COOKIE值
routes:- id: firsturi: lb://ribbon-api-servicepredicates:- name&#61;COOKIENameregexp&#61;aa*filters:# 检查请求在什么时间范围内
routes:- id: firsturi: lb://ribbon-api-servicepredicates:- Path&#61;/api/**- Between&#61;2020-01-31T18:00:00.000&#43;08:00[Asia/Shanghai],2020-01-31T20:00:00.000&#43;08:00[Asia/Shanghai]filters:# 检查请求头
routes:- id: firsturi: lb://ribbon-api-servicepredicates:- Path&#61;/api/**- pattern&#61;*filters:# 权重实现负载均衡&#xff08;通常不会使用&#xff0c;只有在多版本服务发布的时候偶尔使用&#xff0c;新版本权重2&#xff0c;接受少量的请求&#xff0c;如果没有问题&#xff0c;就可以替换旧版本&#xff09;
routes:- id: firsturi: lb://ribbon-api-service-1predicates:- Path&#61;/api/**- Weight&#61;group,2 #group表示分组名称&#xff0c;分组名称一样&#xff0c;表示同一组filters: SpripPrefix&#61;1- id: twouri: lb://ribbon-api-service-2predicates:- Path&#61;/api/**- Weight&#61;group,8 #按照权重比例分配请求filters: SpripPrefix&#61;1
Filter
Filter作用&#xff1a;在路由转发到代理服务器之前和代理服务器返回结果之后额外做的事情。
Filter执行了&#xff0c;那就说明谓词条件通过了
在Spring Cloud Gateway的路由中Filter分为&#xff1a;
内置Filter&#xff0c;都是GatewayFilter实现类
自定义GlobalFilter
# 添加一个请求头信息
routes:- id: firsturi: lb://ribbon-api-servicepredicates:- Path&#61;/api/**- pattern&#61;*filters:- AddRequestHeader&#61;a,b# 完整写法
routes:- id: firsturi: lb://ribbon-api-servicepredicates:filters:- name: AddRequestHeaderargs:name: newHeadervalue: newValue查看源码&#xff0c;XXXGatewayFilterFactory&#xff0c;这些都是过滤器&#xff0c;对应配置类就可以配置属性参数常用的&#xff1a;
AddRequestHeader
AddResponseHander
DedupeResponseHander
StripPrefix
使用Gateway实现限流
可以利用Gateway中RequestRateLimiter实现限流
以QPS每秒查询率为100举例子
从第一个请求开始计时&#xff0c;每个请求让计数器加一&#xff0c;到达到100以后&#xff0c;其他的请求都拒绝
如果1秒内前200毫秒请求数量已经到达了100&#xff0c;后面800毫秒中500次请求都被拒绝了&#xff0c;这种情况称为‘突刺现象’
漏桶算法可以解决突刺现象
和生活中漏桶一样&#xff0c;有一个水桶&#xff0c;下面有一个漏眼往出漏水&#xff0c;不管桶里有多少水&#xff0c;漏水的速率都是一样的。但是既然是一个桶&#xff0c;桶里装的谁都是有上限的。当达到了上限新进来的水就装不了&#xff08;主要出现在突然倒进来大量水的情况&#xff09;
令牌桶算法可以说是对漏桶算法的一种改进
在桶中放令牌&#xff0c;请求获取令牌后才能继续执&#xff0c;如果桶中没有令牌&#xff0c;请求可以选择进行等待或者直接拒绝
由于桶中令牌是按照一定速率放置的&#xff0c;所以可以一定程度解决突发访问&#xff0c;如果桶中令牌最多100个&#xff0c;qps最大为100
RequestRateLimiter 基于Redis和lua脚本实现令牌桶算法
# 完整写法
routes:- id: firsturi: lb://ribbon-api-servicepredicates:filters:- name: RequestRateLimiterargs:keyResolver: &#39;#{&#64;myKeyResolver}&#39; #使用SpringEL表达式&#xff0c;从Spring容器中找对象&#xff0c;并赋值 ‘#{&#64;beanName}’redis-rate-limiter.replenishRate: 1 #每秒产生的令牌数量redis-rate-limiter.burstCapacity: 5 #桶的容量上限
&#64;Component
public class MyKeyResolver implements KeyResolver{&#64;Overridepublic Mono<String> resolve(ServerWebExchange exchange){String remoteAddr &#61; exchange.getRequest().getRempteAddress().getAddress.getHostAddress();return Mono.just(remoteAddr);}
}
gateway可以利用Hystrix实现服务降级等功能
当gateway进行路由转发时&#xff0c;如果发现下游服务连接超时允许进行服务降级
实现原理&#xff1a;当连接超时时&#xff0c;使用gateway自己的一个降级接口返回托底数据&#xff0c;保证程序继续运行
# 完整写法
routes:- id: firsturi: lb://ribbon-api-servicepredicates:filters:- name: Hystrixargs:name: fallbackcmd #名字分组fallbackUri: forward:/downgrade #远程服务错误的时候&#xff0c;Gateway工程中的哪一个控制器逻辑&#xff0c;返回结果
&#64;RestController
public class DowngradeController{&#64;RequestMapping(value&#61;"/downgrade",produces&#61;"text/html;charset&#61;UTF-8")public String downgrade(){return "
}
全局过滤器不需要工厂&#xff0c;也不需要配置&#xff0c;直接对所有的路由都生效
可以使用GlobalFilter实现统一的权限验证、日志记录等希望对所有代理的项目都生效的内容都可以配置在全局过滤器中
且在项目中可以配置多个GlobalFilter的实现类&#xff0c;都可以自动执行
&#64;Component
public class MyGlobalFilter implements GlobalFilter{&#64;Overridepublic Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){// 前置过滤System.out.prinln("全局过滤器执行&#xff0c;当前时刻是&#xff1a;"&#43;new Date());// 执行后续逻辑Mono<Void> result &#61; chain.filter(exchange);return result;}
}
&#64;Component
public class RequestPathGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestPathGatewayFilterFactory.Config>{// 当前过滤器需要使用的配置内容class Config{private String name;private String path;// get set 方法}// 过滤逻辑&#64;Overridepublic GatewayFilter apply(Config config){return new GatewayFilter(){&#64;Overridepublic Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){// 执行后续逻辑Mono<Void> result &#61; chain.filter(exchange);return result;}}}// 如果可以简化配置方案&#xff0c;当前方法返回简化配置参数// RequestPath&#61;nameValue,pathValue&#64;Overridepublic List<String> showtcutFieldOrder(){return Arrays.asList("name","path");}
}