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 的文章。

  

  🤗 您的點贊轉發是對我最大的支持。

  📢 掃碼關注 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕松噢 ~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM