SpirngCloud(五)之走进Zuul

389 阅读5分钟

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

介绍

Zuul 是 Netflix OSS 中的一员,是一个基于 JVM 路由和服务端的负载均衡器。提供路由、监控、弹性、安全等方面的服务框架。Zuul 能够与 Eureka、Ribbon、Hystrix 等组件配合使用。

作用

1)动态路由

动态地将客户端的请求路由到后端不同的服务,做一些逻辑处理,比如聚合多个服务的数据返回。

2)请求监控

可以对整个系统的请求进行监控,记录详细的请求响应日志,可以实时统计出当前系统的访问量以及监控状态。

3)认证鉴权

对每一个访问的请求做认证,拒绝非法请求,保护好后端的服务。

4)压力测试

压力测试是一项很重要的工作,像一些电商公司需要模拟更多真实的用户并发量来保证重大活动时系统的稳定。通过 Zuul 可以动态地将请求转发到后端服务的集群中,还可以识别测试流量和真实流量,从而做一些特殊处理。

5)灰度发布

灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

6)减少客户端与服务端的耦合

服务可以独立发展,通过网关层来做映射。

7)统一入口

为所有服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。

Zuul和Nginx

以上为zuul的用法介绍,我们可以看得出他的用法和Nginx极其的相似,再次我也对他们做出了一些个人的看法。

  • 相同点: Zuul和Nginx都可以实现负载均衡、反向代理(隐藏真实ip地址),过滤请求,实现网关的效果
  • 不同点: 1)Nginx使用的C语言开发 Zuul使用的Java语言开发。

2)负载均衡实现:Zuul采用ribbon+eureka实现本地负载均衡。Nginx采用服务器实现负载均衡。

3)Nginx相比zuul功能会更加强大,因为Nginx整合一些脚本语言(Nginx+lua)。

4)作用场景:Nginx适合于服务器端负载均衡,Zuul适合微服务中实现网关。

搭建网关场景

Maven稳健引入

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

yml文件的配置。以下的作用可以将路径shop/** 的内容拦截,直接访问www.baidu.com

  • 注意:Zuul 的 API 路由还提供了本地跳转功能,通过 forward 就可以实现。

zuul.routes.fsh-substitution.path=/api/**

zuul.routes.fsh-substitution.url=forward:/local

spring:
  application:
    name: zuul-demo
server:
  port: 3366

zuul:
  #prefix:/api  #路由前缀添加 http://localhost:3366/shop/1 这样登录接口,如果想将其变成 http://localhost:3366/api/shop/1
  routes:
    #fsh-house:
    #  path: /api/** #自定义跳转地址api/** 后面一定要配置两个星号,两个星号表示可以转发任意层级的 URL,比如"/api/shop/1"。如果只配置一个星号,那么就只能转发一级,比如"/api/shop"。
    biancheng:
      path: /shop/** #拦截地址
      url: https://www.baidu.com #跳转地址

然后我们将在Tomcat启动类上加入@EnableZuulProxy代理注解

@EnableZuulProxy
@SpringBootApplication
public class ZuulMain3366 {
    public static void main(String[] args) {
        SpringApplication.run(ZuulMain3366.class, args);
    }
}

以上我们就可以实现一个简单的拦截,我们可以访问http://localhost:3366/shop/1 浏览器将自动跳转到www.baidu.com 此链接显示的页面。

过滤器类型

介绍

Zuul 中的过滤器和我之前写到的拦截器中写到的Filter是不一样,Filter只有一种类型,可以通过配置 urlPatterns 来拦截对应的请求。而 Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。

1)pre

可以在请求被路由之前调用。适用于身份认证的场景,认证通过后再继续执行下面的流程。

2)route

在路由请求时被调用。适用于灰度发布场景,在将要路由的时候可以做一些自定义的逻辑。

3)post

在 route 和 error 过滤器之后被调用。这种过滤器将请求路由到达具体的服务之后执行。适用于需要添加响应头,记录响应日志等应用场景。

4)error

处理请求时发生错误时被调用。在执行过程中发送错误时会进入 error 过滤器,可以用来统一记录错误信息。

ZuulService


@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse)
        throws ServletException, IOException {
    try {
        init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();
        try {
            preRoute();
        } catch (ZuulException e) {
            error(e);
            postRoute();
            return;
        }
        try {
            route();
        } catch (ZuulException e) {
            error(e);
            postRoute();
            return;
        }
        try {
            postRoute();
        } catch (ZuulException e) {
            error(e);
            return;
        }
    } catch (Throwable e) {
        error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
    } finally {
        RequestContext.getCurrentContext().unset();
    }
}

使用过滤器

我们创建一个 pre 过滤器,用于实现黑名单IP("192.168.0.157")的过滤。

public class IpFilter extends ZuulFilter {
    // IP黑名单列表
    private List<String> blackIpList = Arrays.asList("192.168.0.157");
    public IpFilter() {
        super();
    }
    @Override
    public boolean shouldFilter() {
        return true
    }
    @Override
    public String filterType() {
        return "pre";
    }
    @Override
    public int filterOrder() {
        return 1;
    }
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        String ip = IpUtils.getIpAddr(ctx.getRequest());
        // 在黑名单中禁用
        if (StringUtils.isNotBlank(ip) && blackIpList.contains(ip)) {
            ctx.setSendZuulResponse(false);
            ResponseData data = ResponseData.fail("当前为非法请求:", ResponseCode.NO_AUTH_CODE.getCode());
            ctx.setResponseBody(JsonUtils.toJson(data));
            ctx.getResponse().setContentType("application/json; charset=utf-8");
            return null;
        }
        return null;
    }
}

1)shouldFilter

是否执行该过滤器,true 为执行,false 为不执行,这个也可以利用配置中心来实现,达到动态的开启和关闭过滤器。

2)filterType

过滤器类型,可选值有 pre、route、post、error。

3)filterOrder

过滤器的执行顺序,数值越小,优先级越高。

4)run

执行自己的业务逻辑,本段代码中是通过判断请求的 IP 是否在黑名单中,决定是否进行拦截。blackIpList 字段是 IP 的黑名单,判断条件成立之后,通过设置 ctx.setSendZuulResponse(false),告诉 Zuul 不需要将当前请求转发到后端的服务了。通过 setResponseBody 返回数据给客户端。

注入使过滤器生效

@Configuration
public class FilterConfig {
    @Bean
    public IpFilter ipFilter() {
        return new IpFilter();
    }
}

以上为Zuul的一部分使用场景,只要看过我之前所写的这边文章“Filter”,那么大家就可以同理去解决异常,错误等统一处理如何实现,大家都可以理解,在这我不在多说。

手写不易,还需大家多多点赞和专注,喜欢技术的同学们不要心急,此系列文章我将逐步推出,我们大家一起学习。