Spring Cloud 系列之 Netflix Zuul 服务网关(三)

本篇文章为系列文章,未读前几集的同学请猛戳这里:

本篇文章讲解 Zuul 和 Hystrix 的无缝结合,实现网关监控、网关熔断、网关限流、网关调优。

  

Zuul 和 Hystrix 无缝结合

  

  在 Spring Cloud 中,Zuul 启动器中包含了 Hystrix 相关依赖,在 Zuul 网关工程中,默认是提供了 Hystrix Dashboard 服务监控数据的(hystrix.stream),但是不会提供监控面板的界面展示。在 Spring Cloud 中,Zuul 和 Hystrix 是无缝结合的,我们可以非常方便的实现网关容错处理。

  关于 Hystrix 服务监控更多内容请猛戳:Spring Cloud 系列之 Netflix Hystrix 服务监控

  

网关监控

  

  Zuul 的依赖中包含了 Hystrix 的相关 jar 包,所以我们不需要在项目中额外添加 Hystrix 的依赖。

  但是需要开启数据监控的项目中要添加 dashboard 依赖。

<!-- spring cloud netflix hystrix dashboard 依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

  

配置文件

  

  在配置文件中开启 hystrix.stream 端点。

# 度量指标监控与健康检查
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

  

启动类

  

  在需要开启数据监控的项目启动类中添加 @EnableHystrixDashboard 注解。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
// 开启 Zuul 注解
@EnableZuulProxy
// 开启数据监控注解
@EnableHystrixDashboard
public class ZuulServerApplication {

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

}

  

访问并查看数据

  

  访问:http://localhost:9000/hystrix 监控中心界面如下:

  

  请求多次:http://localhost:9000/product-service/product/1?token=abc123 结果如下:

  

网关熔断

  

  在 Edgware 版本之前,Zuul 提供了接口 ZuulFallbackProvider 用于实现 fallback 处理。从 Edgware 版本开始,Zuul 提供了接口 FallbackProvider 来提供 fallback 处理。

  Zuul 的 fallback 容错处理逻辑,只针对 timeout 异常处理,当请求被 Zuul 路由后,只要服务有返回(包括异常),都不会触发 Zuul 的 fallback 容错逻辑。

因为对于Zuul网关来说,做请求路由分发的时候,结果由远程服务运算。远程服务反馈了异常信息,Zuul 网关不会处理异常,因为无法确定这个错误是否是应用程序真实想要反馈给客户端的。

  

代码示例

  

  ProductProviderFallback.java

package com.example.fallback;

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;
import java.nio.charset.Charset;

/**
 * 对商品服务做服务容错处理
 */
@Component
public class ProductProviderFallback implements FallbackProvider {

    /**
     * return - 返回 fallback 处理哪一个服务。返回的是服务的名称。
     * 推荐 - 为指定的服务定义特性化的 fallback 逻辑。
     * 推荐 - 提供一个处理所有服务的 fallback 逻辑。
     * 好处 - 某个服务发生超时,那么指定的 fallback 逻辑执行。如果有新服务上线,未提供 fallback 逻辑,有一个通用的。
     */
    @Override
    public String getRoute() {
        return "product-service";
    }

    /**
     * 对商品服务做服务容错处理
     *
     * @param route 容错服务名称
     * @param cause 服务异常信息
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            /**
             * 设置响应的头信息
             * @return
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders header = new HttpHeaders();
                header.setContentType(new MediaType("application", "json", Charset.forName("utf-8")));
                return header;
            }

            /**
             * 设置响应体
             * Zuul 会将本方法返回的输入流数据读取,并通过 HttpServletResponse 的输出流输出到客户端。
             * @return
             */
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("{\"message\":\"商品服务不可用,请稍后再试。\"}".getBytes());
            }

            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 HttpStatus
             * @return
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }

            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 int
             * @return
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 String
             * @return
             */
            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            /**
             * 回收资源方法
             * 用于回收当前 fallback 逻辑开启的资源对象。
             */
            @Override
            public void close() {
            }
        };
    }

}

  

访问

  

  关闭商品服务,访问:http://localhost:9000/product-service/product/1?token=abc123 结果如下:

  

网关限流

  

  顾名思义,限流就是限制流量,就像你宽带包有 1 个 G 的流量,用完了就没了。通过限流,我们可以很好地控制系统的 QPS,从而达到保护系统的目的。Zuul 网关组件也提供了限流保护。当请求并发达到阀值,自动触发限流保护,返回错误结果。只要提供 error 错误处理机制即可。

  

为什么需要限流

  

  比如 Web 服务、对外 API,这种类型的服务有以下几种可能导致机器被拖垮:

  • 用户增长过快(好事)
  • 因为某个热点事件(微博热搜)
  • 竞争对象爬虫
  • 恶意的请求

  这些情况都是无法预知的,不知道什么时候会有 10 倍甚至 20 倍的流量打进来,如果真碰上这种情况,扩容是根本来不及的。

  

  

  从上图可以看出,对内而言:上游的 A、B 服务直接依赖了下游的基础服务 C,对于 A,B 服务都依赖的基础服务 C 这种场景,服务 A 和 B 其实处于某种竞争关系,如果服务 A 的并发阈值设置过大,当流量高峰期来临,有可能直接拖垮基础服务 C 并影响服务 B,即雪崩效应。

  

限流算法

  

  常见的限流算法有:

  • 计数器算法
  • 漏桶(Leaky Bucket)算法
  • 令牌桶(Token Bucket)算法

  

计数器算法

  

  点击链接观看:计数器算法视频(获取更多请关注公众号「哈喽沃德先生」)

  

  计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于 A 接口来说,我们 1 分钟的访问次数不能超过 100 个。那么我们可以这么做:在一开始的时候,我们可以设置一个计数器 counter,每当一个请求过来的时候,counter 就加 1,如果 counter 的值大于 100 并且该请求与第一个请求的间隔时间还在 1 分钟之内,触发限流;如果该请求与第一个请求的间隔时间大于 1 分钟,重置 counter 重新计数,具体算法的示意图如下:

  

  这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题,我们看下图:

  从上图中我们可以看到,假设有一个恶意用户,他在 0:59 时,瞬间发送了 100 个请求,并且 1:00 又瞬间发送了 100 个请求,那么其实这个用户在 1 秒里面,瞬间发送了 200 个请求。我们刚才规定的是 1 分钟最多 100 个请求,也就是每秒钟最多 1.7 个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

  

  还有资料浪费的问题存在,我们的预期想法是希望 100 个请求可以均匀分散在这一分钟内,假设 30s 以内我们就请求上限了,那么剩余的半分钟服务器就会处于闲置状态,比如下图:

  

漏桶算法

  

  点击链接观看:漏桶算法视频(获取更多请关注公众号「哈喽沃德先生」)

  

  漏桶算法其实也很简单,可以粗略的认为就是注水漏水的过程,往桶中以任意速率流入水,以一定速率流出水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。

  漏桶算法是使用队列机制实现的。

  

  漏桶算法主要用途在于保护它人(服务),假设入水量很大,而出水量较慢,则会造成网关的资源堆积可能导致网关瘫痪。而目标服务可能是可以处理大量请求的,但是漏桶算法出水量缓慢反而造成服务那边的资源浪费。

  漏桶算法无法应对突发调用。不管上面流量多大,下面流出的速度始终保持不变。因为处理的速度是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就会丢弃。

  

令牌桶算法

  

  点击链接观看:令牌桶算法视频(获取更多请关注公众号「哈喽沃德先生」)

  

  令牌桶算法是对漏桶算法的一种改进,漏桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌。

场景大概是这样的:桶中一直有大量的可用令牌,这时进来的请求可以直接拿到令牌执行,比如设置 QPS 为 100/s,那么限流器初始化完成一秒后,桶中就已经有 100 个令牌了,等服务启动完成对外提供服务时,该限流器可以抵挡瞬时的 100 个请求。当桶中没有令牌时,请求会进行等待,最后相当于以一定的速率执行。

  

  Zuul 内部使用 Ratelimit 组件实现限流,使用的就是该算法,大概描述如下:

  • 所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
  • 根据限流大小,设置按照一定的速率往桶里添加令牌;
  • 桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
  • 请求到达后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
  • 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流。

  漏桶算法主要用途在于保护它人,而令牌桶算法主要目的在于保护自己,将请求压力交由目标服务处理。假设突然进来很多请求,只要拿到令牌这些请求会瞬时被处理调用目标服务。

  

