修订日期 | 内容 |
---|---|
2021-2-22 | 初稿 |
5-Spring Cloud微服务快速搭建-网关-(zull、gateway)
zuul网关
简介
zuul网关由netflix开发的开源的微服务网关,zuul网关的核心是一系列的过滤器,这些过滤器注意有以下功能
- 动态路由:将不同的请求分发到不同的服务器
- 压力测试:逐渐增加流量
- 负载设置:为每一种负载类型增加对应的容器,并放弃超出限定的请求
- 静态响应处理:如css,js的文件避免服务转发到内部
- 身份认证:对每一份资源进行安全验证
搭建zuul网关
- 创建工程导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
-
使用注解
@EnableZuulProxy
-
配置路由
- 基础配置法(类似与Nginx的功能,不推荐)
# 路径设置
zuul:
routes:
express-service: # 服务名
path: /express-service/** # 映射路径 如:localhost://express-serivce/query/1
url: http://localhost:8001 # 实际映射地址
- 服务名配置法(配合eureka使用,这才是正确的打开方式)
1,先接入eureka,接入eureka服务请参考待补充链接
zuul:
routes:
express-service: # 路由名-随便定义
path: /express-service/** # 映射路径 如:localhost://express-serivce/express/query/1
# 面向服务配置 express-service为注册到eureka的服务名
serviceId: express-service
上述配置可以继续简化(依然不推荐):
zuul:
routes:
express-service: /express-service/** # 简化配置(express-service 一定要存在,不能随便定义)
终极配置
zuul:
routes:
# express-service: /express-service/** # 可以省略,zuul会根据eureka中的服务名自动路由转发
【坑点】新手注意,zuul使用eureka接入服务时,如果响应时间超过1秒会报如下错误
前端报错:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing >this as a fallback.
Tue Feb 23 23:13:38 CST 2021
There was an unexpected error (type=Gateway Timeout, status=504).
后端报错:
com.netflix.zuul.exception.ZuulException:
at org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter.findZuulException(SendErrorFilter.java:118) ~[spring-cloud-netflix-zuul-2.2.7.RELEASE.jar:2.2.7.RELEASE]
at org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter.run(SendErrorFilter.java:78) ~[spring-cloud-netflix-zuul-2.2.7.RELEASE.jar:2.2.7.RELEASE]
解决方案:调整负载均衡ribbon的超时时间
ribbon:
ReadTimeout: 3000
SocketTimeout: 3000
zuul 网关过滤器
过滤器类型介绍
- pre : 在转发微服务之前调用
- routing 在调用微服务时调用
- post 在微服务返回了结果时调用
- error 在整个阶段抛出异常时调用
zuul的执行过程图:
创建一个过滤器
创建一个类,继承ZuulFilter即可
@Component
public class LoginFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(LoginFilter.class);
/**
* 过滤器类型,
* pre,routing,post,error
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的执行顺序,当有多个过滤器时实现的顺序,数字越小越先执行
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 是否开启,true开启,false 关闭
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤的业务逻辑
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
logger.info("执行了过滤器");
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String token = request.getParameter("token");
if(StringUtils.isEmpty(token)){
//直接拦截请求
currentContext.setSendZuulResponse(false);
currentContext.setResponseBody("token验证不通过");
currentContext.getResponse().setCharacterEncoding("UTF-8");
currentContext.getResponse().setContentType("text/html;cahrset=UTF-8");
}
return null;
}
}
Spring Cloud Gateway
由于zuul存在一些问题,
①本质是一个同步的servlet,既每一个请求过来都会分配一个线程来处理对应的请求直到客户端响应才会释放。
②zuul不支持长链接,如:webscoket
我们可以采用其他的替换方案 spring cloud gateway
整合
- 引入maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 如果父工程中有引入spring-web可以使用此方式巧妙剔除spring-web的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
注意spring gateway 是通过Netty+webflux实现,与tomcat会有冲突,所以子项目与父工程都不能引入spring-web
- 启动项
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(GatewayApplication.class).run(args);
}
}
- 配置路由信息
- 配置方式一基本路由配置(不推荐)
server:
port: 8087
spring:
application:
name: gateway-service-demo
cloud:
gateway:
routes:
- id: express-service
uri: http://localhost:8082
predicates: # 路由规则
- Path=/express/** # 路由条件 注意这里例子:的express不能随便命名,配置需要拦截的真实url路径,如原服务url:http://localhost:8082/express/query/1,则此处路由条件要配置express
- 测试
原服务路径url:http://localhost:8082/express/query/1
启动geteway网关后的路径:http://localhost:8087/express/query/1
<!-- 引入eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 修改配置文件并启动服务
spring:
application:
name: gateway-service-demo
cloud:
gateway:
routes:
- id: express-service
uri: lb://express-service # lb标示根据微服务名称路由
predicates: # 路由规则
- Path=/express/** # 路由条件 path路由配置
server:
port: 8088
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
instance:
ip-address: true # 使用ip地址注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} # 向注册中心注册服务ip
ribbon:
ReadTimeout: 3000
SocketTimeout: 3000
路径重写过滤器filter
如:原真实URL:http://localhost:8082/express/query/1
使用gateway后期望的URL:http://localhost:8088/api/express/query/1
这里可以使用gateway的过滤器filter实现
spring:
application:
name: gateway-service-demo
cloud:
gateway:
routes:
- id: express-service
uri: lb://express-service # lb标示根据微服务名称路由
predicates: # 路由规则
- Path=/api/** # 路由条件 path路由配置 注意这里的api与下面的路径过滤器中的api要一致,否则无法拦截
filters:
- RewritePath=/api/(?<segment>.*), /$\{segment} #路径重写的过滤器,在yml中$写为$\
配置自动根据微服务名称进行路由转发(推荐)
上述的配置略微有点复杂,有一种更为简单的配置方式
spring:
application:
name: gateway-service-demo
cloud:
gateway:
# 自动根据微服务名称路由转发 ,真实地址:http://localhost:8082/express/query/1,启用后:http://localhost:8088/express-service/express/query/1
discovery:
locator:
enabled: true # 开启
lower-case-service-id: true # 使用微服务小写名称
spring gateway 全局过滤器
实现org.springframework.cloud.gateway.filter.GlobalFilter
接口即可,如一下例子实现登录验证
@Component
public class LoginFilter implements GlobalFilter, Ordered {
private Logger logger = LoggerFactory.getLogger(LoginFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
logger.info("执行了全局过滤器...");
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(token == null){
logger.error("没有登录信息...");
//返回码:401, "Unauthorized"
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//完成请求
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
//请求顺序,多拦截器执行的顺序,越小越先执行
@Override
public int getOrder() {
return 0;
}
}
测试:
http://localhost:8088/express-service/express/query/1 -无法执行
http://localhost:8088/express-service/express/query/1?token=1 -请求成功
网关限流
基于filter限流
spring gateway 支持令牌桶算法,基于内置的过滤器实现,在过滤器中使用redis与lua脚本实现
准备工作:
- 安装redis
- 引入redis相关依赖
- 配置
<!-- 监控相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
– 待补充
基于sentinel限流
1.引入sentinel对gateway的相关依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>