路由网关——Zuul
Springloud中API网关是Zuul。对于网关而言,存在两个作用:第一个作用将请求的地址映射为真实服务器的地址。
然这个作用就起到路由分发的作用,从而降低单个节点的负载。从这点来说,可以把称为服务端负载均衡。从高可用的角度来说,则一个请求地址可以映射到多台服务上,如果单点出现障,则其他节点也能提供服务,这样这就是一个高可用的服务了。
Zuul网关的第二个作用是过滤服务,在互联网中,服务器可能面临各种攻击,Zuul提供了过滤器,通过它过那些恶意或者无效的请求,把它们排除在服务网站之外,这样就可以降低网站服务的风险。
构建Zuul网关
首先,有引入依赖是必要的:
1 | <dependency> |
接着就是在主要执行的程序上加一个注解:@EnableZuulProxy。
这个注解会启动Zuul网关。
我们来看一看这个注解的构成吧:
1 |
|
这么看其实有点不清晰,其实以下的注解包含了两种类型的过滤器。
pre类型过滤器
PreDecorationFilter:该过滤器根据提供的RouteLocator确定路由到的地址,以及怎样去路由。该路由器也可为后端请求设置各种代理相关的header。
route类型过滤器
(1) RibbonRoutingFilter:该过滤器使用Ribbon,Hystrix和可插拔的HTTP客户端发送请求。serviceId在RequestContext.getCurrentContext().get(“serviceId”)中。该过滤器可使用不同的HTTP客户端,例如
Apache HttpClient:默认的HTTP客户端
SquareupOkHttpClient v3:如需使用该客户端,需保证com.squareup.okhttp3的依赖在classpath中,并设置ribbon.okhttp.enabled = true。
Netflix Ribbon HTTP client:设置ribbon.restclient.enabled = true即可启用该HTTP客户端。需要注意的是,该客户端有一定限制,例如不支持PATCH方法,另外,它有内置的重试机制。
(2) SimpleHostRoutingFilter:该过滤器通过Apache HttpClient向指定的URL发送请求。URL在RequestContext.getRouteHost()中。
并且,他已经引入了断路机制。之所以引入断路机制,是因为在请求不到的时候会进行断路,以避免网关发生球无法释放的场景,导致微服务瘫痪。
配置文件
1 | server: |
这个配置是将Zuul网关注册给服务治理中心,这样它就能够获取各个微服务的服务ID了看到用户微服务映射的配置,这里采用 zuul.routes.
但是,这样配置有一个弊端,因为我们的用户微服务有两个节点,一个是9002端口,另一个是9003端口,这里只能映射到9002端口的微服务,而映射不到9003端口的微服务。
为了解决这个问题,Zuul还提供了面向服务的配置。再看到代码中的产品微服务映射配置,这里使用了zuul.routes.
这样配置后,Zuul会自动实现负载均衡,也就是会将请求转发到某个服务的节点上。
这里可以打开:http://localhost/p/product/ribbon。查看结果。
使用过滤器
上面只是将请求转发到具体的服务器或者具体的微服务上,但是有时候还希望网关功能更强大一些。
例如,监测用户登录、黑名单用户、购物验证码、恶意刷请求攻击等场景。如果这些在过滤器内判断失,那么就不再把请求转发到其他微服务上,以保护微服务的稳定。下面模拟这样的一个场景。
假设当前需要提交一个表单,而每一个表单都存在一个序列号(seria!Number),并且这个序列对应个验证码(verficationCode),在提交表单的时候,这两个参数都会一并提交到Zuul网关。
对于Redis服务器会以序列(seriaNumber)为键(key),而以验证码(verificationCode)为值(value)进行存储。
当路由网关过滤器判用户提交的验证码与Redis服务器保存不一致的时候,则不再转发请求到微服务。
测试过滤器
这里验证码使用Redis进行存储,所以会比使用数据库快得多,这有助于性能的提高,避免造成瓶颈。由于使用了Redis,因此需要在Zuul网关中引入Redis的依赖:
1 | server: |
Zuul存在一个抽象类,那就是ZuulFilter。
这里需要注意的是,只是画出了抽象类ZuulFilter自定义的抽象方法和接口ZuulFilter定义的抽类,也就是说,当需要定义一个非抽象的Zuul过滤器的时候,需要实现这4个抽象方法,在重写这4个抽象方法前,我们很有必要知道它们的作用。
shouldFilter:返回boolean值,如果为true,则执行这个过滤器的run方法。
run:运行过滤逻辑,这是过滤器的核心方法。
filterType:过滤器类型,它是一字符串,可以配置为以下4种。
pre:请求行之前filter。
route:处理请求,进行路由。
post:请求处理完成后执行的filter。
error:出现错误时执行的filter。
filterOrder:指定过滤器顺序,值越小则越优先。
使用过滤器判定验证码
我们可以在Reddit中生成一个验证码。当用户提交的验证码与Redis不一致时,就会被Zuul网关给拦截。
1 |
|
述代码中在类上标注了@Component,这样Spring就会扫描它,将其装配到IoC容器中。为继承了抽象类ZuulFilter,所以Zuul自动将它识别为过滤器。filterType方法返回了“pre,则过滤器会在路由之前执行。
filterOrder返回为0,这个方法在指定多个过滤器顺序才有意义,数字越小,则越优先,这里只有一个过滤器,所以就返回0。
然后在shouldFilter中判断是否存在序列号参数,如果存在,则返回住时,这就意味着将启用这个过滤器,否则就不再启用这个过滤器。run方法是过滤器的核心,它首先获取请求中的序列号和验证码,跟着使用StringRedisTemplate通过序列号获取Redis服务器上的验证码。
然后将Redis的验证码与请求的验证码进行比较,如果匹配不一致,则设置再转发请求到微服系统,并且将晌应码设置为401,响应类型为JSON数据集,最后还会设置响应体的内容;如果一致,则返回null,放行服务。
这样用户提交的验证码与Redis保存的不一致时,请求就会在Zuul网关中被过滤器拦截,而不会转发到微服务中
总结
这里可以举一个例子:用户服务提供一个登录接口,用户名密码正确后返回一个Token,此Token作为用户服务的通行证,那么用户登录成功后返回的Token就需要进行加密或者防止篡改处理。在到达用户服务其他接口前,就需要对Token进行校验,非法的Token就不需要转发到用户服务中了,直接在网关层返回信息即可。
那么,我们再来看一张图:
上图显示这几种过滤器的前后调用顺序,第一个是pre过滤器然后是Route过滤器,最后响应是post过滤器。
这样