添加依赖

  

  Zuul 的限流保护需要额外依赖 spring-cloud-zuul-ratelimit 组件,限流数据采用 Redis 存储所以还要添加 Redis 组件。

  RateLimit 官网文档:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit

<!-- spring cloud zuul ratelimit 依赖 -->
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.3.0.RELEASE</version>
</dependency>
<!-- spring boot data redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 对象池依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

  

全局限流配置

  

  使用全局限流配置,Zuul 会对代理的所有服务提供限流保护。

server:
  port: 9000 # 端口

spring:
  application:
    name: zuul-server # 应用名称
  # redis 缓存
  redis:
    timeout: 10000        # 连接超时时间
    host: 192.168.10.101  # Redis服务器地址
    port: 6379            # Redis服务器端口
    password: root        # Redis服务器密码
    database: 0           # 选择哪个库,默认0库
    lettuce:
      pool:
        max-active: 1024  # 最大连接数,默认 8
        max-wait: 10000   # 最大连接阻塞等待时间,单位毫秒,默认 -1
        max-idle: 200     # 最大空闲连接,默认 8
        min-idle: 5       # 最小空闲连接,默认 0

# 配置 Eureka Server 注册中心
eureka:
  instance:
    prefer-ip-address: true       # 是否使用 ip 地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # 设置服务注册中心地址
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

zuul:
  # 服务限流
  ratelimit:
    # 开启限流保护
    enabled: true
    # 限流数据存储方式
    repository: REDIS
    # default-policy-list 默认配置,全局生效
    default-policy-list:
      - limit: 3
        refresh-interval: 60    # 60s 内请求超过 3 次,服务端就抛出异常,60s 后可以恢复正常请求
        type:
          - origin
          - url
          - user

  

  Zuul-RateLimiter 基本配置项:

配置项 可选值 说明
enabled true/false 是否启用限流
repository REDIS:基于 Redis,使用时必须引入 Redis 相关依赖
CONSUL:基于 Consul
JPA:基于 SpringDataJPA,需要用到数据库
使用 Java 编写的基于令牌桶算法的限流库:
BUCKET4J_JCACHE
BUCKET4J_HAZELCAST
BUCKET4J_IGNITE
BUCKET4J_INFINISPAN
限流数据的存储方式,无默认值必填项
key-prefix String 限流 key 前缀
default-policy-list List of Policy 默认策略
policy-list Map of Lists of Policy 自定义策略
post-filter-order - postFilter 过滤顺序
pre-filter-order - preFilter 过滤顺序

Bucket4j 实现需要相关的 bean @Qualifier("RateLimit"):

  • JCache - javax.cache.Cache
  • Hazelcast - com.hazelcast.core.IMap
  • Ignite - org.apache.ignite.IgniteCache
  • Infinispan - org.infinispan.functional.ReadWriteMap

  

  Policy 限流策略配置项说明:

说明
limit 单位时间内请求次数限制
quota 单位时间内累计请求时间限制(秒),非必要参数
refresh-interval 单位时间(秒),默认 60 秒
type 限流方式:
ORIGIN:访问 IP 限流
URL:访问 URL 限流
USER:特定用户或用户组限流(比如:非会员用户限制每分钟只允许下载一个文件)
URL_PATTERN
ROLE
HTTP_METHOD

  

访问

  

  访问:http://localhost:9000/product-service/product/1?token=abc123 控制台结果如下:

ErrorFilter...com.netflix.zuul.exception.ZuulException: 429 TOO_MANY_REQUESTS

  

  查看 Redis

127.0.0.1:6379> keys *
1) "zuul-server:product-service:0:0:0:0:0:0:0:1:/product/1:anonymous"
2) "zuul-server:product-service:0:0:0:0:0:0:0:1:/product/1:anonymous-quota"

  

