zoukankan      html  css  js  c++  java
  • spring cloud Zuul + 路由熔断【服务降级】 --- 心得

    1.前言

        刚入门 时,使用 ribbon + hystrix + restTemplate  ,实现了简单的 接口访问 + 客户端负载均衡 + 服务熔断保护 ;

    然后学习了 feign ,整合了  ribbon + hystrix + restTemplate  的功能优点 并实现了上面功能 ;

    上面的是实现服务与服务之间的服务熔断保护。

      如今 ,引入了 zuul ,API网关 ,外部用户统一访问zuul服务器,网关拦截请求 并作验证等操作通过后 路由到 指定的 内部 服务器集群 【zuul默认开启客户端负载均衡,类似ribbon】,

    那么 ,如果 恰好 路由到的指定服务器时刚好宕机或者线程崩溃了怎么办?或者说当我们的后端服务出现异常的时候,我们不希望将异常抛出给最外层,期望服务可以自动进行降级处理。

    这是网关与服务之间的服务熔断保护

      Zuul给我们提供了这样的支持。当某个服务出现异常时,直接返回我们预设的信息。【类似于 hystrix 的熔断处理】

    2.准备一个服务提供者,端口 8001

     目录结构

     pom.xml

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>cen.cloud</groupId>
            <artifactId>cen-mycloud</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>provider-8001</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>provider-8001</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <!--        spring boot web 组件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!--        测试组件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <!--eureka 注册中心依赖包 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
    
            <!-- 修改后立即生效,热部署 -->
            <!-- 热修改后端-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>springloaded</artifactId>
                <version>1.2.4.RELEASE</version>
            </dependency>
            <!-- 热修改前端-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <!--            <optional>true</optional>-->
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    View Code

    application.properties

    # 服务名称
    spring.application.name=provider-8001
    # 端口
    server.port=8001
    
    
    #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址,
    # [#找不到其他服务注册中心地址会报错]
    eureka.client.serviceUrl.defaultZone=http://localhost:7001/eureka/
    #  ,http://localhost:7002/eureka/
    View Code

    controller层

    package com.example.provider8001.controller;
    
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class PRController {
    
        @RequestMapping(value = "/getname",method = RequestMethod.GET)
        public String getname(String name){
            System.out.println("接收名字="+name);
            return "你大爷叫:"+name;
        }
    
    }
    View Code

    启动类

    package com.example.provider8001;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    @SpringBootApplication
    //开启发现服务
    @EnableEurekaClient
    public class Provider8001Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Provider8001Application.class, args);
        }
    
    }
    View Code

     3.准备一个服务消费者,端口 9001

    目录结构

      pom.xml

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>cen.cloud</groupId>
            <artifactId>cen-mycloud</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>consumer-9001</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>consumer-9001</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <!--        spring boot web 组件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!--        测试组件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <!--eureka 注册中心依赖包 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
    
            <!-- 修改后立即生效,热部署 -->
            <!-- 热修改后端-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>springloaded</artifactId>
                <version>1.2.4.RELEASE</version>
            </dependency>
            <!-- 热修改前端-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <!--            <optional>true</optional>-->
            </dependency>
    
    
            <!--feign依赖包-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
            <!--配置中心-客户端依赖包-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-config</artifactId>
            </dependency>
    
    
            <!--健康检测管理中心 ,可刷新配置文件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <!--spring cloud bus,消息总线-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bus-amqp</artifactId>
            </dependency>
    
    
    
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    View Code

    application.properties 为空

    bootstrap.proterties  【配置内容有 消息中间件 rabbitmq 和bus 的配置,如果没有安装rabbitmq则不配置】

    spring.application.name=consumer-9001
    server.port=9001
    #
    # 当前微服务注册到eureka中(消费端),可不写 ,默认为true
    #eureka.client.register-with-eureka=true
    eureka.client.service-url.defaultZone=http://localhost:7001/eureka/
    #
    #配置中心客户端配置
    #获取指定配置文件名称 ,多个则以英文符号 , 隔开,不可有空格
    spring.cloud.config.name=gittest
    #获取配置的策略 , 读取文件:dev开发环境、test测试、pro生产
    spring.cloud.config.profile=dev
    #获取配置文件的分支,默认是master。如果是是本地获取的话,则无用,
    spring.cloud.config.label=master
    #开启配置信息发现
    spring.cloud.config.discovery.enabled=true
    #指定配置中心服务端的service-id,便于扩展为高可用配置集群,不区分大小写
    spring.cloud.config.discovery.serviceId=config-server-6001
    #
    #健康检测管理中心配置
    #springboot 1.5.X 以上默认开通了安全认证,这里可加可不加,不影响
    #management.security.enabled=false
    #springboot 2.x 默认只开启了info、health的访问接口,*代表开启所有访问接口
    management.endpoints.web.exposure.include=*
    
    #
    #
    ## spring cloud bus 刷新配置
    ##rabbitmq 服务所在ip
    #使用 localhost 会出错 ,使用 127.0.0.1 则没问题
    spring.rabbitmq.host=127.0.0.1
    #默认端口 5672
    spring.rabbitmq.port=5672
    #默认账户
    spring.rabbitmq.password=guest
    #默认密码
    spring.rabbitmq.username=guest
    ##
    ##
    ## 开启消息跟踪
    spring.cloud.bus.trace.enabled=true
    #
    #
    #feign开启熔断器必须加这句话,不然无法使用,直接报500状态码
    feign.hystrix.enabled=true
    #设置连接超时时间
    #feign.client.config.default.connect-timeout=10000
    #feign.client.config.default.read-timeout=10000
    View Code

    controller层

    package com.example.consumer9001.controller;
    
    
    import com.example.consumer9001.feignInter.FeignService1;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Date;
    
    @RefreshScope
    @RestController
    public class NameController {
        @Autowired
        private FeignService1 feignService1;
    
        @RequestMapping(value = "/doname", method = RequestMethod.GET)
        public String doname(String name) {
            System.out.println("接收名字=" + name + "==" + new Date());
            return "我是消费者端口9001,微服务处理结果是:" + feignService1.getname(name);
        }
    
    
        @Value("${yourname}")
        private String namestr;
    
        @RequestMapping(value = "/getname", method = RequestMethod.GET)
        public String getConfig() {
    
            String str = "我是消费者端口9001,获取远程配置文件信息:" + namestr + "===" + new Date();
            System.out.println(str);
            return str;
        }
    
    //    http://localhost:9001/getname
    
    }
    View Code

    feign 服务接口

    package com.example.consumer9001.feignInter;
    
    import com.example.consumer9001.feignInter.myFallbackFactory.FeignServuce1FallbackFactory;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    
    //注入远程服务的应用名【不区分大小写】 ,以及熔断回调类
    @FeignClient(name = "provider-8001", fallbackFactory = FeignServuce1FallbackFactory.class )
    public interface FeignService1 {
    
    
    //    对应远程服务具体接口的名称和参数
        @RequestMapping(value = "/getname", method = RequestMethod.GET)
    //    需要添加 @RequestParam ,用于纠正参数映射 ,不然会报错 405 ,
    //    feign.FeignException$MethodNotAllowed: status 405 reading
        public String getname(@RequestParam("name") String name);
    
    
    }
    View Code

    feign 服务降级回路操作

    package com.example.consumer9001.feignInter.myFallbackFactory;
    
    import com.example.consumer9001.feignInter.FeignService1;
    import feign.hystrix.FallbackFactory;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    
    
    /**
     * feign使用断路器【熔断器】 ,当熔断发生后,运行这里的方法。类似于异常抛出
     * 这里主要是处理异常出错的情况(降级/熔断时服务不可用,fallback就会找到这里来)
     */
    @Component  // 不要忘记添加,不要忘记添加,不加则无法使用熔断器
    public class FeignServuce1FallbackFactory implements FallbackFactory<FeignService1> {
        @Override
        public FeignService1 create(Throwable throwable) {
            return new FeignService1() {
                @Override
                public String getname(String name) {
                    //这里写熔断后的具体操作逻辑
    
                    return "输入参数是" + name + ",feign使用了断路器【熔断器】,限制服务处于熔断状态,运行了类似于抛出异常的方法,时间=" + new Date();
                }
            };
        }
    }
    View Code

    feign里的 Ribbon客户端负载均衡策略设置

    package com.example.consumer9001.myconfig;
    
    import com.netflix.loadbalancer.BestAvailableRule;
    import com.netflix.loadbalancer.IRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    ///注解@Configuration千万千万不要忘记添加 ,不然无法使用这个静态配置类
    @Configuration
    public class ConfigBean {
    
        //设置负载均衡策略
        @Bean
        public IRule myRule() {
            //其他看看 https://www.cnblogs.com/htyj/p/10705472.html
            //
    
            //轮询策略,其实里面就是一个计数器
    //        return new RoundRobinRule();
    
            //该策略通过遍历负载均衡器中维护的所有实例,会过滤调故障的实例,并找出并发请求数最小的一个,所以该策略的特征是选择出最空闲的实例
            //如果集群有个服务器挂了,就可以过略的他,防止访问了故障服务器
            return new BestAvailableRule();
    
        }
    
    }
    View Code

    启动类

    package com.example.consumer9001;
    
    import com.example.consumer9001.myconfig.ConfigBean;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    //服务客户端【发现服务】
    @EnableEurekaClient
    //@EnableDiscoveryClient ,也可以使用这个
    //指定feign接口扫描范围 ,也可以不写
    @EnableFeignClients(basePackages = {"com.example.consumer9001.feignInter"})
    //开启客户端负载均衡自定义策略,参数name是该服务器的应用名字 ,configuration设置 策略配置类
    @RibbonClient(name = "consumer-9001" ,configuration = ConfigBean.class)
    public class Consumer9001Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Consumer9001Application.class, args);
        }
    
    }
    View Code

    4.准备一个Zuul 网关 ,端口 5001

    目录结构

     pom.xml

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>cen.cloud</groupId>
            <artifactId>cen-mycloud</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>zuul-server-5001</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>zuul-server-5001</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!--==========================================================================-->
            <!--eureka 注册中心依赖包 -->
            <!--        这是服务中心端的依赖包-->
            <!--        <dependency>-->
            <!--            <groupId>org.springframework.cloud</groupId>-->
            <!--            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>-->
            <!--        </dependency>-->
            <!--        可是服务客户端的依赖包,两个包都有一样的功能-发现服务,但是下面这个包无 服务端的注解-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!--==========================================================================-->
            <!--zuul 网关-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
    
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    View Code

    application.properties 

    spring.application.name=zuul-server-5001
    server.port=5001
    #
    eureka.client.service-url.defaultZone=http://localhost:7001/eureka/
    #全局添加前缀,如 localhost:114/myzuul/test/bb ,用于识别是否需要转发路由操作
    #不可使用 /zuul ,猜测这是保留字
    zuul.prefix=/mzuul
    #//默认是false,这里是全局配置
    #zuul.strip-prefix: //是否将这个代理前缀去掉
    #
    #忽略所有的,表示禁用默认路由,只认我们自己配置的路由.
    #zuul.ignored-services="*"
    #
    #自定义路由设置
    #拦截路径
    zuul.routes.bd.path=/bd/**
    #拦截后访问的指定地址
    zuul.routes.bd.url=https://www.baidu.com/
    #
    ##拦截路径
    #zuul.routes.CONSUMER-9001.path=/CONSUMER-9001/**
    ##拦截后访问的指定地址
    #zuul.routes.CONSUMER-9001.service-id=CONSUMER-9001
    #
    #拦截后访问的指定服务,使用服务名,根据注册中心获取的服务列表映射具体服务ip地址
    #zuul.routes.api-b.service-id=520LOVE
    
    
    #http://localhost:5001/mzuul/CONSUMER-9001/gettname
    
    # 心得 :如果使用 服务列表默认映射的 拦截路径 ,则写在 httpurl 的服务名必须小写 ,即便 远程服务名 有大小写字符 ,
    # 但在请求路径也必须全部改成小写 ,否则报错 404
    View Code

    自定义拦截文件夹 myFilter 的两个过滤文件 ,随笔 有详细讲解   https://www.cnblogs.com/c2g5201314/p/12996687.html

    package com.example.zuulserver5001.myFilter;
    
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    
    //注册bean ,不使用@Component注解则需要去启动类创建一个方法new一个LoginFilter类后
    // 使用 @bean注解注册bean,一样的作用
    @Component
    public class LoginFilter extends ZuulFilter {
    
    
        /**
         * 选择过滤器类型
         */
        @Override
        public String filterType() {
            //一共有下面4种过滤器
    //        public static final String ERROR_TYPE = "error";
    //        public static final String POST_TYPE = "post";
    //        public static final String PRE_TYPE = "pre";
    //        public static final String ROUTE_TYPE = "route";
            return FilterConstants.PRE_TYPE;
        }
    
        /**
         * 通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高
         */
        @Override
        public int filterOrder() {
            return 0;
        }
    
        /**
         * 返回一个`Boolean`值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
         */
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        /**
         * 过滤器的具体业务逻辑
         */
        @Override
        public Object run() throws ZuulException {
            System.out.println("进入zuul拦截-login拦截");
            //获取上下文
            RequestContext ctx = RequestContext.getCurrentContext();
            //获取Request
            HttpServletRequest request = ctx.getRequest();
            //获取请求参数
            String token = request.getParameter("token");
            System.out.println("参数token=" + token);
    //
    //
            if (StringUtils.isBlank(token)) {
                //参数内容为空
                //拦截,拒绝路由
                ctx.setSendZuulResponse(false);
                //返回状态码
                ctx.setResponseStatusCode(401);
                //返回的响应体信息
                try {
                    //不可以直接写中文,前端会显示中文乱码,加上这就解决中文乱码问题
                    //以文本格式显示,字体比较大
                    ctx.getResponse().setContentType("text/html;charset=UTF-8");
                    //以json格式显示,字体比较小
    //                ctx.getResponse().setContentType("application/json;charset=UTF-8");
    //               上一句等同于  ctx.getResponse().setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    //
                    ctx.getResponse().getWriter().write("token is 空的-------401");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            return null;
        }
    }
    View Code
    package com.example.zuulserver5001.myFilter;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.util.logging.Filter;
    
    @Component
    public class LoginCheckFilter extends ZuulFilter {
        //上下文,不可以设为 private
        RequestContext ctx = null;
        //Request
        private HttpServletRequest request = null;
    
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 1;
        }
    
        @Override
        public boolean shouldFilter() {
            System.out.println("进入zuul拦截-loginCheck拦截,判断是否开启该拦截");
            //获取上下文
            ctx = RequestContext.getCurrentContext();
    //        判断上一层拦截是否通过
            if(!ctx.sendZuulResponse()){
                //上一层拦截不通过
                System.out.println(" 上一层拦截不通过,不开启loginCheck拦截");
                //该拦截不需要开启
                return false;
            }
            //上层拦截通过
            //获取Request
            request = ctx.getRequest();
            //获取请求路径
            String urlStr = request.getRequestURI().toString();
            //当访问路径含有/mzuul/bd/则开启该拦截
            return urlStr.contains("/mzuul/bd");
        }
    
        @Override
        public Object run() throws ZuulException {
            System.out.println("运行loginCheck拦截逻辑");
            //获取请求参数
            String token = request.getParameter("token");
            System.out.println("---参数token=" + token);
            if (StringUtils.isBlank(token) | (token != null && !token.equals("kk"))) {
    //            token 是空的 或者 不是 kk
                //拦截
                System.out.println("拦截,拒绝路由请求, token 是空的 或者 不是 kk");
                ctx.setSendZuulResponse(false);
                ctx.setResponseStatusCode(7781);
                try {
                    ctx.getResponse().setContentType("text/html;charset=UTF-8");
                    ctx.getResponse().getWriter().write("请求参数="+token+",当参数是kk才可以通过");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            return null;
        }
    }
    View Code

    路由熔断后的服务降级回路操作

    package com.example.zuulserver5001.myProducerFallback;
    
    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;
    
    //注册bean
    @Component
    public class Consumer9001Fallback implements FallbackProvider {
    
        //测试请求1
    //    http://localhost:5001/mzuul/consumer-9001?token=kk
        //测试请求2
    //    http://localhost:5001/mzuul/consumer-9001/doname?token=kk&name=lili
    
        //指定要处理的远程服务
        @Override
        public String getRoute() {
            //必须是小写,即使 远程服务名的字符有大写,这里也必须换成小写,否则报错,无法执行服务降级操作,【与http网址一样,必须小写】
            return "consumer-9001";
        }
    
        /**
         * 具体退路操作逻辑
         */
        private ClientHttpResponse fallbackResponse(){
            return new ClientHttpResponse() {
                //返回http状态
                @Override
                public HttpStatus getStatusCode() throws IOException {
                    return HttpStatus.OK;
                }
                //返回状态码
                @Override
                public int getRawStatusCode() throws IOException {
                    //200是正常
                    return 200;
                }
                //返回状态内容
                @Override
                public String getStatusText() throws IOException {
                    return "OK";
                }
    
                //目前这里不清楚是干什么的
                @Override
                public void close() {
    
                }
                //返回响应体
                @Override
                public InputStream getBody() throws IOException {
                    //以字节流形式返回
                    return new ByteArrayInputStream("beng--i can do nothing,崩溃了,11223344".getBytes());
                }
    
                //返回响应头
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders httpHeaders = new HttpHeaders();
                    //设置响应数据的编码类型
                    httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
                    return httpHeaders;
                }
            };
        }
    
    
    
        @Override
        public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
            if (cause != null && cause.getCause() != null) {
                String reason = cause.getCause().getMessage();
                System.out.println("异常原因:
    "+reason);
    //            logger.info("Excption {}",reason);
            }
    
            return this.fallbackResponse();
        }
    }
    
    
    /*
    总结:
    (1)zuul使用服务列表的默认映射,那么在网址访问的时候,在路径写远程服务名时必须字符全小写【不论远程服务名字符是否有大写】,否则找不到服务;
    (2)当远程服务异常,zuul调用该服务的熔断/降级操作,在回路操作设置时指定的远程服务名必须字符全小写【不论远程服务名字符是否有大写】,
         否则报错,无法执行该服务降级操作;
    (3)使用 zuul --> 服务消费者 --> 服务提供者  的分布式微服务架构 。
        当提供者出现异常时,消费者对其服务降级,执行回路操作 ;
        但如果是zuul 路由到消费者去调用提供者服务,当提供者出现异常时,则将会执行zuul对消费者的服务降级,执行指定消费者的回路操作,
        【打印原因 java.net.SocketTimeoutException: Read timed out】
        但是过了一段时间后再次访问zuul 路由到消费者去调用该提供者服务,将会返回消费者对提供者执行的回路操作结果。【很是奇怪,原因还不清楚】
        当消费者出现异常时,那么zuul将会执行对消费者的服务降级,执行该消费者的回路操作;
    
    使用 zuul --> 服务消费者 --> 服务提供者  的分布式微服务架构 。
    如果是zuul 路由到消费者去调用提供者服务,当提供者出现异常时,则将会执行zuul对消费者的服务降级,执行指定消费者的回路操作,
    但是过了一段时间后再次访问zuul 路由到消费者去调用该提供者服务,将会返回消费者对提供者执行的回路操作结果。
    【很是奇怪,原因还不清楚】
     */
    View Code

    启动类

    package com.example.zuulserver5001;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    import org.springframework.cloud.netflix.zuul.EnableZuulServer;
    
    @SpringBootApplication
    //开启发现服务
    @EnableEurekaClient
    //开启zuul网关代理
    @EnableZuulProxy
    public class ZuulServer5001Application {
    
        public static void main(String[] args) {
            SpringApplication.run(ZuulServer5001Application.class, args);
        }
    
    }
    View Code

    5.其他准备

    具体配置源码 这里 省略 ,可参考去我的其他随笔

    提前准备一个 端口 7001 的 服务注册中心 ,参考随笔地址   https://www.cnblogs.com/c2g5201314/p/12877948.html

    提前准备一个 端口 6001 的 服务配置中心 ,参考随笔地址  https://www.cnblogs.com/c2g5201314/p/12897753.html

     

    6.测试

     (1)依次启动 的工程端口号 7001 【注册中心】,6001【配置中心】 ,8001【提供者】,9001【消费者】,5001【Zuul网关】

     

     

     (2)测试目的:检测服务消费者能否调用服务提供者的服务

    访问端口  9001 , http://localhost:9001/doname?name=tom

     

     调用成功

    (3)测试目的:检测外部访问Zuul 能否路由到服务消费者 后调用 服务消费者 的服务

    访问端口 5001  , http://localhost:5001/mzuul/consumer-9001/getname?token=gh

     

    调用成功

    (4)测试目的:检测外部访问Zuul 能否路由到服务消费者 后调用 服务提供者 的服务

    访问端口 5001  ,http://localhost:5001/mzuul/consumer-9001/doname?token=kg&name=tom

     调用成功

     (5)测试目的:检测服务提供者崩溃后,消费者能否服务熔断后做服务降级回路操作

    关闭端口8001 的工程 ,

    访问端口  9001 , http://localhost:9001/doname?name=tom

     

     回路操作成功

     (6)测试目的:检测服务消费崩溃后,Zuul网关能否路由熔断后做服务降级回路操作

     重新开启端口8001的工程  ,然后再关闭端口9001的工程 

    访问端口 5001  ,http://localhost:5001/mzuul/consumer-9001/doname?token=kg&name=tom

     

     回路操作成功

    再次访问访问端口 5001  ,http://localhost:5001/mzuul/provider-8001/getname?token=kg&name=tom   ,直接路由的 服务提供者服务 ,正常使用 ,因此回路操作仅局限于指定服务

    (7)测试目的:检测服务提供者崩溃后,Zuul路由到 服务消费者后调用服务提供者到底会返回 服务消费者的服务降级结果 还是执行了 Zuul 网关对服务消费者路由熔断后的服务降级回路操作

     重新开启端口9001的工程  ,然后再关闭端口8001的工程 

    访问端口 5001  ,http://localhost:5001/mzuul/consumer-9001/doname?token=kg&name=tom

     显然 ,服务提供者崩溃后,执行了 Zuul 网关对服务消费者路由熔断后的服务降级回路操作

    查看控制台

     显示异常 java.net.SocketTimeoutException: Read timed out

    可是,万万没想到 ,过了一段时间后 ,

    再次访问端口 5001  ,http://localhost:5001/mzuul/consumer-9001/doname?token=kg&name=tom

     竟然 返回 了 服务消费者对服务提供者的服务降级结果

    查看控制台,并没有打印原因

    奇怪,奇怪,真奇怪 !!!!!!目前还没有找到原因,因为没有老师,这就是自学的弊端!

    查阅了大量资料,出现这个现象的原因是 Zuul 路器熔断的超时时间小于远程服务消费者的feign熔断超时时间 导致的,

    那么在 Zuul 工程 的 application.properties文件加入配置

     完整的源码

    spring.application.name=zuul-server-5001
    server.port=5001
    #
    eureka.client.service-url.defaultZone=http://localhost:7001/eureka/
    #全局添加前缀,如 localhost:114/myzuul/test/bb ,用于识别是否需要转发路由操作
    #不可使用 /zuul ,猜测这是保留字
    zuul.prefix=/mzuul
    #//默认是false,这里是全局配置
    #zuul.strip-prefix: //是否将这个代理前缀去掉
    #
    #忽略所有的,表示禁用默认路由,只认我们自己配置的路由.
    #zuul.ignored-services="*"
    #
    #自定义路由设置
    #拦截路径
    zuul.routes.bd.path=/bd/**
    #拦截后访问的指定地址
    zuul.routes.bd.url=https://www.baidu.com/
    #
    ##拦截路径
    #zuul.routes.CONSUMER-9001.path=/CONSUMER-9001/**
    ##拦截后访问的指定地址
    #zuul.routes.CONSUMER-9001.service-id=CONSUMER-9001
    #
    #拦截后访问的指定服务,使用服务名,根据注册中心获取的服务列表映射具体服务ip地址
    #zuul.routes.api-b.service-id=520LOVE
    
    
    #http://localhost:5001/mzuul/CONSUMER-9001/gettname
    
    # 心得 :如果使用 服务列表默认映射的 拦截路径 ,则写在 httpurl 的服务名必须小写 ,即便 远程服务名 有大小写字符 ,
    # 但在请求路径也必须全部改成小写 ,否则报错 404
    
    #熔断超时时间设置
    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000
    spring.cloud.loadbalancer.retry.enabled=true
    ribbon.ReadTimeout=60000
    ribbon.ConnectTimeout=60000
    View Code

    将所有工程重新运行后,再次执行一次本次测试步骤 ,

    服务提供者崩溃后,直接返回 服务消费者对服务提供者的服务降级结果

    7.总结

    (1)zuul使用服务列表的默认映射,那么在网址访问的时候,在路径写远程服务名时必须字符全小写【不论远程服务名字符是否有大写】,否则找不到服务;

    (2)当远程服务异常,zuul调用该服务的熔断/降级操作,在回路操作设置时指定的远程服务名必须字符全小写【不论远程服务名字符是否有大写】,
    否则报错,无法执行该服务降级操作;

    (3)使用 zuul --> 服务消费者 --> 服务提供者 的分布式微服务架构 。
    当提供者出现异常时,消费者对其服务降级,执行回路操作 ;
    但如果是zuul 路由到消费者去调用提供者服务,当提供者出现异常时,则将会执行zuul对消费者的服务降级,执行指定消费者的回路操作,
    【打印原因 java.net.SocketTimeoutException: Read timed out】
    但是过了一段时间后再次访问zuul 路由到消费者去调用该提供者服务,将会返回消费者对提供者执行的回路操作结果。
      出现这个现象的原因是 Zuul 路器熔断的超时时间小于远程服务消费者的feign熔断超时时间 导致的,修改Zuul的熔断超时时间即可。
    当消费者出现异常时,那么zuul将会执行对消费者的服务降级,执行该消费者的回路操作。

    完整的项目我放在了GitHub仓库 ,在分支 branch5-31

    https://github.com/cen-xi/test/tree/branch5-31

  • 相关阅读:
    漫画 | 一台Linux服务器最多能支撑多少个TCP连接?
    frida hook pthread_create
    mac搜索so中的字符串
    字节字符串和数字互转
    twsited使用延迟项,并发量限制
    常见汇编指令和EFLAGS寄存器对应位的含义
    scrapy设置cookie的三种方式
    G1GC 读书笔记
    SLF4J 适配图
    支付宝和微信提现有办法免费了!
  • 原文地址:https://www.cnblogs.com/c2g5201314/p/12993310.html
Copyright © 2011-2022 走看看