背景

    服务注册发现巧秒的实现了各个微服务之间的松耦合调用,从而不考虑服务提供方实例节点的动态变化(增加或减少)。特别是配合配置中心、负载均衡、断路器等功能,完美的实现了微服务架构。所有这些微服务都是在一个安全的局域网内裸奔,不过也并无大碍,但是整个服务体系最终会面向客户,不可能完全闭关锁国自己嗨,一旦提供的服务需要暴露给外部使用就会带来一连串的问题:如:安全、路由、使用的复杂度、性能、新老业务的对接等。

API网关是一个更为智能的应用服务器,它的定义类似于面向对象设计模式中的Facade模式,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由、 负载均衡、 校验过滤等功能之外,还需要更多能力,比如与服务治理框架的结合、请求转发时的熔断机制、服务的聚合等一系列高级功能

 

如何解决问题:

    首先,对于路由规则与服务实例的维护间题。SpringCloud Zuul通过与SpringCloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。这样的设计非常巧妙地将服务治理体系中维护的实例信息利用起来,使得将维护服务实例的工作交给了服务治理框架自动完成,不再需要人工介入。

    其次,对千类似签名校验、登录校验在微服务架构中的冗余问题。SpringCloud Zuul提供了一套过滤器机制,它可以 很好地支持这样的任务。开发者可以通过使用Zuul来创建各种校验过滤器,然后指定哪些规则的请求需要执行校验逻辑,只有通过校验的才会被路由到具体的微服务接口,不然就返回错误提示。通过这样的改造,各个业务层的微服务应用就不再需要非业务性质的校验逻辑了,这使得我们的微服务应用可以更专注千业务逻辑的开发,同时微服务的自动化测试也变得更容易实现。

    简单来说,就是既具备路由转发功能,又具备过滤器功能,比如将/aaa/**路径请求转发到service-ribbon服务上,将/bbb/***路径请求转发到service-feign服务上,比如过滤,对请求参数的信息进行过滤,不符合的进行过滤拦截等。

 

(1)Zuul是Netflix基于JVM的路由器和服务器端负载均衡器。最常用的场景是替换Nginx反向代理后台微服务供前端UI访问

(2) Zuul使用Ribbon来定位一个通过发现转发的实例,所有请求都以hystrix命令执行,所以故障将显示在Hystrix指标中。 Zuul不包括发现客户端,因此对于基于服务ID的路由,需要在类路径中提供其中一个路由

 

springcloud使用什么网关 springcloud api网关_spring

Zuul的能力:

智能路由:通过与Eureka整合,将自身注册到服务中心,可以获到所有其他微服务实例信息。Zuul默认通过以服务名作为ContextPath来创建路由映射,可以满足大多数情况需要,特殊路由可以通过配置来实现,在Zuul默认路由规则小节有详细描述。

权限校验:Zuul提供一套过滤器机制,可以实现签名校验、登录校验等与业务逻辑无关的操作,在Zuul过滤器小节有详细描述

 

什么是zuul

zuul是由netflix提供的用于内外网隔离和负载均衡以及路由的轻量级网关。

springcloud使用什么网关 springcloud api网关_ide_02

下图为netflix官网的使用方式

springcloud使用什么网关 springcloud api网关_微服务_03

zuul作用

根据spring cloud官网解释,zuul具有以下功能:

  • 认证
  • 监控
  • 压力测试
  • 金丝雀测试
  • 动态路由
  • 服务迁移
  • 负载剪裁
  • 安全
  • 静态资源处理
  • 动态流量管理

集成

maven依赖

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

代码

main方法启动类

@SpringBootApplication
@EnableEurekaClient//eureka client
@EnableZuulProxy //zuul服务器
public class ZuulApp
{

    public static void main( String[] args )
    {
        //启动项目
        SpringApplication.run(ZuulApp.class,args);
    }
}

配置

#应用名称
spring:
  application:
    name: zuul

#端口
server:
  port: 9393


#管理接口配置
management:
  context-path: /admin
  port: ${server.port}


#eureka client配置:支持从eureka获取服务自动路由
eureka:
  instance:
    preferIpAddress: true
    statusPageUrlPath: ${management.context-path}/info
    healthCheckUrlPath: ${management.context-path}/health
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}
    homePageUrl: https://${spring.cloud.client.ipAddress}:${server.port}
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/


#自定义配置
zuul:
  routes:
    #服务
    baidu:
      #context-path配置:如http://api.xxx.com/baidu/user/1中的/baidu
      path: /baidu/**
      #context-path对应的实际提供方的服务地址
      url: http://www.baidu.com

测试

浏览器中访问 http://localhost:9393/baidu , 正常情况下会跳转到baidu.com,即实现的简单的路由

集成服务注册发现

上面的例子简单演示了手功配置服务与服务节点的路由映射方式。实际业务中,服务会随着业务的变化越来越多,手工维护太多的映射会变得很困难也容易出错。服务注册发现是一个自动维护服务与各节点映射的中央服务器,如果服务提供方是微服务,就可完全重用服务注册发现的映射,从而实现路由配置的全自动化。事实上zuul项目中,只需要加入eureka client相关依赖和简单配置(注册中心地址等)默认自动支持eureka路由映射。上一节已经把eurka相关依赖和配置引入了项目,因此本节不需要任何修改,即支持eureka.

测试

假如注册到eureka中的微服务名为:stores,节点有多个; 接口:/product 返回内容中包含当前节点的ip, 那么在浏览器中访问:http://localhost:9393/stores/product ,不停刷新返回的ip会不断变化。

原理

以下图片为官网原理图

springcloud使用什么网关 springcloud api网关_ide_04

zuul通过大量的filter对请求进行安全、认证、路由进行控制。

PRE Filters 是在把请求路由到目标节点前执行。如:认证、加载目标服务节点、打印日志。

ROUTING Filters 把请求路由到目标服务的节点. 到目标的请求就在这些filter中被创建,并通过Apache HttpClient 或 Netflix Ribbon转发到目标节点。

POST Filters 是目标节点请示结束并返回到zuul后执行。可以把HTTP headers添加到返回给客户端的response中,并可以收集统计信息和健康信息,以及把目标节点的业务数据返回给客户。

ERROR Filters 任何一个步骤出错都会调用当前类型的filter.

ZuulServletFilter 核心代码如下:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            try {
                preRouting();
            } catch (ZuulException e) {
                error(e);
                postRouting();
                return;
            }

            filterChain.doFilter(servletRequest, servletResponse);
            try {
                routing();
            } catch (ZuulException e) {
                error(e);
                postRouting();
                return;
            }

            try {
                postRouting();
            } catch (ZuulException e) {
                error(e);
                return;
            }
        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }

    }

zuul通过RequestContext(theadLocal)保存了当前request/response,并在各filter中进行传递。各种类型在spring cloud的实现,具体请查看地:spring-cloud-netflix-core-xxx.jar下org.springframework.cloud.netflix.zuul.filters包中的类。

自定义filter 

zuul可以实现很多功能,但部分功能并没实现,有时为了达到定制化的目的,可以行实现对应的filter,原理:实现ZuulFilter接口,代码:

@Component //注册为普通的bean
public class MyPreFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(MyPreFilter.class);

    @Override
    public String filterType() {
       return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        LOGGER.debug("执行了MyPreFilter");
        return null;
    }

}

filterType:返回过滤器的类型。有pre、route、post、error类型。详细参考com.netflix.zuul.ZuulFilter.filterType() 。

filterOrder:返回一个int值来指定过滤器的执行顺序,不同的过滤器允许返回相同的数字。

shouldFilter:返回一个boolean值来判断该过滤器是否要执行,true表示执行,false表示不执行。

run:过滤器的具体逻辑。

 

全局异常处理

介绍一下Zuul的全局异常处理的一种方式:添加一个类型为”error”的filter,将错误信息写入RequestContext,这样SendErrorFilter就可以获取错误信息了

class ErrorFilter extends ZuulFilter {

    @Override
    String filterType() {
        return FilterConstants.ERROR_TYPE;
    }

    @Override
    int filterOrder() {
        return 10;
    } 

    @Override
    boolean shouldFilter() {
        return true;
    }

    @Override
    Object run() {
        RequestContext context = getRequestContext();
        Throwable throwable = context.getThrowable();
        LOGGER.error("[ErrorFilter] error message: {}", throwable.getCause().getMessage());

        ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        ctx.set("error.exception", throwable.getCause());
        return null
    }

}

性能优化参考

在application.yml文件中配置线程数、缓冲大小

server:
    tomcat:
        max-threads: 128 # 最大worker线程
        min-spare-threads: 64 # 最小worker线程
    undertow:
        io-threads: 8 # IO线程数,默认为CPU核心数,最小为2
        worker-threads: 40 # 阻塞任务线程池,值设置取决于系统的负载,默认为io-threads * 8
        buffer-size: 512 # 每块buffer的空间大小
        buffers-per-region: 10 # 每个区分配的buffer数量
        direct-buffers: 512 # 是否分配的直接内存

在application.yml文件中配置zuul和ribbon

zuul:
    host:
        max-total-connections: 500 # 每个服务的http客户端连接池最大连接,默认值是200
        max-per-route-connections: 50 # 每个route可用的最大连接数,默认值是20
        ribbon-isolation-strategy: THREAD # 可选:SEMAPHORE THREAD

问题

性能和高可用

做为zuul网关,所有请求如果都经过zuul,那么很可能网关会成为整个系统的瓶颈,目前为了提高性能和高可用主是通过横向扩展机器节点以及断路器实现。zuul支持使用groovy语言来动态修改filter,它是基于jvm的语言,语法简单并且很多与java类似.