有一些问题:不同的微服务一般会有不同的网络地址,客户端在访问这些微服务时必须记住几十甚至几百个地址,这对于客户端方来说太复杂也难以维护。
如果让客户端直接与各个微服务通讯,可能会有很多问题:
因此,我们需要一个微服务网关,介于客户端与服务器之间的中间层,所有的外部请求都会先经过微服务网关。客户端只需要与网关交互,只知道一个网关地址即可,这样简化了开发还有以下优点:
API网关是一个服务器,是系统对外的唯一入口
API网关封装了系统内部架构,为每个客户端提供一个统一定制的API(应用编程接口)
API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能
通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务
Kong
Zuul
Traefik
Spring Cloud Gateway
Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的核心是一系列的过滤器。Spring Cloud对Zuul进行了整合和增强
Zuul的最主要作用:路由分配,过滤器(身份认证,铭感词处理……)
当然,这个技术,跟Eureka,Hystrix-DashBoard一样又是项目经理的东西
配一次,一般就不需要再做多次的修正
网关程序,一般都是一个独立的微服务程序
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- 导入Eureka的客户端依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
@SpringCloudApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class,args);
}
}
#配置端口
server:
port: 10000
#配置项目名称
spring:
application:
name: zuul
#配置eureka注册路径
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka
#配置路由
zuul:
routes:
#路由ID/名称(可以随便写)
micro-base-server:
#访问路径(不能随便写,它需要对应微服务中Controller发布的接口)
path:
/auction/**
/market/**
#在eureka中注册的微服务名称
serverId: micro-base-server
micro-user-server:
path:
/feign/**
/rent/**
serverId: micro-user-server
url格式:网关微服务ip:网关微服务端口/路由ID/对应微服务Controller路径
zuul:
routes:
#eureka中微服务id: 访问路径
micro-base-server: /auction/**,/market/**
micro-user-server: /feign/**,/rent/**
Zuul它包含了两个核心功能:对请求的路由和过滤
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。其实,路由功能在真正运行时,它的路由映射和请求转发同样也由几个不同的过滤器完成的。所以,过滤器可以说是Zuul实现API网关功能最为核心的部件,每一个进入Zuul的HTTP请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端
Zuul中的过滤器跟我们之前使用的javax.servlet.Filter不一样,javax.servlet.Filter只有一种类型,可以通过配置urlPatterns来拦截对应的请求
Zuul中的过滤器总共有4种类型,且每种类型都有对应的使用场景
//需要被spring容器管理
@Component
public class MyZuulFilter extends ZuulFilter {
/**
* 用来指定过滤器的类型
* pre:路由之前
* routing:路由之时
* post: 路由之后
* error:发送错误调用
*/
public String filterType() {
return "pre";
}
/**
* 过滤器的顺序
* @return 返回的Int越小,越先执行
* 每个过滤器,默认都有一个中间值5
*/
public int filterOrder() {
return 0;
}
/**
* 用来判断哪些路径,是需要做过滤处理的
* @return true 需要做过滤(就是需要执行run()) false 不需要做过滤(就是不需要执行run())
*/
public boolean shouldFilter() {
return true;
}
/**
* 具体的过滤器规则
*/
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
System.out.println("进入路由的过滤器 "+request.getServletPath());
//true:成功响应,false:拒绝响应
context.setSendZuulResponse(true);
context.setResponseStatusCode(200);
return null;
}
}
不要添加spring-boot-starter-web,因为gateway使用的是webFlux,和springboot的web场景启动器会有冲突
<dependencies>
<dependency>
<groupId>top.ygang</groupId>
<artifactId>star-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
</dependencies>
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
server:
port: 80
spring:
application:
name: gateway
cloud:
gateway:
# 自定义配置路由规则
routes:
# 路由标识,必须唯一,默认是UUID
- id: student-sys
# 路由的目标地址
uri: http://localhost:8080
# 路由的优先级,数字越小代表路由的优先级越高
order: 1
# 断言
predicates:
# 路径断言,当请求路径匹配Path时,就会路由到uri
- Path=/student-sys/**
# 过滤器
filters:
# 转发之前,去掉1层路径
- StripPrefix=1
访问地址格式:http://gateway的ip:gateway的端口/请求路径Path/Controller路径
在简单使用的基础上,将Gateway添加到nacos上,通过微服务名称在nacos上查找微服务,一边实现访问微服务集群,实现负载均衡
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
server:
port: 80
spring:
application:
name: gateway
cloud:
nacos:
discovery:
# 将服务注册到nacos
server-addr: http://localhost:8848
gateway:
discovery:
locator:
# 从nacos中获取服务信息,自动生成routes,开启会导致断言失效
enabled: false
# 自定义配置路由规则
routes:
# 路由标识,必须唯一,默认是UUID
- id: student-sys
# 路由的目标地址,lb(loadblance)表示使用负载均衡,其后是微服务的标识
uri: lb://student-system
# 路由的优先级,数字越小代表路由的优先级越高
order: 1
# 断言
predicates:
# 路径断言,当请求路径匹配Path时,就会路由到uri
- Path=/student-sys/**
# 过滤器
filters:
# 转发之前,去掉1层路径
- StripPrefix=1
server:
port: 80
spring:
application:
name: gateway
cloud:
nacos:
discovery:
# 将服务注册到nacos
server-addr: http://localhost:8848
gateway:
discovery:
locator:
# 让gateway从nacos中获取服务信息,自动生成routes,开启会导致断言失效
enabled: true
路由(Route)是Gateway中的组件之一,用来选择某一个具体的微服务。路由有以下的相关配置:
大致的执行流程如下:
Predicate(断言)用于进行条件判断,只有断言都返回真,才会执行真正的路由。
Spring Cloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:
基于Datetime类型的断言工厂
AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否介于指定时间段之内
-After=2020-12-31T23:59:59.999+08:00[Asia/Shanghai]
基于远程地址的断言工厂
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中
-RemoteAddr=192.168.1.1/24
基于Cookie的断言工厂
CookieRoutePredicateFactory:接收两个参数,cookie名字和一个正则表达式。判断请求Cookie是否具有给定名称且值与正则表达式匹配
-Cookie=foo,\w*
基于Header的断言工厂
HeaderRoutePredicateFactory:接收两个参数,标题名和正则表达式。判断请求Header是否具有给定名称且值与正则表达式匹配
-Header=X-Request-Id,\d+
基于Method请求方法的断言工厂
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否与指定的类型匹配
-Method=Get
基于Path请求路径的断言工厂
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则
-Path=/foo/bar
基于Host的断言工厂
基于Query请求参数的断言工厂
基于路由权重的断言工厂
1、编写一个断言工厂的类
类名必须以RoutePredicateFactory为结束
继承AbstractRoutePredicateFactory
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
public AgeRoutePredicateFactory(){
super(AgeRoutePredicateFactory.Config.class);
}
// 读取配置文件中的参数,并封装到Config内部类的对象中
@Override
public List<String> shortcutFieldOrder() {
// 该集合中元素的顺序,必须与配置文件中参数的顺序一致
return Arrays.asList("minAge", "maxAge");
}
@Override
public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) {
return new Predicate<ServerWebExchange>(){
@Override
public boolean test(ServerWebExchange serverWebExchange) {
//通过路由获得传递的参数
String age = serverWebExchange.getRequest().getQueryParams().getFirst("age");
//判断是否有值
if(!StringUtils.isEmpty(age)){
//转型
Integer a = Integer.parseInt(age);
return a>=config.getMinAge()&&a<=config.getMaxAge();
}
return false;
}
};
}
public static class Config {
private Integer minAge;
private Integer maxAge;
public Integer getMinAge() {
return minAge;
}
public void setMinAge(Integer minAge) {
this.minAge = minAge;
}
public Integer getMaxAge() {
return maxAge;
}
public void setMaxAge(Integer maxAge) {
this.maxAge = maxAge;
}
}
}
2、在配置文件中配置断言
配置后,在启动gateway时,会对自定义断言工厂进行加载
访问时,如果没有传age参数或age不符合配置中的范围,那么会404
过滤器的作用:在请求的处理过程中,对请求和响应进行加工。
在Gateway中,Filter的生命周期主要有两个阶段:"pre"和"post"
Gateway的Filter从作用范围可分为两种:GatewayFilter与GloalFilter。
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称和对应的值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名和对应的值 |
AddResponseHeader | 为原始响应添加Header | Header的名称和对应的值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand的名称 |
FallbackHeaders | 为FallbackUrl的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性, 路由过滤器会检查该属性以决定是否要发送原始的Host |
无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、 denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后的路径 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save操作 | 无 |
secureHeaders | 为原始响应添加一系列响应头,这些响应头可以保证安全 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中,某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP状态码,而已是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收的请求的最大大小,如果请求包大小超过设置的 值,则会返回413 Payload Too Large |
请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
server:
port: 9009
spring:
application:
name: star-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: false
routes:
- id: product_route
uri: lb://star-product
order: 1
predicates:
- Path=/product-serv/**
filters:
- StripPrefix=1
- SetStatus=202 #修改 response的状态码
请求后,相应的状态码变为202
1、编写一个过滤器类
类名必须以GatewayFilterFactory为结尾
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
public LogGatewayFilterFactory(){
super(LogGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if(config.isConsolelog()){
System.out.println("控制台日志功能开启!!");
}
if(config.isCachelog()){
System.out.println("缓存日志功能开启!!");
}
return chain.filter(exchange);
}
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("consoleLog", "cacheLog");
}
public static class Config {
private boolean consolelog;
private boolean cachelog;
public boolean isConsolelog() {
return consolelog;
}
public void setConsolelog(boolean consolelog) {
this.consolelog = consolelog;
}
public boolean isCachelog() {
return cachelog;
}
public void setCachelog(boolean cachelog) {
this.cachelog = cachelog;
}
}
}
2、在配置文件中添加过滤器
全局过滤器不需要再配置文件中进行配置
全局过滤器 | 作用 |
---|---|
ForwardRoutingFilter | 用于本地forward,也就是将请求在Gateway服务内进行转发,而不是转发到下游服务 |
LoadBalancerClientFilter | 整合Ribbon实现负载均衡 |
NettyRoutinFilter | 使用Netty的 HttpClient 转发http、https请求 |
NettyWriteResponseFilter | 将代理响应写回网关的客户端侧 |
RouteToRequestUrlFilter | 将从request里获取的原始url转换成Gateway进行请求转发时所使用的url |
WebsocketRoutingFilter | 使用Spring Web Socket将转发 Websocket 请求 |
GatewayMetricsFilter | 整合监控相关,提供监控指标 |
1、编写过滤器类,实现GlobalFilter和Ordered接口,重写方法
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(!"test".equals(token)){
System.out.println("未认证");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
2、进行访问,只有参数token为test时,才可以正常访问