本篇講網關上的限流
用開源項目spring-cloud-zuul-ratelimit 做網關上的限流 (項目github:https://github.com/marcosbarbero/ )
1,在網關項目里,引入限流組件的maven依賴:
2,在網關項目yml配置里,配限流相關配置
github也有相關配置說明:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
限流框架限流需要存一些信息,可以存在數據庫里,也可以存在redis里,這里就用redis做實驗,而且我看該組件的限流存儲枚舉類里,如果使用數據庫保存限流信息,只能用JPA,不能用mybatis(可能歪果仁更喜歡JPA):
public enum RateLimitRepository { REDIS, CONSUL, JPA, BUCKET4J_JCACHE, BUCKET4J_HAZELCAST, BUCKET4J_IGNITE, BUCKET4J_INFINISPAN, }
限流yml配置:
限流的信息,在redis是有有效期的,過了有效期會自動清除,所以要想在redis看到限流的相關信息,這里時間窗口就設置為 10 秒(剛開始我設置的是1秒,redis死活看不到限流信息,浪費了十幾分鍾時間)
zuul: routes: #路由的配置是個Map,可以配置多個 token: #token結尾的請求,都轉發到http://localhost:9090認證服務器地址 url: http://localhost:9090 order: #order結尾的請求,都轉發到http://localhost:9060 頂單服務 url: http://localhost:9060 sensitive-headers: null #設置敏感頭設置為空,Authorization等請求頭的請求,都往后轉發 ratelimit: key-prefix: rate enabled: true repository: REDIS default-policy-list: # ########### 如下的配置就是說:每1秒內不能超過2個請求,2個請求時間加起來不能超過1秒(quota)############ - limit: 2 #optional - request number limit per refresh interval window quota: 1 #optional - request time limit per refresh interval window (in seconds) refresh-interval: 10 #時間窗口 (in seconds) type: ##根據什么控制流量,可以組合使用,如url、httpmethod組合,就會把 /orders的get和post請求分開處理 - url - http_method #- user #根據用戶控制需要Security支持,(一般不用) #- origin #根據客戶端的ip控制
3,實驗
1,分別啟動 訂單 、認證、網關 三個服務
2,從網關獲取一個token:
OAuth2 password模式參數
網關client的username,password
3,拿token訪問網關創建訂單
手速點的快點,Http就會返回 429 狀態碼,表示過多的請求。
看redis,已經有限流的信息了:
生成的限流的key是: rate:order:/orders:POST rate是配置的限流信息的前綴,order是zuul轉發的規則,/orders:POST 是配置的限流類型 url 和http_method的組合
當下一個請求過來的時候就會計算key,然后根據key去redis找,看當前的key已經過了多少個請求了,來判斷這次請求能不能過。
大部分情況下,根據user 、origin、 url 、http_method已經滿足需求了。有些特殊的場景,需要根據傳過來的參數進行限流,比如有兩種優惠券A、B,優惠券A業務簡單,每秒能處理100個請求,優惠券B復雜,每秒能處理10個請求,此時自帶的限流規則就不能滿足需求了。限流歸根揭底是根據key來限流的,所以此時就要自定義key的生成規則。
自定義key的生成規則:
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; /** * 自定義限流key生成規則,自定義限流規則 */ @Component public class MyKeyGen extends DefaultRateLimitKeyGenerator { public MyKeyGen(RateLimitProperties properties, RateLimitUtils rateLimitUtils){ super(properties,rateLimitUtils); } @Override public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) { //可以從route拿出路由信息,自定義key生成規則:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit#usage return super.key(request, route, policy); } }
重寫錯誤處理:
package com.nb.security.filter; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.repository.DefaultRateLimiterErrorHandler; import org.springframework.stereotype.Component; /** * 限流錯誤自定義處理 */ @Component public class MyRateLimitErrorHandler extends DefaultRateLimiterErrorHandler { //限流數據存時候報錯了的處理,一般不覆蓋 @Override public void handleSaveError(String key, Exception e) { super.handleSaveError(key, e); } //限流取數據報錯的處理,一般不覆蓋 @Override public void handleFetchError(String key, Exception e) { super.handleFetchError(key, e); } //限流錯誤處理,記日志等 @Override public void handleError(String msg, Exception e) { super.handleError(msg, e); } }
都在網關上做限流是有問題的
1,耦合
如上所述的根據不同的優惠券進行的限流,如果優惠券又多了種類型,就要重寫網關的限流的代碼,重寫部署網關,這就是耦合,是不行的。服務和服務之間,以及服務和基礎組件之間,一定要解耦。微服務場景下解耦是價值最大的事。一旦兩個服務耦合在一塊,一個變了,另一個也要跟着變。
2,限流的數量的問題
網關只能處理從整個微服務外邊進來的請求,並不處理微服務之間的調用。如,有A B兩個微服務,A還調用了B服務,A、B服務每秒處理的請求最大都是100個,網關限流 A 100請求/秒 ,B 100請求/秒。此時有100個A請求到網關,100個B請求也到了網關,都通過了網關,而此時,100個到A的請求又請求了100次B,導致B服務不可用!
所以:在網關上不要做細粒度的限流,沒有用,因為很多服務之間的調用,都不走網關。網關層面只根據硬件設備的處理能力做一些限流,如服務的節點用的是tomcat,根據每個tomcat的資源配置,計算能處理的多少並發請求,根據這個去限流。具體的方法,跟業務邏輯的耦合,都不要發生在網關上的限流邏輯里。這就是上邊配置里說的,限流類型 user最好不在網關上用。
本篇github : https://github.com/lhy1234/springcloud-security/tree/chapt-4-10-ratelimit 如果對你有一點幫助,點個小星星吧!謝謝
歡迎關注個人公眾號一起交流學習: