微服务实战SpringCloud之Zuul

注意:本文的前提是基于zuul的1.3.X版本来解析的,2.0版本采用了netty作为底层框架重新设计了整个zuul的架构,将在后面进行分析。

zuul是什么

zuul是Netflix设计用来为所有面向设备、web网站提供服务的所有应用的门面,zuul可以提供动态路由、监控、弹性扩展、安全认证等服务,他还可以根据需求将请求路由到多个应用中。

zuul是用来解决什么问题的

在使用网关之前,动态的路由是通过Nginx的配置来做的,但是一旦发生改变,比如IP地址发生改变,加入其它路由,就要重新配置Nginx,重启Nginx。安全认证是放在每一个应用中,应用中包含了非业务强相关的内容,看起来也是不够优雅。

在目前的应用中,zuul主要用来做如下几件事情:

  • 动态路由:APP、web网站通过zuul来访问不同的服务提供方,且与ribbon结合,还可以负载均衡的路由到同一个应用不同的实例中。

  • 安全认证:zuul作为互联网服务架构中的网关,可以用来校验非法访问、授予token、校验token等。

  • 限流:zuul通过记录每种请求的类型来达到限制访问过多导致服务down掉的目的。

  • 静态响应处理:直接在zuul就处理一些请求,返回响应内容,不转发到微服务内部。

  • 区域弹性:主要是针对AWS上的应用做一些弹性扩展。

zuul的最佳实践是怎样的

Netflix是如何使用zuul的?

可以看到,在Netflix的架构中,亚马逊的弹性负载均衡作为第一层,zuul作为第二层,为所有应用的网关,请求经过AWS的负载均衡先发送到zuul,zuul再将流量转发到各个API、website等等,然后各个API、website应用再调用各个微服务。

当然在Netflix的使用中,zuul也结合了Netflix的其他微服务组件一起使用。

  • Hystrix:用来服务降级及熔断

  • Ribbon:用来作为软件的负载均衡,微服务之间相互调用是通过ribbon作为软件负载均衡使用负载到微服务集群内的不同的实例

  • Turbin:监控服务的运行状况

  • Feign:用作微服务之间发送rest请求的组件,可以将rest调用类似spring的其他bean一样直接注入使用功能

  • Eureka:服务注册中心

入门示例

引入zuul

dependencies {
    //spring-cloud-starter-netflix-zuul模块
    implementation('org.springframework.cloud:spring-cloud-starter-netflix-zuul')
    //test模块
    testImplementation('org.springframework.boot:spring-boot-starter-test')
    //引入eureka-client模块
    implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
}

这里引入eureka-client是为了将zuul也注册到eureka中,作为微服务中的一个应用,那么zuul就能将eureka中注册的所有微服务应用的注册信息都拿到,从而做到动态路由。

启动类

@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
   

    public static void main(String[] args) {
   
        SpringApplication.run(ZuulApplication.class, args);
    }
}

zuul的路由配置

zuul的配置类为ZuulProperties,配置前缀为zuul。

下面为路由的一些示例配置(未结合eureka):

zuul:
  routes:
    product: http://localhost:8082/product/**
    cart: http://localhost:8081/cart/**

zuul服务引用了eureka-client后配置可以改成如下:

zuul:
  routes:
    product: /product/**
    cart: /cart/**

或者更细致的配置:

zuul:
  routes:
    cart:
      path: /cart/**
      serviceId: cart

原访问地址:http://localhost:8081/cart/1

经过zuul后的访问地址:http://localhost:8080/cart/cart/1

访问效果:

注意:zuul只有结合了eureka,才会有ribbon作为软件负载均衡,直接配置逻辑URL,不会起到负载均衡的效果,也不会有hystrix作为熔断器使用。

如下:

zuul:
  routes:
  users:
  path: /product/**
  url: http://localhost:8082/product

如果想指定多个服务的列表且需要通过ribbon实现负载均衡,配置可参考如下:

zuul:
  routes:
    users:
      path: /product/**
      serviceId: product

ribbon:
  eureka:
    enabled: false

product:
  ribbon:
    listOfServers: example.com,google.com

当整合eureka时,配置简单,服务太多,不想一个个配置的时候,可以通过定义PatternServiceRouteMapper来全局定义匹配规则。

个人推荐这种方式,简单。

示例如下:

@Configuration
public class ZuuPatternServiceRouteMapperConfiguration {
   

    //注意!!!这两者保留一个即可。
    /**
     * 没有版本号的路由匹配规则bean
     * @retu没rn 路由匹配规则bean
     */
    @Bean
    public PatternServiceRouteMapper patternServiceRouteMapper(){
   

        return new PatternServiceRouteMapper("(?<version>v.*$)","${name}");
    }

    /**
     * 有版本号的路由匹配规则bean
     * @return 路由匹配规则bean
     */
    @Bean
    public PatternServiceRouteMapper patternServiceRouteMapperWithVersion(){
   
        return new PatternServiceRouteMapper("(?<name>.*)-(?<version>v.*$)","${version}/${name}");
    }

}

全局添加映射前缀

通过zuul.prefix可以指定全局映射前缀,可以避免服务URL直接暴露出去,如前缀为/api(注意,这个”/“要加上,否则可能会出现404),默认情况下,代理前缀会在请求转发前从请求中删除前缀,可以通过zuul.stripPrefix来配置,默认是true。

zuul:
  prefix: /api
  strip-prefix: true

这样原来直接访问比如说cart服务的地址为http://localhost:8081/cart/1,通过zuul网关,且添加了全局映射前缀/api后的访问路径变为http://localhost:8080/api/cart/cart/1。

如果在局部路由想关闭这个删除前缀映射,可以通过以下配置指定:

