目錄
Failed to convert property value of type 'java.lang.String' to required type 'java.time.Duration'
問題:
前端頁面存在大量重複點擊行爲,對系統服務造成無用請求開支.
同時也可以防止DOS,故此可以通過限流方式進行控制
有時必須控制網絡接收到的流量的速率,以防止DoS之類的攻擊或將單個用戶/源可以向某個終結點發出的請求數量限制到一定範圍。
在微服務架構中,API網關是整個應用程序的入口點,並且在這一層具有速率限制非常適合。
快捷接入
依賴
- 引入ratelimit jar包
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
寫下該博客時,zuul-rateLimit已更新到 版本.
但該版本對SpringBoot低版本不太友好,必須更新SpringBoot v2.0.1.RELEASE版本後方才能正常運行,詳看【踩坑篇 3】
本次項目中使用Redis進行計數器存儲,更多數據存儲【
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置
# ReteLimit 開關,如果已引入對應jar包,該配置必須爲true.詳看【坑1】
zuul.ratelimit.enabled = true
# 自定義關鍵字前綴
zuul.ratelimit.key-prefix = GTW-MS
# 數據存儲方式
zuul.ratelimit.repository = REDIS
# response-heads中是否追加RateLimit信息
zuul.ratelimit.add-response-headers = true
# RateLimitPreFilter order 優先級,0-最高
#zuul.ratelimit.post-filter-order = 10
# RateLimitPostFilter order 優先級,0-最高
#zuul.ratelimit.pre-filter-order = 0
# 默認限流策略(全局),可解讀爲:
# 全局策略1,根據url進行限流,1秒允許調用10次,且這10次調用總耗時需小於20s
# 如果想設置更多的限流策略,新增default-policy-list數組元素即可.
zuul.ratelimit.default-policy-list[0].limit = 10
zuul.ratelimit.default-policy-list[0].quota = 20
zuul.ratelimit.default-policy-list[0].refresh-interval = 1
zuul.ratelimit.default-policy-list[0].type[0] = url
# 微服務MSG-MS網關登記配置,msg爲路徑自定義命名.
# 指定網關對外映射路徑
zuul.routes.msg.path=/msg/**
# 前綴
zuul.routes.msg.stripPrefix=true
# 指向微服務
zuul.routes.msg.serviceId=MSG-MS
# 爲指定msg定製策略,可解讀:
# /msg/* 路徑請求, 3秒內允許訪問2次,且2次調用耗時需小於4s
# 或 30秒內允許訪問10次,且10次調用耗時小於30s
zuul.ratelimit.policy-list.msg[0].limit = 2
zuul.ratelimit.policy-list.msg[0].quota = 4
zuul.ratelimit.policy-list.msg[0].refresh-interval = 3
zuul.ratelimit.policy-list.msg[0].type[0] = url
zuul.ratelimit.policy-list.msg[1].limit = 10
zuul.ratelimit.policy-list.msg[1].quota = 30
zuul.ratelimit.policy-list.msg[1].refresh-interval = 30
zuul.ratelimit.policy-list.msg[1].type[0] = url
代碼:
/**
* 重寫Key定義
*
* @Date 2020/5/19
* @Time 18:16
*/
@Configuration
@Slf4j
public class RateLimitConfig {
/**
* 爲了針對地址+用戶進行限流,重新定義RateLimitKey
* 配置中已定義 ratelimit.xxx.type=url,並且在原先key中追加消息頭中的token字段加以實現
* @param properties
* @param rateLimitUtils
* @return
*/
@Bean
public RateLimitKeyGenerator rateLimitKeyGenerator(final RateLimitProperties properties, final RateLimitUtils rateLimitUtils) {
return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
@Override
public String key(final HttpServletRequest request, final Route route, final RateLimitProperties.Policy policy) {
String key = super.key(request, route, policy) + ":" + request.getHeader("Auth");
log.info("===> current limit key :{}", key);
return key;
}
};
}
}
package com.enmonster.platformbasic.gtw.config;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.repository.DefaultRateLimiterErrorHandler;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.repository.RateLimiterErrorHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @Date 2020/5/20
* @Time 18:01
*/
@Component
@Slf4j
public class RateLimiterErrorConfig {
/**
* 重新定義RateLimiterErrorHandler
* 可以進行日誌捕捉或者其他操作
* @return
*/
@Bean
public RateLimiterErrorHandler rateLimitErrorHandler() {
return new DefaultRateLimiterErrorHandler() {
/**
* 往redis存儲限流請求信息的時候報錯的處理,此方法一般不用重寫
*
* @param key
* @param e
*/
@Override
public void handleSaveError(String key, Exception e) {
log.info("===>rateLimiter redisKey 存儲出錯:{}!! ", e);
super.handleSaveError(key, e);
}
/**
* 從redis取出限流請求信息的時候報錯的處理,此方法一般不用重寫
*
* @param key
* @param e
*/
@Override
public void handleFetchError(String key, Exception e) {
super.handleFetchError(key, e);
}
/**
* 請求發生限流了,或者限流過程中發生錯誤了的處理
* 對限流進行日誌記錄,返回限流的信息等,方便後期分析日誌排查問題,這裏就簡單處理打印日誌
*
* @param msg
* @param e
*/
@Override
public void handleError(String msg, Exception e) {
log.error("限流信息msg={},錯誤信息e={}", e);
super.handleError(msg, e);
}
};
}
}
踩坑篇
zuul.ratelimit.enabled配置
一旦引入RateLimit的jar包後,邊必須設置zuul.ratelimit.enabled=true,否則項目啓動時報錯
Swagger-ui 攔截問題
經測試 2.2.3.RELEASE版本會對Swagger-ui頁面造成攔截,將版本升級到2.2.7.RELEASE版本後可解決該問題
Failed to convert property value of type 'java.lang.String' to required type 'java.time.Duration'
服務啓動失敗報錯
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties@2178d296 failed:
Property: zuul.ratelimit.defaultPolicyList[0].quota
Value: 1000
Reason: Failed to convert property value of type 'java.lang.String' to required type 'java.time.Duration' for property 'defaultPolicyList[0].quota'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.time.Duration]
Property: zuul.ratelimit.defaultPolicyList[0].refreshInterval
Value: 60
Reason: Failed to convert property value of type 'java.lang.String' to required type 'java.time.Duration' for property 'defaultPolicyList[0].refreshInterval'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [@javax.validation.constraints.NotNull java.time.Duration]
Action:
Update your application's configuration
原因
在Zuul RateLimit版本介紹中,v2.3.0.RELEASE 支持
java.time.Duratio
配置refresh-internal
和quote
作者對配置解析進行了優化,這是好事.
但是翻看v2.3.0版本後發現,作者使用org.springframework.boot.convert.DurationUnit
將String
轉換爲Duration
,.且該方法SpringBootv2.0.1.RELEASE
版本纔開始存在.
故此低SpringBoot版本引入2.3.0.RELEASE或更高的Zuul-RateLimit包後,啓動會報上述錯誤
解決方案
SpringBoot版本在 v2.0.1.RELEASE
以下的,建議使用Zuul-RateLimit版本不得超過 v2.2.7.RELEASE
SpringBoot版本在v2.0.1.RELEAS
或者更高版本的,推薦使用Zuul-RateLimit的最新版本.
最新接入可參考GitHub官方文檔:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit