Spring Cloud:第五章:Zuul服务网关

快速入门

定义user,order,pay服务,定义zull服务网关服务都注册到eureka服务上,

通过一下接口访问user,order,pay的服务,

http://localhost:7070/pay/index
http://localhost:8080/user/index
http://localhost:9090/order/index

定义服务网关服务zuul,我们看看其相关配置,zuul-service加入依赖:

   <dependencies>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-zuul</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-eureka</artifactId>
          </dependency>
    </dependencies>

配置文件:

spring:
  application:
    name: zuul-service
eureka:
  client:
    service-url:
     defaultZone: http://localhost:8761/eureka
  instance:
    instance-id:  ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
    prefer-ip-address: true
server:
  port: 6069

定义启动类:

package com.zhihao.miao;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
 
//使用@EnableZuulProxy注解开启zuul的api网关服务功能
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }
}

然后可以通过服务网关进行访问上面的三个服务接口,

http://localhost:6069/pay-service/pay/index
http://localhost:6069/user-service/user/index
http://localhost:6069/order-service/order/index

注意:
默认的zuul结合eureka会将注册到eureka的服务名作为访问的ContextPath。

也可以指定一些路由机制,application.yml中定义如下,

zuul:
  routes:
    user-service:
      path: /users/**
      serviceId: user-service

url符合/users/**规则的就被转发到user-service实例中了。

http://localhost:6069/users/user/index

之前的通过服务实例名称的也能访问

http://localhost:6069/user-service/user/index

也可以忽略所有的代理,只保留自己配置的即可。

zuul:
  ignored-services: '*'
  routes:
    user-service: /users/**

此时只能访问user-service服务,并且只能以users这种路由来访问。
不能访问

http://localhost:6069/pay-service/pay/index
http://localhost:6069/order-service/order/index

之前默认的注册到eureka上的服务(user-service)也不能访问。
不能访问

http://192.168.1.57:6069/user-service/user/index

可以访问

http://192.168.1.57:6069/users/user/index

以上的配置也可以这样指定

zuul:
  ignored-services: order-service,pay-service
  routes:
    user-service: /users/**

当然此时除了http://192.168.1.57:6069/users/user/index能被代理访问到
http://192.168.1.57:6069/user-service/user/index也能访问了
请求过滤

在实现请求理由功能之后,我们的微服务应用提供的接口就可以通过统一的api网关入口被客户端访问到了。每个客户端用户请求服务应用提供的接口时,他们的访问权限往往都有一定的限制,系统并不会将所有的微服务接口对他们开放。然而,目前的服务路由并没有限制权限这样的功能,所有请求都会被毫无保留的转发到具体的应用并返回结果,为了实现对客户端请求的安全校验和权限控制,最简单和粗暴的方法就是在每个微服务应用都实现一套用于校验签名和鉴别权限的过滤器或拦截器。这样有个问题就是功能实现太过冗余。比较好的做法就是将这些校验逻辑剥离出去,构建一个独立的鉴权服务。在完成剥离之后,直接在微服务应用中通过调用鉴权系统服务来实现校验,但是这样仅仅只是解决了鉴权逻辑的分离,并没有在本质上将这部分不属于冗余的逻辑从原有的微服务应用中拆出去,冗余的拦截器或者过滤器依然会存在。

对于这样的问题,更好的做法是通过前置的网关服务来完成这些非业务性质的校验。由于网关服务的加入,外部客户端访问我们的系统已经有了统一的入口,既然这些校验与具体的业务无关,那何不在请求到达的时候就完成校验和过滤,微服务应用端就可以去除各种复杂的过滤器和拦截器了,这使得微服务应用接口的开发和测试复杂度也得到了相应的降低。这就涉及到了zuul的另一个主要功能,请求过滤。

在zuul-service服务中定义一个过滤器,

public class AccessFilter extends ZuulFilter{
 
    private Logger logger = LoggerFactory.getLogger(getClass());
 
    //过滤器的类型,它决定过滤器在请求的哪个生命周期中执行,这里定义为pre,代表会在请求被理由之前执行。
    @Override
    public String filterType() {
        return "pre";
    }
 
    //过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行
    @Override
    public int filterOrder() {
        return 0;
    }
 
    //判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有的请求都生效。实际运行中我们可以利用该函数
    //来指定过滤器的有效范围。
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
    //过滤器的具体执行逻辑。
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
 
        logger.info("send {} request to {}",request.getMethod(),request.getRequestURI().toString());
 
        Object accessToken = request.getParameter("accessToken");
        if(accessToken == null){
            logger.warn("access token is empty");
            ctx.setSendZuulResponse(false); //令zuul过滤该请求,不对其进行路由
            ctx.setResponseStatusCode(401); //设置返回的错误码
        }
 
        logger.info("access token ok");
        return null;
    }
}

ZuulFilter接口中定义四个方法:
filterType:过滤器的类型,它决定过滤器在请求的哪个生命周期中执行,这里定义为pre,代表会在请求被理由之前执行。
filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。
shouldFilter:判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有的请求都生效。实际运行中我们可以利用该函数。
run:过滤器的具体执行逻辑。

在将过滤器纳入spring容器中,

@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }
 
    @Bean
    public AccessFilter accessFilter(){
        return new AccessFilter();
    }
}

访问服务:

http://localhost:6069/users/user/index

状态码错误为401,正确访问姿势是

http://localhost:6069/users/user/index?accessToken=111

过滤器类型与请求生命周期

PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
ERROR:在其他阶段发生错误时执行该过滤器。

除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。

总结

就目前掌握的api网关知识,总结出四点如下

    它作为系统的统一入口,屏蔽了系统内部各个微服务的细节。
    它可以与服务治理框架结合,实现自动化的服务实例维护以及负载均衡的路由转发。
    它可以实现接口权限校验与微服务业务逻辑的解耦。
    通过服务网关中的过滤器,在各个生命周期中去校验请求的内容,将原本在对外服务层做的校验迁移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。