在微服务项目搭建之后,我们不可能让客户知道所有的微服务,让客户进行针对性地使用某一个或者几个服务。我们应当有一个统一的请求入口,这个入口替服务器拦截所有的请求,并将请求分发到对应的服务中去调用,这就是服务网关。那么作为项目的唯一入口,服务网关应当有一些必要的特性:
- 稳定性,高可用
- 性能和并发性
- 安全
- 扩展性
平时我们使用到的服务网关最多的就是Nginx + lua的形式,Nginx凭借性能的优势占据了网关服务的很大一部分市场。在SpringCloud全家桶中Netflix给我们提供了他自己的解决方案智能路由Zuul,相对而言Zuul提供了更多有意思的功能如身份验证、服务迁移、分级卸载等。至于性能,它的第一个版本的性能应该是差于Nginx的,不过也在后续的版本中对它的性能进行了升级了。
Zuul原理
zuul的核心是一系列的过滤器,这些过滤器分位四个类型
"pre":位置在路由请求到服务调用之前,一般可用于数据校验和限流等
"route":这类过滤器将请求路由到微服务,用于构建发送给微服务的请求,并发起微服务请求。
"post":这类过滤器在路由到微服务之后请求,可以对返回的数据进行处理或包装
"error":其他三类过滤器执行中如果发生错误,会执行该类过滤器。
网络图片
Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。
Zuul使用
新建一个名为zuul的modular,引入依赖如下
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类上增加注解@EnableZuulProxy
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
配置文件bootstrap.yml
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:1001/eureka/
spring:
application:
name: zuul
cloud:
config:
discovery:
enabled: true
service-id: config
label: master
profile: dev
github上面的 /zuul-dev.yml
server:
port: 2992
zuul:
routes:
xxxxx:
path: /api-product/**
serviceId: product
上面这段配置很明显是对product服务做了什么。我这里依次将eureka、config、zuul、product四个服务启动起来,通过以下三种方式去获取调用product的一个接口,结果如下:
localhost:3003/product/findByProductStatus?productStatus=1
localhost:2992/api-product/product/findByProductStatus?productStatus=1
localhost:2992/product/product/findByProductStatus?productStatus=1
其实很明显能看出来,zuul的端口为2992,后面连个请求已经可以通过zuul路由到了product服务上。其中第三个请求中有两个product,第一个是serviceId服务ID,后面的是完整的请求路径。第二个请求正如zuul-dev.yml上所示,将product服务映射到了一个自定义的路径“/api-product/**”下,因此也能获取到同意的服务。在配置中有个"xxxxx"的内容,这个内容名字可以自定义,也就是将该路由规则命名而已,没有实际意义。
这样的配置还有一种更加简单的配置方式
zuul:
routes:
product: /api-product/**
# product就不是随便命名的了,而是product的服务serviceId
Cookie相关
我们平常使用cookie是时候,是可以直接从HttpServletRequest中获取到信息的,但是每当请求经过了一层zuul的转发之后,cookie就不能直接从request中获取到了。我们需要加入一下配置
zuul:
routes:
xxxxx:
path: /api-product/**
serviceId: product
sensitiveHeaders:
即在对应的服务路由配置下,加载一个sensitiveHeaders的空值,在Zuul的配置信息类ZuulProperties中,sensitiveHeaders参数有些默认值,它的作用是默认屏蔽掉这些内容的传递,其中就包括cookie和Authorization,因此我们想要使用Cookie的话就可以把其中的Cookie去掉,我这里直接设置为空了。
此外,在里面还有一些参数我们可能也会用到,比如说有三个ignored开头的参数,和.gitignore类似,它们的作用是在不同维度划分的情况下,将某一些接口不适用zuul路由,配置了之后再通过zuul访问是找不到的(通常这些接口都是服务之间调用的,而不能被外部直接调用)
举例,加入配置后重启项目:
zuul:
ignored-patterns:
- /api-product/product/findByProductStatus
## - /product/product/findByProductStatus
再次访问,结果如下
localhost:2992/api-product/product/findByProductStatus?productStatus=1
localhost:2992/product/product/findByProductStatus?productStatus=1
因此呢,ignored-patterns就可以达成我们屏蔽某些接口的效果,这个里面也可以使用 ** 通配符等。
bus-refresh配置
配置config通过bus-refresh刷新:之前的order和product的配置方式我们知道了要在对应的Controller上添加一个@RefreshScope,但是Zuul的项目看起来没有什么Controller,我们可以添加到这里来
新建一个这样的类,在原有的ZuulProperties类上加入@RefreshScope即可
@Component
public class ZuulConfig {
@ConfigurationProperties("zuul")
@RefreshScope
public ZuulProperties zuulProperties(){
return new ZuulProperties();
}
}
自定义过滤器
原理的时候已经提到了,我们可以重写一些过滤器。比如说下面这个,这个例子是我在网上看到别人做的,我拿过来试了一下,自己写一下也能更好地理解它的原理:增加一个类型是pre的过滤器,要求请求中必须带有"token"参数,否则将不会路由过去。
在这之前,先看看zuul原本有哪些已经存在的过滤器
Zuul中默认实现的Filter(https://www.jianshu.com/p/8ea59534bedb)
按照上面的排列,接下来我根据token的逻辑,创建一个Filter内容如下:
其中某些参数如PRE_TYPE、PRE_DECORATION_FILTER_ORDER 都是从 FilterConstants 中取出使用的,这些值对应了上面zuul的默认实现的值
@Component
public class TokenCheckFilter extends ZuulFilter{
/** 表示类型是pre */
@Override
public String filterType() {
return PRE_TYPE;
}
/** 配许的序号为5-1 = 4 */
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
/** 为true表示可用,false代表该filter暂不拦截 */
@Override
public boolean shouldFilter() {
return true;
}
/** 具体逻辑,没有token则不路由请求,且返回状态码401 */
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getParameter("token");
if (StringUtils.isEmpty(token)){
/** 如果没有token或为空,不路由请求到服务 */
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
}
return null;
}
}
测试结果如下:
最后在说一下禁用指定的Filter的配置,如下所示,通过对指定过滤器的配置,可以单独关闭或者启动一个或者多个过滤器
zuul:
TokenCheckFilter:
pre:
disable: true
网友评论