局部限流配置

  

  使用局部限流配置,Zuul 仅针对配置的服务提供限流保护。

zuul:
  # 服务限流
  ratelimit:
    # 开启限流保护
    enabled: true
    # 限流数据存储方式
    repository: REDIS
    # policy-list 自定义配置,局部生效
    policy-list:
      # 指定需要被限流的服务名称
      order-service:
        - limit: 5
          refresh-interval: 60  # 60s 内请求超过 5 次,服务端就抛出异常,60s 后可以恢复正常请求
          type:
            - origin
            - url
            - user

  

  访问:http://localhost:9000/order-service/order/1?token=abc123 控制台结果如下:

ErrorFilter...com.netflix.zuul.exception.ZuulException: 429 TOO_MANY_REQUESTS

  

  查看 Redis

127.0.0.1:6379> keys *
1) "zuul-server:order-service:0:0:0:0:0:0:0:1:/order/1:anonymous-quota"
2) "zuul-server:order-service:0:0:0:0:0:0:0:1:/order/1:anonymous"

  

自定义限流策略

  

  如果希望自己控制限流策略,可以通过自定义 RateLimitKeyGenerator 的实现来增加自己的策略逻辑。

  修改商品服务控制层代码如下,添加 /product/single

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * 根据主键查询商品
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Product selectProductById(@PathVariable("id") Integer id) {
        return productService.selectProductById(id);
    }

    /**
     * 根据主键查询商品
     *
     * @param id
     * @return
     */
    @GetMapping("/single")
    public Product selectProductSingle(Integer id) {
        return productService.selectProductById(id);
    }

}

  

  自定义限流策略类。

package com.example.ratelimit;

import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.DefaultRateLimitKeyGenerator;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * 自定义限流策略
 */
@Component
public class RateLimitKeyGenerator extends DefaultRateLimitKeyGenerator {

    public RateLimitKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) {
        super(properties, rateLimitUtils);
    }

    /**
     * 限流逻辑
     *
     * @param request
     * @param route
     * @param policy
     * @return
     */
    @Override
    public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
        // 对请求参数中相同的 id 值进行限流
        return super.key(request, route, policy) + ":" + request.getParameter("id");
    }

}

  

  多次访问:http://localhost:9000/api/product-service/product/single?token=abc123&id=1 被限流后,马上更换 id=2 重新访问发现服务任然可用,再继续多次访问,发现更换过的 id=2 也被限流了。Redis 信息如下:

127.0.0.1:6379> keys *
1) "zuul-server:product-service:0:0:0:0:0:0:0:1:/product/single:anonymous:1"
2) "zuul-server:product-service:0:0:0:0:0:0:0:1:/product/single:anonymous:2"

  

错误处理

  

  配置 error 类型的网关过滤器进行处理即可。修改之前的 ErrorFilter 让其变的通用。

package com.example.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 异常过滤器
 */
@Component
public class ErrorFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext rc = RequestContext.getCurrentContext();
        ZuulException exception = this.findZuulException(rc.getThrowable());
        logger.error("ErrorFilter..." + exception.errorCause, exception);

        HttpStatus httpStatus = null;
        if (429 == exception.nStatusCode)
            httpStatus = HttpStatus.TOO_MANY_REQUESTS;

        if (500 == exception.nStatusCode)
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

        // 响应状态码
        rc.setResponseStatusCode(httpStatus.value());
        // 响应类型
        rc.getResponse().setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = rc.getResponse().getWriter();
            // 响应内容
            writer.print("{\"message\":\"" + httpStatus.getReasonPhrase() + "\"}");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != writer)
                writer.close();
        }
        return null;
    }

    private ZuulException findZuulException(Throwable throwable) {
        if (throwable.getCause() instanceof ZuulRuntimeException)
            return (ZuulException) throwable.getCause().getCause();

        if (throwable.getCause() instanceof ZuulException)
            return (ZuulException) throwable.getCause();

        if (throwable instanceof ZuulException)
            return (ZuulException) throwable;
        return new ZuulException(throwable, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
    }

}

  还有一种方法是实现 org.springframework.boot.web.servlet.error.ErrorController 重写 getErrorPath() 本文中不做重点讲解。

  

  多次访问:http://localhost:9000/product-service/product/1?token=abc123 结果如下:

  

网关调优

  

  使用 Zuul 的 Spring Cloud 微服务结构图:

  从上图中可以看出。整体请求逻辑还是比较复杂的,在没有 Zuul 网关的情况下,client 请求 service 的时候,也有请求超时的可能。那么当增加了 Zuul 网关的时候,请求超时的可能就更明显了。

  当请求通过 Zuul 网关路由到服务,并等待服务返回响应,这个过程中 Zuul 也有超时控制。Zuul 的底层使用的是 Hystrix + Ribbon 来实现请求路由。

  

  

  Zuul 中的 Hystrix 内部使用线程池隔离机制提供请求路由实现,其默认的超时时长为 1000 毫秒。Ribbon 底层默认超时时长为 5000 毫秒。如果 Hystrix 超时,直接返回超时异常。如果 Ribbon 超时,同时 Hystrix 未超时,Ribbon 会自动进行服务集群轮询重试,直到 Hystrix 超时为止。如果 Hystrix 超时时长小于 Ribbon 超时时长,Ribbon 不会进行服务集群轮询重试。

  

配置文件

  

  Zuul 中可配置的超时时长有两个位置:Hystrix 和 Ribbon。具体配置如下:

zuul:
  # 开启 Zuul 网关重试
  retryable: true

# Hystrix 超时时间设置
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 10000  # 线程池隔离,默认超时时间 1000ms

# Ribbon 超时时间设置:建议设置小于 Hystrix
ribbon:
  ConnectTimeout: 5000                    # 请求连接的超时时间: 默认超时时间 1000ms
  ReadTimeout: 5000                       # 请求处理的超时时间: 默认超时时间 1000ms
  # 重试次数
  MaxAutoRetries: 1                       # MaxAutoRetries 表示访问服务集群下原节点(同路径访问)
  MaxAutoRetriesNextServer: 1             # MaxAutoRetriesNextServer表示访问服务集群下其余节点(换台服务器)
  # Ribbon 开启重试
  OkToRetryOnAllOperations: true

  

添加依赖

  

  Spring Cloud Netflix Zuul 网关重试机制需要使用 spring-retry 组件。

<!-- spring retry 依赖 -->
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

  

启动类

  

  启动类需要开启 @EnableRetry 重试注解。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication
// 开启 Zuul 注解
@EnableZuulProxy
// 开启 EurekaClient 注解,目前版本如果配置了 Eureka 注册中心,默认会开启该注解
//@EnableEurekaClient
// 开启数据监控注解
@EnableHystrixDashboard
// 开启重试注解
@EnableRetry
public class ZuulServerApplication {

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

}

  

模拟超时

  

  商品服务模拟超时。

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * 根据主键查询商品
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Product selectProductById(@PathVariable("id") Integer id) {
        // 模拟超时
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return productService.selectProductById(id);
    }

}

  

访问

  

  配置前访问:http://localhost:9000/product-service/product/1?token=abc123 结果如下(触发了网关服务降级):

  

  配置后访问:http://localhost:9000/product-service/product/1?token=abc123 结果如下:

  思考:根据目前的配置如果访问订单服务会怎么样?

下一篇我们讲解 Zuul 和 Sentinel 整合,实现网关限流和容错以及高可用网关环境搭建,记得关注噢~

  本文采用 知识共享「署名-非商业性使用-禁止演绎 4.0 国际」许可协议

  大家可以通过 分类 查看更多关于 Spring Cloud 的文章。

  

  🤗 您的点赞转发是对我最大的支持。

  📢 扫码关注 哈喽沃德先生「文档 + 视频」每篇文章都配有专门视频讲解,学习更轻松噢 ~

posted @ 2020-04-01 07:56  哈喽沃德先生  阅读(991)  评论(0编辑  收藏  举报