SpringCloud zuul 網關限流分析


最近項目中 spring cloud zuul 運用到限流功能,打算配置一下就直接使用,不過在壓測與調優過程中遇到一些沒有預測到的問題,附上排查與解析結果

 

yml、pom配置

強烈推薦,按最新github上的文檔配,可以避免搜到一些已經廢棄不用的配置方式!

https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit

我的一些配置,可以直接套用:

zuul:
  routes:
  #路由、重試等zuul其他配置省略
  #限流
  ratelimit:
    enabled: true # 開啟限流功能
    behind-proxy: true # 開啟則限流與業務訪問是異步的,相當於rateLimitFilter先放過;默認是false
    repository: REDIS # 可選REDIS、CONSUL、JPA等,老版本還有本地內存可選
    policy-list:
      myProject1:
        - limit: 5 # 這種配置方式相當於:10分鍾內允許5個請求訪問/api/test/info接口
          refresh-interval: 10
          type:
            - url_pattern=/api/test/info
      myProject2:
        - limit: 3000
          refresh-interval: 1 # 更常見的配置是這種,一秒允許3k個,相當於配qps限制
          type:
            - url_pattern=/api/test2/info
- limit: 300 refresh-interval: 1 # 如果同一個服務有多個需要限流的url,可以這樣 type: - url_pattern=/api/test2/info2

pom需要:

<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>${latest-version}</version>
</dependency>

如果 repository 選擇用 REDIS,還需要:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

啟動,檢查限流功能生效,並且不影響不限流的其他接口!至此解決了限流有無的問題!

返回處理

我們可能需要對觸發限流的情況做監控、報警等,需要識別由限流導致的異常返回

  1. 推薦使用默認的 RateLimiterErrorHandler,直接寫自己需要的處理就行
  2. zuul觸發限流后會拋出 http 429,可以針對這個錯誤碼對response包裝
  3. 也可以在全局異常處理中,判斷出現的異常是否是 RateLimitExceededException

其他配置方式 

目前項目中只用到了對特定url的qps限流,zuul ratelimit 還提供對user、http method、url正則等

性能分析

限流配置之前,單實例壓測,qps大概能到2500;配個2300的限流,開開心心啟動服務,啟動壓測!

 

WTF!限流對性能的影響已經超過了限流配置本身。。一定是我哪里不對TAT 

開始排查問題,查實現原理

zuul 限流的入口是zuul的 RateLimitPreFilter

其中 rateLimitKeyGenerator.key 所生成的redis key較長,規則為 前綴(默認為springBoot項目名) + : + zuul項目名 + : + matcher(和限流策略有關,這里是URL_PATTERN) + : + matcher(再來一遍)

如果項目名和url較長,可能出現key例如:my-test-project-gateway:my-sub-project:/api/test2/info2:/api/test2/info2

不過監控看redis暫不是短板,繼續查

 

限流的實現,通過 rateLimiter.consume 方法

繼續往下看,calcRemainingLimit 方法,內部調用了calcRemaining 方法:

原理不難,利用redis incr命令,每次計算當前過期窗口內還剩幾次,來決定是否限流,安全又高效

 

但是,上一張圖 rateLimiter.consume 方法增加了 synchornized,懷疑是這個原因

其中 redisTemplate.opsForValue().increment(key, usage) 已經沒有並發問題了,這里感覺不用再 synchornized +_+*

 

發現也有同僚遇到了這個問題,建議是重寫這個類,去掉synchornized

https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit/issues/96

討論中有人主張去掉synchornized,有人主張保留

決定還是要去掉synchornized,壓測

完美!!!

再驗證一下對無限流的接口是否無影響,確保可用

其他網關限流方式

目前剛開始接觸,也還在探索中,可以一起討論下

1. spring cloud gateway:

  • 也是成熟的技術,並且大部分文章分析 Finchley版本的 gateway比 zuul 1.x系列的性能和功能整體要好
  • 目前 spring cloud 沒集成 Zuul 2.x,雖然zuul 2.x使用了異步無阻塞式的 API,性能改善明顯
  • 實現思想很簡潔,令牌桶,只有50行lua,其中有4次redis調用
  • 可以通過monitor命令看出:
request_rate_limiter.lua
測試配置為:
redis-rate-limiter.replenishRate: 500 允許每秒500個請求
redis-rate-limiter.burstCapacity: 5000 令牌桶容量5000
1. 查詢當前桶里剩余令牌數
1590493327.354684
[0 lua] "get" "request_rate_limiter.{/app/test/info}.tokens"
2. 查詢上次取牌的時間
注意:通過當前時間,和上次取牌的時間差,即可在lua中計算出這段時間內新補充進桶里的令牌數,不用每秒真正補充進桶 1590493327.354696 [0 lua] "get" "request_rate_limiter.{/app/test/info}.timestamp"
3. 把這次取的1個令牌,和這段時間內需要重新補充進桶的令牌整合,更新最新令牌數
注意:超時時間為 (brustCapacity/replenishRate)*2,感覺不乘2也行,只要保證超時時間 >= 桶重新裝滿的時間就夠了,乘2是否完全是為了保險? 1590493327.354712 [0 lua] "setex" "request_rate_limiter.{/app/test/info}.tokens" "20" "4999"
4. 更新最新取牌時間 1590493327.354727 [0 lua] "setex" "request_rate_limiter.{/app/test/info}.timestamp" "20" "1590493327"

2. 自己用filter+redis、guava rate limiter等實現

可以做單機緩存、自己定制規則

總結

目前的測試是單機壓測,集群下壓測或許還會在別的地方遇到瓶頸

單實例配置、zuul進程數和其他配置、redis集群性能、業務代碼,都有提升的空間

還需要繼續排查與優化


免責聲明!

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



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