zuul:
   routes:
     cart:
       path: /cart/**
       stripPrefix: false

不走路由的服务或者映射配置

ZuulProperties配置中,有两个属性ignoredServices,ignoredPatterns,属性类型均为LinkedHashSet。

ignoredServices:指定忽略某些服务的路由

ignoredPatterns:配置不走路由的某些路由匹配规则

zuul的filter

1.X版本的zuul实现是依赖于servlet的,所以对过滤器支持较好。zuul本身提供了较多的filter,如SendForwardFilter、DebugFilter、SendResponseFilter、SendErrorFilter等。

zuul本身也提供了抽象类ZuulFilter,供自定义filter。

  • pre:在zuul按照规则路由到下游服务之前执行,如果需要对请求进行预处理,比如鉴权、限流等,应在此类型的过滤器实现。
  • route:这类filter是zuul路由的具体执行者,是HTTPClient、OKhttp、或ribbon发送原始http请求的地方。
  • post:这类filter是下游服务返回响应信息后执行的地方,如果需要对response做一些处理,可以考虑在此类过滤器实现。
  • error:在整个生命周期类如果出现异常,则会进入此类过滤器,可作为全局异常处理。
自定义ZuulFilter

自定义ZuulFilter,需要实现几个方法,下面为前置过滤器的简单示例。

public class AuthenticationFilter extends ZuulFilter {
   
    @Override
    public String filterType() {
   
        //fiterType,有pre、route、post、error四种类型,分别代表路由前、路由时
        //路由后以及异常时的过滤器
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
   
        //排序,指定相同filterType时的执行顺序,越小越优先执行,这里指定顺序为路由过滤器的顺序-1,即在路由前执行
        return return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
   
        //是否应该调用run()方法
        return false;
    }

    @Override
    public Object run() throws ZuulException {
   
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        String token = request.getHeader("X-Authentication");
        if (StringUtils.isBlank(token)) {
   
            throw new TokenNotValidException("token不存在!");
        }
        //校验token正确性
        return null;
    }
}

多个过滤器之间可能想要传输内容,那么可以通过zuul提供的RequestContext来完成,RequestContext本身也是依赖于ThreadLocal来完成的。RequestContext本身也携带了HttpServletRequest和HttpServletResponse,可以获取请求或响应中的内容。

zuul的过滤器也可以配置为不启用

通过zuul...disable=false来关闭指定过滤器。

例如:

zuul.SendResponseFilter.post.disable=true

@EnableZuulServer与@EnableZuulProxy的区别

在zuul的@EnableZuulProxy注解的源代码中,是这么说的,@EnableZuulProxy提供了一些基本的反向代理过滤器,@EnableZuulServer只是将zuul指定为一个zuul的server,并不提供任何反向代理的过滤器。一般我们推荐使用@EnableZuulProxy,如果不想用zuul自带的过滤器,可以通过上面的方式关闭指定的过滤器。

@EnableZuulProxy自带的filter如下:

  1. pre类型:

    • ServletDetectionFilter

    • FormBodyWrapperFilter:解析请求form表单并在转发到下游请求前重新编码

    • DebugFilter:会打印执行每个过滤器的执行日志,但仅仅是打印”Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);“

    • SendForwardFilter:将请求转发(路由)到下游服务

  2. post类型:

    • SendResponseFilter:将下游服务对请求的响应写入当前response中。
  3. error类型:

    • SendErrorFilter:处理异常情况的过滤器

zuul的http客户端

zuul实现路由,是通过http方式转发到其他服务的,那么就需要http客户端。默认情况下是通过Apache HTTP Client来发送http请求的,如果想使用restClient或者OKhttp可以通过配置指定。

ribbon:
  okhttp:
    enabled: true

或者

ribbon:
  restclient:
    enabled: true

当然可以自定义Apache HTTP Client和OkHttpClient并声明为Bean。

敏感header内容

zuul支持将一些敏感的header内容不转发到下游的其他服务中,通过zuul.sensitiveHeaders将这些header内容对下游服务屏蔽。默认情况下,header中的Cookie、Set-Cookie、Authorization将不被传递到下游服务器中,可以通过zuul.sensitiveHeaders指定全局的敏感header内容。所以在下游服务器想要获取到cookie内容时,这里需要重新配置下。个人建议,cookie、Authorization内容应该在zuul内将其转换成下游服务需要的userId、user等,再传递到下游服务器。

超时配置

zuul支持配置超时时间,如果使用的是eureka作为服务注册中心,那么只要指定ribbon的超时时间即可。即ribbon.ReadTimeout和ribbon.SocketTimeout。如果使用的是具体的URL路由,那么通过zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis指定。zuulProperties内部类Host中还有一些诸如maxTotalConnections最大连接数等的一些配置。

CORS配置

默认情况下,zuul是允许所有cors请求的,如果要自定义cors,可以通过自定义WebMvcConfigurer来完成。

如:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
   

    @Override
    public void addCorsMappings(CorsRegistry registry) {
   
        registry.addMapping("/*")
                .allowedOrigins("http://baidu.com");
    }
}

其他配置

重试

zuul.retryable可以配置zuul是否使用ribbon的重试机制。

代理请求header

zuul.addProxyHeaders可以配置是否将X-Forwarded-Host放入转发的请求中

zuul的实现原理及设计是怎样的

了解了zuul的使用方式,我们要开始了解下zuul的源码以及zuul的整个架构设计。

先从启动类ZuulApplication入手。

@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
   

    public static void main(String[] args) {
   
        SpringApplication.run(ZuulApplication.class, args);
    }
}

可以看到启动类上加了注解@EnableZuulProxy,我们看下这个注解的源码。

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulPr
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值