SpringCloud(八)之Zuul(网关的详解和实例)

转载请注明出处:https://blog.csdn.net/weixin_41133233/article/details/85070323
本文出自 程熙cjp 的博客

继上一篇讲述完Feign的详解和实例之后,本篇小熙将会讲述Zuul的详解和实例。

一. Zuul服务网关的介绍与用途

  1. Zuul简介

    Zuul相当于是第三方调用(app应用端和PC端)和服务提供方之间的防护门。作为前端服务(Edge Service也称边缘服务,前端服务的作用是对后端服务做必要的聚合和裁剪后暴露给外部不同的设备,如PC,Pad或者Phone),Zuul旨在实现动态路由,监控,弹性和安全性。它具备根据需求将请求路由到多个AWS自动弹性伸缩组的能力。

  2. Zuul能做什么

    Netflix API流量的量级和多样性随时可能导致生产环境故障而没有预警。因此需要一个系统能使我们迅速改变策略行为,以便应对各种情况。 Zuul使用一些不同类型的过滤器,使我们能够快速灵活地将功能应用于我们的前端服务。这些过滤器具有以下功能:

     (1)- 权限控制和安全性--为每个请求提供身份认证,并拒绝不满足条件的请求。		
     
     (2)- 预警和监控--跟踪前端有意义的请求和统计数据,以便我们准确了解生产环境运行状况。
     
     (3)- 动态路由--根据需求将请求动态地路由到不同的后端集群。
     
     (4)- 压力测试--逐渐增大到集群的流量,以便进行性能评估。
     
     (5)- 负载均衡--为每种类型的请求分配容量并丢弃超过限额的请求。
     
     (6)- 静态资源处理--直接在Zuul处理静态资源并响应,而并非转发这些请求到内部集群中。
     
     (7)- 多区域弹性--实现跨AWS区域请求路由,扩大了ELB的使用范围,并使前端服务更接近我们的成员。
    

    (翻译后,英文讲述来自GitHub:https://github.com/Netflix/zuul/wiki)

二. 案例环境搭建

这里小熙自己另起了一个Zuul项目,当然在原有的基础上搭建是更好的。

  1. 第一步

    点击Project
    创建项目

  2. 第二步

    点击next
    创建第二步

  3. 第三步

    根据自己喜好,修改Group和Artiface,之后点击next

    创建第三步

  4. 第四步

    这里还是可以自定义选择,下面小熙会附上pom文件,建议对比一下。
    创建第四步

  5. 第五步

    点击Finish

    创建第五步

嗯,至此项目搭建骨架搭建完成了。如图:

项目搭建成功图片

接下来就是配置环境了。

  1. pom文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.chengxi</groupId>
        <artifactId>zuul-test</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>zuul-test</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.1.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
            <spring-cloud.version>Finchley.RC1</spring-cloud.version>
            <jjwt.version>0.7.0</jjwt.version>
            <joda-time.version>2.9.6</joda-time.version>
        </properties>
    
        <dependencies>
            <!--导入服务网关zuul-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <!--导入eureka客户端-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
            <!--导入重试机制的坐标-->
            <dependency>
                <groupId>org.springframework.retry</groupId>
                <artifactId>spring-retry</artifactId>
            </dependency>
    
            <!--引入Hystrix依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>
    
            <!--导入jwt相关依赖-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jjwt.version}</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
                <version>${joda-time.version}</version>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
        <repositories>
            <repository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>https://repo.spring.io/milestone</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
        </repositories>
    
    
    </project>
    
    

    这里的jwt将用于测试用户是否登录(具有颁发的token),是否有权限查询用户信息。

  2. yml配置文件

    server:
      port: 10012   # 端口号
    spring:
      application:
        name: zuul-service   # 网关服务名称
    zuul:
      prefix: /api   # 访问网关路径的前缀(在映射路径的前面,一般用于区别开发的版本)
      routes:
        zuul-service01:       # 随意写的区分不同映射服务器的名称(只是区分不同的映射服务器)
          path: /user-service-zuul/**    # 自定义映射服务器路径的名称(相当于key,外部访问这个地址会映射到下面的service-id这个value值。然后从eureka服务列表找到对应服务名称,进而负载均衡的请求一个服务器)
    #      url: http://127.0.0.1:8093  # 这是写的固定映射url,可代替service-id。但是不能实现服务器的负载均衡和高可用,因为总是访问同一服务器
          service-id: user-service     # eureka注册中心中要映射的服务名称,因为是双层map结构,所以可以实现负载均衡和高可用
        zuul-service02:               # 搭建另一个映射服务器,这里就简单的映射同一服务了。简单测试下而已
          path: /user-service-zuul-first/**
          service-id: user-service    # 映射服务器名称简单的使用上面的,仅供测试
    #  因为zuul是整合ribbon和hystrix的另一个客户端,所以我们需要自己导入spring-retry坐标,并且开启服务
      retryable: true
    #  配置zuul的连接时间,一般不需要配置
    #  host:
    #    max-per-route-connections:
    #    socket-timeout-millis:
    #    connect-timeout-millis:
    #  ignored-services: microservice-comsumer-movie-ribbon-withhystrix    # 这是表示某一个服务不允许代理,上面配置的是需要代理的
    eureka:
      client:
        registry-fetch-interval-seconds: 5    # 获取注册列表的周期
        service-url:
    #    eureka注册中心地址
          defaultZone: http://127.0.0.1:8090/eureka,http://127.0.0.1:8091/eureka,http://127.0.0.1:8092/eureka
      instance:
        prefer-ip-address: true   # 返回ip地址而不是hostname
        ip-address: 127.0.0.1      # 本机地址
    ribbon:
      ConnectTimeout: 250 # 连接超时时间(ms),默认值为250ms
      ReadTimeout: 2000 # 通信超时时间(ms),默认值为2000ms
      OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
      MaxAutoRetriesNextServer: 2 # 对同一服务不同实例重试次数(同一服务下集群个数的重试次数)
      MaxAutoRetries: 2 # 对同一实例重试的次数(单个集群节点服务重试的次数)
    # 开启熔断机制,超过六秒即开启熔断机制,网关内的时间排序:zuul的通信时间 > hystrix熔断时间 > retry重试时间
    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 6000
    

    这里路由转服务的映射抒写是正常抒写,没有简写。下面会介绍简写方式。

  3. 修改启动配置类

    package com.chengxi;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    //@EnableZuulProxy简单理解为@EnableZuulServer的增强版,当Zuul与Eureka、Ribbon等组件配合使用时,我们使用@EnableZuulProxy。
    @SpringBootApplication
    @EnableZuulProxy            // 开启zuul的网关功能,他是一个组合注解,集成了eureka客户端注解。
    //@EnableDiscoveryClient      // 开启eureka客户端的消费者
    public class ZuulTestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ZuulTestApplication.class, args);
        }
    }
    

    在这里小熙经过上网查询和实例测试,发现@EnableZuulProxy整合了@EnableDiscoveryClient注解(当然写上也可以)

    先贴上完整项目的注册列表图
    注册列表图片

    注意这里小熙考虑了电脑性能,只启动了一个Eureka,而消费者只是一个过渡替换成了用户,消费者和服务提供者的这种相互调用,可以理解为以后项目的服务间相互调用,feign还是能很优雅的提供restful的。

  4. 项目代码结构图(下面会附上代码)

    项目代码结构图

  5. config中的LoginFilter类

    package com.chengxi.config;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     *  编辑ZuulFilter自定义过滤器,用于校验登录
     *  重写zuulFilter类,有四个重要的方法
     *  1.- `shouldFilter`:返回一个`Boolean`值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
     *  2.- `run`:过滤器的具体业务逻辑。
     *  3.- `filterType`:返回字符串,代表过滤器的类型。包含以下4种:
     *      - `pre`:请求在被路由之前执行
     *      - `routing`:在路由请求时调用
     *      - `post`:在routing和errror过滤器之后调用
     *      - `error`:处理请求时发生错误调用
     *  4.- `filterOrder`:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高
     *
     *
     * @author chengxi
     * @date 2018/12/5 17:24
     */
    @Component
    public class LoginFilter extends ZuulFilter {
        @Override
        public String filterType() {
            // 登录校验的过滤级别,肯定是第一层过滤
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            // 执行顺序为1,值越小执行顺行越靠前
            return 1;
        }
    
        @Override
        public boolean shouldFilter() {
            // 默认此类过滤器时false,不开启的,需要改为true
            return true;
        }
    
        /**
         *  登录校验过滤器,执行逻辑的方法
         * @return
         * @throws ZuulException
         */
        @Override
        public Object run() throws ZuulException {
            // 登录校验逻辑
            // 1)获取zuul提供的请求上下文对象(即是请求全部内容)
            RequestContext currentContext = RequestContext.getCurrentContext();
            // 2) 从上下文中获取request对象
            HttpServletRequest request = currentContext.getRequest();
            // 3) 从请求中获取token
            String token = request.getParameter("access-token");
            // 4) 判断(如果没有token,认为用户还没有登录,返回401状态码)
            if(token == null || "".equals(token.trim())){
                // 没有token,认为登录校验失败,进行拦截
                currentContext.setSendZuulResponse(false);
                // 返回401状态码。也可以考虑重定向到登录页
                currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            }
    
            // 如果校验通过,可以考虑吧用户信息放入上下文,继续向后执行
            return null;
        }
    }
    
    

    这里代码都是固定的,未实现读取配置文件而形成的动态,下面会优化的。

三. 测试运行

完成以上给的,就可以实现简单的项目测试了。

如图:

  1. 失败的(没有token被拦截)
    失败的

  2. 成功的

    成功访问

由于篇幅过长,小熙将优化放在下一篇讲解吧。下篇小熙将会讲述Zuul网关优化之动态配置、jwt、rsa

猜你喜欢

转载自blog.csdn.net/weixin_41133233/article/details/85166383
今日推荐