• 【spring cloud】spring cloud zuul 路由网关


    【spring cloud】spring cloud zuul 路由网关

    GitHub源码地址:https://github.com/AngelSXD/springcloud

    版本介绍:

    复制代码
    <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
            <spring-boot.version>2.0.4.RELEASE</spring-boot.version>
            <spring-cloud.version>Finchley.SR1</spring-cloud.version>
            <lcn.last.version>4.2.1</lcn.last.version>
        </properties>
    复制代码

    ====================================================

    参考地址:https://www.cnblogs.com/cralor/p/9234697.html

    ====================================================

    一.简单介绍

     Zuul作为微服务系统的网关组件,用于构建边界服务,致力于动态路由、过滤、监控、弹性伸缩和安全。

    为什么需要Zuul

    Zuul、Ribbon以及Eureka结合可以实现智能路由和负载均衡的功能;

    网关将所有服务的API接口统一聚合,统一对外暴露。

    外界调用API接口时,不需要知道微服务系统中各服务相互调用的复杂性,保护了内部微服务单元的API接口;

    网关可以做用户身份认证和权限认证,防止非法请求操作API接口;

    网关可以实现监控功能,实时日志输出,对请求进行记录;

    网关可以实现流量监控,在高流量的情况下,对服务降级;

    API接口从内部服务分离出来,方便做测试。

    Zuul通过Servlet来实现,通过自定义的ZuulServlet来对请求进行控制。核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列过滤器。Zuul采取了动态读取、编译和运行这些过滤器。过滤器之间不能直接通信,而是通过RequestContext对象来共享数据,每个请求都会创建一个RequestContext对象。

    Zuul生命周期如下图。 当一个客户端Request请求进入Zuul网关服务时,网关先进入”pre filter“,进行一系列的验证、操作或者判断。然后交给”routing filter“进行路由转发,转发到具体的服务实例进行逻辑处理、返回数据。当具体的服务处理完成后,最后由”post filter“进行处理,该类型的处理器处理完成之后,将Request信息返回客户端。 

    二.配置过程

    1.pom依赖

    复制代码
        <!--熔断器 健康检查-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
        <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>    
        <!--zuul核心依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
    复制代码

    2.配置application.properties

    复制代码
    spring.application.name=springcloud-ms-gateway
    server.port=8001
    eureka.client.service-url.defaultZone=http://127.0.0.1:8000/eureka/
    
    
    
    #因为parent的pom.xml中  添加了连接数据库的依赖,所以 需要配置数据库连接相关配置
    spring.datasource.continue-on-error=false 
    spring.datasource.url=jdbc:mysql://localhost:3306/springcloudtest?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.username=root
    spring.datasource.password=root
    
    
    
    
    
    
    zuul.host.socket-timeout-millis=10000
    #网关最大超时时间10s
    zuul.host.connect-timeout-millis=10000
    #网关最大连接数 默认200
    zuul.host.max-total-connections=200
    
    #给要路由的API请求加前缀 可加可不加
    zuul.prefix=/v1
    #需要忽略的服务 ,号分隔  配置后将不会被路由
    zuul.ignored-services=spring-cloud-ms-eureka
    
    #配置了zuul之后,那么整个分布式系统,对外则只暴露http://zuul-IP:zuul-port/v1/服务自定义地址/具体API请求地址
    
    #这一组配置后,访问http://localhost:8001/v1/ms-member/member/showMember 即相当于直接访问会员服务http://localhost:9000/member/showMember
    #zuul.routes.xx  xx随便写,zuul中唯一即可
    #zuul.routes.xx.serviceId=eureka中注册的服务名 即各个服务配置文件中的spring.application.name
    zuul.routes.member.serviceId=springcloud-ms-member
    #zuul.routes.xx.path=/自定义的地址    /**表示下级也可以访问
    zuul.routes.member.path=/ms-member/**
    
    #这一组配置后,所有访问积分服务的请求 即可直接访问网关地址http://localhost:8001/v1/ms-integral/具体接口地址   ,由zuul进行路由转发和负载均衡
    zuul.routes.integral.serviceId=springcloud-ms-integral
    zuul.routes.integral.path=/ms-integral/**
    
    #这一组配置后,所有访问商品服务的请求 即可直接访问网关地址http://localhost:8001/v1/ms-goods/具体接口地址  ,由zuul进行路由转发和负载均衡
    zuul.routes.goods.serviceId=springcloud-ms-goods
    zuul.routes.goods.path=/ms-goods/**
    
    
    #txmanager地址 必填
    tm.manager.url=http://127.0.0.1:7000/tx/manager/
    复制代码

    3.启动类注解

    核心注解@EnableZuulProxy

    同时需要添加注解,服务发现,注册eureka

    复制代码
    package com.swapping.springcloud.ms.gateway;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @EnableCircuitBreaker    //Hystrix Dashboard必须加
    @EnableHystrixDashboard//展示熔断器仪表盘
    
    @EnableZuulProxy    //网关映射 注解
    @EnableFeignClients    //feign调用注解
    @EnableDiscoveryClient
    @SpringBootApplication(scanBasePackages = "com.swapping")
    public class SpringcloudMsGatewayApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudMsGatewayApplication.class, args);
        }
    }
    复制代码

     >>>>>>>>>>>>>路由转发

    4.启动网关服务 

    springcloud-ms-gateway  端口8001

    启动ms-member服务,端口9000

    那么现在访问

    http://localhost:9000/member/showMember
    即直接访问ms-member服务,的/member/showMember这个API

    现在可以访问网关,通过zuul进行动态路由

    http://localhost:8001/v1/ms-member/member/showMember
    统一访问网关地址,由网关进行统一路由转发

    至此,zuul实现路由转发! 

      >>>>>>>>>>>>>路由拦截

     5.使用zuul进行路由拦截

    现在既然所有的访问接口都是由zuul网关暴露出去的,那所有的请求都来找网关,这样的话在网关的过滤器中就可以做很多的事情。

    Filter是Zuul的核心,用来实现对外服务的控制。Filter的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,整个生命周期可以用下图来表示

    5.1 Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。

    • PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
    • ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
    • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
    • ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。

     5.2 在zuul中默认已经实现的filter有:

    类型顺序过滤器功能
    pre -3 ServletDetectionFilter 标记处理Servlet的类型
    pre -2 Servlet30WrapperFilter 包装HttpServletRequest请求
    pre -1 FormBodyWrapperFilter 包装请求体
    route 1 DebugFilter 标记调试标志
    route 5 PreDecorationFilter 处理请求上下文供后续使用
    route 10 RibbonRoutingFilter serviceId请求转发
    route 100 SimpleHostRoutingFilter url请求转发
    route 500 SendForwardFilter forward请求转发
    post 0 SendErrorFilter 处理有错误的请求响应
    post 1000 SendResponseFilter 处理正常的请求响应

    5.3 zuul还提供了一类特殊的过滤器,分别为:StaticResponseFilter和SurgicalDebugFilter

    StaticResponseFilter:StaticResponseFilter允许从Zuul本身生成响应,而不是将请求转发到源。

    SurgicalDebugFilter:SurgicalDebugFilter允许将特定请求路由到分隔的调试集群或主机

    5.4 在zuul中定义自定义的过滤器

      例如:自定义一个请求被路由之前的过滤器,用于验证请求中是否携带auth,如果携带安全验证,则成功路由;否则统一终止路由,返回响应

    自定义filter需要继承ZuulFilter【注意,自定义Filter需要注入spring 使用注解@Component】

    复制代码
    package com.swapping.springcloud.ms.gateway.filter;
    
    import com.alibaba.fastjson.JSON;
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import com.swapping.springcloud.ms.core.response.UniVerResponse;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * 路由拦截
     *
     *
     * >>>>>zuul的filter过滤器的生命周期有一下四个:
     *
     *  PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
     *  ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
     *  POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
     *  ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
     *
     *
     *  Zuul中默认实现了很多Filter,也可以自己自定义过滤器
     *
     *  下面是自己自定义过滤器
     *  实际使用中我们可以结合shiro、oauth2.0等技术去做鉴权、验证
     *
     */
    @Component
    public class AuthFilter extends ZuulFilter{
    
    
        @Override
        public String filterType() {
            return "pre";//可以在请求被路由之前调用
        }
    
        @Override
        public int filterOrder() {
            return 0;//filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
        }
    
        @Override
        public boolean shouldFilter() {
            return true;// 是否执行该过滤器,此处为true,说明需要过滤
        }
    
        /**
         *  filter需要执行的具体操作
         *
         * 例如:本filter实际执行的逻辑 是验证所有的访问请求中,是否包含安全信息auth
         * @return
         * @throws ZuulException
         */
        @Override
        public Object run() throws ZuulException {
    
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
    
            String auth = request.getParameter("auth");
            //TODO 此处可以做日志记录
            System.out.println("zuul拦截--请求前验证---auth:"+auth);
    
            //成功的情况
            if (StringUtils.isNotBlank(auth)){
                ctx.setSendZuulResponse(true); //对请求进行路由
                ctx.setResponseStatusCode(200);
                ctx.set("isSuccess", true);
            }else {
                //失败的情况
                UniVerResponse res = new UniVerResponse();
                res.beFalse3("zuul拦截--请求前验证---没有auth登录验证",UniVerResponse.ERROR_BUSINESS);
    
                ctx.setSendZuulResponse(false); //不对请求进行路由
                ctx.setResponseStatusCode(res.getCode());//设置返回状态码
                ctx.setResponseBody(JSON.toJSONString(res));//设置返回响应体
                ctx.set("isSuccess", false);
                ctx.getResponse().setContentType("application/json;charset=UTF-8");//设置返回响应体格式,可能会乱码
    
            }
    
            return null;
        }
    
    }
    复制代码

    5.5 测试自定义拦截

    启动网关服务 ms-gateway 端口:8001

    启动服务注册中心eureka

    启动要被路由的服务ms-member 端口:9000

    复制代码
    @RestController
    @RequestMapping("/member")
    public class MemberController {
    
    
        @Autowired
        MemberService memberService;
    
        @RequestMapping(value = "/showMember")
        public String showMember(){
            return "会员服务正常,会员服务是服务消费者,也就是服务调用者,会调用商品服务进行商品购买,减少库存,增加销量;
    
    同时调用积分服务,增加积分";
        }
    复制代码

    访问地址:携带auth验证

    http://localhost:8001/v1/ms-member/member/showMember?auth=111

    访问地址:不携带auth

    5.6 如果你想禁用 zuul中的filter,可以这么做:

    如果你想禁用一个,只需设置zuul.<SimpleClassName>.<filterType> .disable = true。按照惯例,过滤器后面的包是Zuul过滤器类型。例如,要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter 需设置 zuul.SendResponseFilter.post.disable = true

     >>>>>>>>>>>>>路由熔断

     6.使用zuul进行路由熔断

     类似与服务之间的feign调用熔断器设定,网关路由各个服务的请求也可以做路由熔断,在不能成功路由到具体服务上的请求时,进行降级处理,定制返回内容【也就是设置统一响应体】

    目前路由熔断仅能 支持服务级别的熔断,不支持具体到某个URL进行熔断

     自定义熔断类需要实现FallbackProvider接口【注意,自定义熔断类需要注入spring 使用注解@Component】

    复制代码
    package com.swapping.springcloud.ms.gateway.fallback;
    
    import com.alibaba.fastjson.JSON;
    import com.swapping.springcloud.ms.core.response.UniVerResponse;
    import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.client.ClientHttpResponse;
    import org.springframework.stereotype.Component;
    
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    /**
     * 路由熔断
     *
     * 类似与服务之间的feign调用熔断器设定,网关路由各个服务的请求也可以做路由熔断,
     * 在不能成功路由到具体服务上的请求时,进行降级处理,定制返回内容【也就是设置统一响应体】
     *
     * 目前路由熔断仅能 支持服务级别的熔断,不支持具体到某个URL进行熔断
     *
     */
    @Component
    public class UniVerFallback implements FallbackProvider {
    
        /**
         * 指定 可以熔断拦截 哪些服务
         * @return
         */
        @Override
        public String getRoute() {
    //        return "springcloud-ms-member";//指定 可熔断某个服务 服务名是配置文件中配置的各服务的serviceId
            return "*"; //指定    可熔断所有服务
        }
    
        /**
         * 指定  熔断后返回的定制化内容
         * @param route
         * @param cause
         * @return
         */
        @Override
        public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
            return new ClientHttpResponse() {
                @Override
                public HttpStatus getStatusCode() throws IOException {
                    return HttpStatus.OK;
                }
    
                @Override
                public int getRawStatusCode() throws IOException {
                    return 200;
                }
    
                @Override
                public String getStatusText() throws IOException {
                    return "OK";
                }
    
                @Override
                public void close() {
    
                }
    
                /**
                 * 设置响应体
                 * @return
                 * @throws IOException
                 */
                @Override
                public InputStream getBody() throws IOException {
                    //TODO 此处可以做日志记录
                    UniVerResponse uniVerResponse = new UniVerResponse();
                    uniVerResponse.beFalse3(route+"服务凉凉了",UniVerResponse.ERROR_BUSINESS);
                    return new ByteArrayInputStream(JSON.toJSONString(uniVerResponse).getBytes());
                }
    
                /**
                 * 设置响应头信息
                 * @return
                 */
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);//指定响应体 格式+编码方式
                    return headers;
                }
            };
        }
    }
    复制代码

    保证eureka启动,

    然后重启网关服务ms-gateway,端口:8001

    关闭ms-member服务,端口:9000

     然后访问地址:来访问ms-member的接口

    http://localhost:8001/v1/ms-member/member/showMember?auth=111

    就可以对指定的服务进行熔断!!!

     >>>>>>>>>>>>>路由重试

     7.使用zuul进行路由重试【慎用】

    在服务暂时不可用的情况下,可能是服务重启正被注册中心扫描或者其他原因,我们想要对发起的请求进行重试,zuul结合Spring Retry实现路由重试

      7.1 网关服务添加依赖

    <!--路由重试 依赖-->
            <dependency>
                <groupId>org.springframework.retry</groupId>
                <artifactId>spring-retry</artifactId>
            </dependency>

      7.2 application.properties添加配置

    复制代码
    #路由重试
    #是否开启路由重试,针对于 查询接口可以使用,但是对于非幂等的新增或更新接口,使用路由重试会出现很大的问题,应该注意
    
    #是否开启重试功能
    zuul.retryable=true
    #对当前服务的重试次数[不包含首次访问],也就是说实际访问接口的次数是3+1次
    ribbon.MaxAutoRetries=3
    #切换相同Server的次数
    ribbon.MaxAutoRetriesNextServer=0
    复制代码

      7.3 重启网关服务,更改一下ms-member的测试接口

    复制代码
    @RequestMapping(value = "/showMember")
        public String showMember(){
            System.out.println("请求到达!!!!");
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "会员服务正常,会员服务是服务消费者,也就是服务调用者,会调用商品服务进行商品购买,减少库存,增加销量;
    
    同时调用积分服务,增加积分";
        }
    复制代码

    可以看到,要访问的接口 休眠10s,而网关配置的超时时间设置为2s

    结合上面的重试次数的配置,请求到达的次数应该是3+1次。

    启动ms-member服务,访问地址:

    http://localhost:8001/v1/ms-member/member/showMember?auth=111

    可以看到,请求总共到达了4次,首次+重试3次

    请求结果可以看到,最终服务被熔断。

      

       7.4 路由重试注意

      

    是否开启路由重试,针对于 查询接口可以使用,但是对于非幂等的新增或更新接口,使用路由重试会出现很大的问题,应该注意
    开启重试在某些情况下是有问题的,比如当压力过大,一个实例停止响应时,路由将流量转到另一个实例,很有可能导致最终所有的实例全被压垮。说到底,断路器的其中一个作用就是防止故障或者压力扩散。用了retry,断路器就只有在该服务的所有实例都无法运作的情况下才能起作用。这种时候,断路器的形式更像是提供一种友好的错误信息,或者假装服务正常运行的假象给使用者。
    
    不用retry,仅使用负载均衡和熔断,就必须考虑到是否能够接受单个服务实例关闭和eureka刷新服务列表之间带来的短时间的熔断。如果可以接受,就无需使用retry。

    =============结束================

    参考:http://www.ityouknow.com/springcloud/2018/01/20/spring-cloud-zuul.html

    参考:https://www.cnblogs.com/cralor/p/9234697.html

  • 相关阅读:
    LeetCode 32. 最长有效括号(Longest Valid Parentheses)
    LeetCode 141. 环形链表(Linked List Cycle)
    LeetCode 160. 相交链表(Intersection of Two Linked Lists)
    LeetCode 112. 路径总和(Path Sum)
    LeetCode 124. 二叉树中的最大路径和(Binary Tree Maximum Path Sum)
    LightGBM新特性总结
    sql service 事务与锁
    C#泛型实例详解
    C# 中的委托和事件(详解)
    C# DateTime日期格式化
  • 原文地址:https://www.cnblogs.com/handsome1013/p/11276162.html
Copyright © 2020-2023  润新知