自定義redission裝配和集成分布式開源限流業(yè)務組件ratelimiter-spring-boot-starter的正確姿勢
1.說明
1.1 pom依賴
<dependency>
<groupId>com.github.taptap</groupId>
<artifactId>ratelimiter-spring-boot-starter</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
??由于使用了redisson-spring-boot-starter,在自定義redisson裝配的時候會被redisson-spring-boot-starter里面的start默認裝配了,同時在使用開源分布式限流組件ratelimiter-spring-boot-starter的時候,這個里面也會自動裝配一個redisson,所以就會產(chǎn)生沖突,容器中會有2個redisson的bean從而導致報錯,所以解決辦法是移除redisson-spring-boot-starter的依賴,加入redisson的依賴,或者不加redisson的依賴,redisson-spring-boot-starter里面包含了redisson-spring-boot-starter的依賴,是在啟動類上將redisson-spring-boot-starter的start排除:
1.2 引入redisson不引入redisson-spring-boot-starter依賴
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.14</version>
</dependency>
redisson
https://github.com/redisson/redisson#quick-start
1.3 引入redisson-spring-boot-starter不引入redisson,啟動類排除redisson-spring-boot-starter的自動裝配
@SpringBootApplication(exclude = {
RedissonAutoConfiguration.class})
@EnableTransactionManagement
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
??此時啟動容器中還是會有2個redisson的bean,所以需要自定義裝配一個,然后加上@Primary為主的redisson
2.自定義redission裝配
2.1 RedissonLockProperties
package xxx.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "redisson.lock.config")
public class RedissonLockProperties {
private String address;
private String password;
/**
* 1.single
* 2.master
* 3.sentinel
* 4.cluster
*/
private int mode = 1;
/**
* 在master模式下需配置這個
*/
private String masterAddress;
/**
* 在master模式下需配置這個
*/
private String[] slaveAddress;
/**
* 在sentinel模式下需配置這個
*/
private String masterName;
/**
* 在sentinel模式下需配置這個
*/
private String[] sentinelAddress;
/**
* 在cluster模式下需配置這個
*/
private String[] nodeAddress;
private int database = 5;
private int poolSize = 64;
private int idleSize = 24;
private int connectionTimeout = 10000;
private int timeout = 3000;
}
2.2 RedissonLockAutoConfiguration
package xx.config;
import jodd.util.StringUtil;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.MasterSlaveServersConfig;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* 分布式鎖自動化配置
*
* @author zlf
*/
@Configuration
@ConditionalOnClass(RedissonClient.class)
@EnableConfigurationProperties(RedissonLockProperties.class)
@ConditionalOnProperty(value = "redisson.lock.enabled", havingValue = "true")
public class RedissonLockAutoConfiguration {
private static Config singleConfig(RedissonLockProperties properties) {
Config config = new Config();
SingleServerConfig serversConfig = config.useSingleServer();
serversConfig.setAddress(properties.getAddress());
String password = properties.getPassword();
if (StringUtil.isNotBlank(password)) {
serversConfig.setPassword(password);
}
serversConfig.setDatabase(properties.getDatabase());
serversConfig.setConnectionPoolSize(properties.getPoolSize());
serversConfig.setConnectionMinimumIdleSize(properties.getIdleSize());
serversConfig.setIdleConnectionTimeout(properties.getConnectionTimeout());
serversConfig.setConnectTimeout(properties.getConnectionTimeout());
serversConfig.setTimeout(properties.getTimeout());
return config;
}
private static Config masterSlaveConfig(RedissonLockProperties properties) {
Config config = new Config();
MasterSlaveServersConfig serversConfig = config.useMasterSlaveServers();
serversConfig.setMasterAddress(properties.getMasterAddress());
serversConfig.addSlaveAddress(properties.getSlaveAddress());
String password = properties.getPassword();
if (StringUtil.isNotBlank(password)) {
serversConfig.setPassword(password);
}
serversConfig.setDatabase(properties.getDatabase());
serversConfig.setMasterConnectionPoolSize(properties.getPoolSize());
serversConfig.setMasterConnectionMinimumIdleSize(properties.getIdleSize());
serversConfig.setSlaveConnectionPoolSize(properties.getPoolSize());
serversConfig.setSlaveConnectionMinimumIdleSize(properties.getIdleSize());
serversConfig.setIdleConnectionTimeout(properties.getConnectionTimeout());
serversConfig.setConnectTimeout(properties.getConnectionTimeout());
serversConfig.setTimeout(properties.getTimeout());
return config;
}
private static Config sentinelConfig(RedissonLockProperties properties) {
Config config = new Config();
SentinelServersConfig serversConfig = config.useSentinelServers();
serversConfig.setMasterName(properties.getMasterName());
serversConfig.addSentinelAddress(properties.getSentinelAddress());
String password = properties.getPassword();
if (StringUtil.isNotBlank(password)) {
serversConfig.setPassword(password);
}
serversConfig.setDatabase(properties.getDatabase());
serversConfig.setMasterConnectionPoolSize(properties.getPoolSize());
serversConfig.setMasterConnectionMinimumIdleSize(properties.getIdleSize());
serversConfig.setSlaveConnectionPoolSize(properties.getPoolSize());
serversConfig.setSlaveConnectionMinimumIdleSize(properties.getIdleSize());
serversConfig.setIdleConnectionTimeout(properties.getConnectionTimeout());
serversConfig.setConnectTimeout(properties.getConnectionTimeout());
serversConfig.setTimeout(properties.getTimeout());
return config;
}
private static Config clusterConfig(RedissonLockProperties properties) {
Config config = new Config();
ClusterServersConfig serversConfig = config.useClusterServers();
serversConfig.addNodeAddress(properties.getNodeAddress());
String password = properties.getPassword();
if (StringUtil.isNotBlank(password)) {
serversConfig.setPassword(password);
}
serversConfig.setMasterConnectionPoolSize(properties.getPoolSize());
serversConfig.setMasterConnectionMinimumIdleSize(properties.getIdleSize());
serversConfig.setSlaveConnectionPoolSize(properties.getPoolSize());
serversConfig.setSlaveConnectionMinimumIdleSize(properties.getIdleSize());
serversConfig.setIdleConnectionTimeout(properties.getConnectionTimeout());
serversConfig.setConnectTimeout(properties.getConnectionTimeout());
serversConfig.setTimeout(properties.getTimeout());
return config;
}
@Bean
@Primary
public RedissonClient redissonClient(RedissonLockProperties properties) {
int mode = properties.getMode();
Config config = null;
switch (mode) {
case 1:
config = singleConfig(properties);
return Redisson.create(config);
case 2:
config = masterSlaveConfig(properties);
return Redisson.create(config);
case 3:
config = sentinelConfig(properties);
return Redisson.create(config);
case 4:
config = clusterConfig(properties);
return Redisson.create(config);
}
return null;
}
}
2.4 RedisConfig
??這里采用jedis的連接池工廠來裝配一個redisTemplateLimit,這個是上一篇文章中的一個配置,這里需要修改一下的,不然有可能會報錯的
自定義注解實現(xiàn)Redis分布式鎖、手動控制事務和根據(jù)異常名字或內容限流的三合一的功能
https://mp.weixin.qq.com/s/aW4PU_wlNVfzPc6uGFnndA
package xxx.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
@Slf4j
@RefreshScope
@Component
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.database}")
private String database;
@Value("${spring.redis.jedis.pool.max-active}")
private String maxActive;
@Value("${spring.redis.jedis.pool.max-idle}")
private String maxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private String minIdle;
//RedisConnectionFactory是這個spring-boot-starter-data-redis中的redis的連接工廠,如果不用jedis需要引入spring-boot-starter-data-redis即可,默認redisson-spring-boot-starter里面有這個依賴,如果沒有redisson-spring-boot-starter需要引入spring-boot-starter-data-redis可以使用的
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 定義泛型為 <String, Object> 的 RedisTemplate
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
// 設置連接工廠
template.setConnectionFactory(factory);
// 定義 Json 序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// Json 轉換工具
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//方法二:解決jackson2無法反序列化LocalDateTime的問題
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
om.registerModule(new JavaTimeModule());
jackson2JsonRedisSerializer.setObjectMapper(om);
// 定義 String 序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
JedisPool redisPoolFactory() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(Integer.valueOf(maxActive).intValue());
jedisPoolConfig.setMaxIdle(Integer.valueOf(maxIdle).intValue());
jedisPoolConfig.setMinIdle(Integer.valueOf(minIdle).intValue());
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, Integer.valueOf(port).intValue(), Protocol.DEFAULT_TIMEOUT, password, database);
log.info("JedisPool注入成功??!");
log.info("redis地址:" + host + ":" + port);
return jedisPool;
}
@Bean
RedisTemplate<String, Long> redisTemplateLimit(JedisConnectionFactory factory) {
final RedisTemplate<String, Long> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericToStringSerializer<>(Long.class));
template.setValueSerializer(new GenericToStringSerializer<>(Long.class));
return template;
}
//springboot報錯:Could not resolve placeholder ‘xxx‘ in value “${XXXX}
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer();
placeholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
return placeholderConfigurer;
}
}
2.3 nacos配置
spring:
redis:
host: xxx
port: 6379
password: xxxx
database: 5
# jedis配置
jedis:
pool:
max-active: 200
max-idle: 20
max-wait: 2000
min-idle: 5
lettuce:
shutdown-timeout: 0ms
redisson:
lock:
enabled: true
config:
address: redis://xxx:6379
password: xxxx
3.集成分布式開源限流組件ratelimiter-spring-boot-starter
ratelimiter-spring-boot-starter
https://github.com/TapTap/ratelimiter-spring-boot-starter#ratelimiter-spring-boot-starter
3.1 引入依賴
maven
<dependency>
<groupId>com.github.taptap</groupId>
<artifactId>ratelimiter-spring-boot-starter</artifactId>
<version>1.3</version>
</dependency>
gradle
implementation 'com.github.taptap:ratelimiter-spring-boot-starter:1.3'
3.2 nacos配置
spring:
ratelimiter:
enabled: true
redis-address: redis://xxx:6379
redis-password: xxxx
response-body: "您請求的太快了,請慢點,不然會有點受不了哦!"
status-code: 500
3.3 基礎使用
3.3.1 在需要加限流邏輯的方法上,添加注解 @RateLimit
如下所示:
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/get")
@RateLimit(rate = 5, rateInterval = "10s")
public String get(String name) {
return "hello";
}
}
3.3.2 @RateLimit 注解說明
屬性 | 單位 | 默認值 | 是否必填 | 描述 |
---|---|---|---|---|
mode | enum(TIME_WINDOW/TOKEN_BUCKET) | TIME_WINDOW | 否 | 限流模式,目前可選時間窗口和令牌桶 |
rate | int | 無 | 是 | 時間窗口模式表示每個時間窗口內的請求數(shù)量、令牌桶模式表示每秒的令牌生產(chǎn)數(shù)量 |
rateInterval | String | 1s | 否 | 用于時間窗口模式,表示時間窗口 |
rateExpression | String | 無 | 否 | 通過 EL 表達式從 Spring Config 上下文中獲取 rate 的值,rateExpression 的優(yōu)先級比 rate 高 |
fallbackFunction | String | 無 | 否 | 自定義觸發(fā)限流時的降級策略方法,默認觸發(fā)限流會拋 RateLimitException 異常 |
customKeyFunction | String | 無 | 否 | 自定義獲取限流 key 的方法 |
bucketCapacity | int | 無 | 否 | 用于令牌桶模式,表示令牌桶的桶的大小,這個參數(shù)控制了請求最大并發(fā)數(shù) |
bucketCapacityExpression | String | 無 | 否 | 通過 EL 表達式從 Spring Config 上下文中獲取 bucketCapacity 的值,bucketCapacityExpression 的優(yōu)先級比 bucketCapacity 高 |
requestedTokens | int | 1 | 否 | 用于令牌桶模式,表示每次獲取的令牌數(shù),一般不用改動這個參數(shù)值,除非你知道你在干嘛 |
??@RateLimit 注解可以添加到任意被 spring 管理的 bean 上,不局限于 controller ,service 、repository 也可以。在最基礎限流功能使用上,以上三個步驟就已經(jīng)完成了。
3.3.3 限流的粒度,限流 key
??限流的粒度是通過限流的 key 來做的,在最基礎的設置下,限流的 key 默認是通過方法名稱拼出來的,規(guī)則如下:
key=RateLimiter_ + 類名 + 方法名
??除了默認的 key 策略,ratelimiter-spring-boot-starter 充分考慮了業(yè)務限流時的復雜性,提供了多種方式。結合業(yè)務特征,達到更細粒度的限流控制。
3.3.4 觸發(fā)限流后的行為
??默認觸發(fā)限流后 程序會返回一個 http 狀態(tài)碼為 429 的響應,響應值如下:
{
"code": 429,
"msg": "Too Many Requests"
}
??同時,響應的 header 里會攜帶一個 Retry-After 的時間值,單位 s,用來告訴調用方多久后可以重試。當然這一切都是可以自定義的,進階用法可以繼續(xù)往下看
3.4 進階用法
3.4.1 自定義限流的 key
??自定義限流 key 有三種方式,當自定義限流的 key 生效時,限流的 key 就變成了(默認的 key + 自定義的 key)。下面依次給出示例
3.4.1.1 @RateLimitKey 的方式
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/get")
@RateLimit(rate = 5, rateInterval = "10s")
public String get(@RateLimitKey String name) {
return "get";
}
}
??@RateLimitKey 注解可以放在方法的入?yún)⑸希笕雲(yún)⑹腔A數(shù)據(jù)類型,上面的例子,如果 name = kl。那么最終限流的 key 如下:
key=RateLimiter_com.taptap.ratelimiter.web.TestController.get-kl
3.4.1.2 指定 keys 的方式
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/get")
@RateLimit(rate = 5, rateInterval = "10s", keys = {"#name"})
public String get(String name) {
return "get";
}
@GetMapping("/hello")
@RateLimit(rate = 5, rateInterval = "10s", keys = {"#user.name", "user.id"})
public String hello(User user) {
return "hello";
}
}
??keys 這個參數(shù)比 @RateLimitKey 注解更智能,基本可以包含 @RateLimitKey 的能力,只是簡單場景下,使用起來沒有 @RateLimitKey 那么便捷。keys 的語法來自 spring 的 Spel
,可以獲取對象入?yún)⒗锏膶傩?,支持獲取多個,最后會拼接起來。使用過 spring-cache 的同學可能會更加熟悉 如果不清楚 Spel
的用法,可以參考 spring-cache 的注解文檔
3.4.1.3 自定義 key 獲取函數(shù)
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/get")
@RateLimit(rate = 5, rateInterval = "10s", customKeyFunction = "keyFunction")
public String get(String name) {
return "get";
}
public String keyFunction(String name) {
return "keyFunction" + name;
}
}
??當 @RateLimitKey 和 keys 參數(shù)都沒法滿足時,比如入?yún)⒌闹凳且粋€加密的值,需要解密后根據(jù)相關明文內容限流??梢酝ㄟ^在同一類里自定義獲取 key 的函數(shù),這個函數(shù)要求和被限流的方法入?yún)⒁恢?,返回值?String 類型。返回值不能為空,為空時,會回退到默認的 key 獲取策略。
3.4.2 自定義限流后的行為
3.4.2.1 配置響應內容
spring.ratelimiter.enabled=true
spring.ratelimiter.response-body=Too Many Requests
spring.ratelimiter.status-code=509
??添加如上配置后,觸發(fā)限流時,http 的狀態(tài)碼就變成了 509 。響應的內容變成了 Too Many Requests 了
3.4.2.2 自定義限流觸發(fā)異常處理器
??默認的觸發(fā)限流后,限流器會拋出一個異常,限流器框架內定義了一個異常處理器來處理。自定義限流觸發(fā)處理器,需要先禁用系統(tǒng)默認的限流觸發(fā)處理器,禁用方式如下:
spring.ratelimiter.exceptionHandler.enable=false
??然后在項目里添加自定義處理器,如下:
@ControllerAdvice
public class RateLimitExceptionHandler {
private final RateLimiterProperties limiterProperties;
public RateLimitExceptionHandler(RateLimiterProperties limiterProperties) {
this.limiterProperties = limiterProperties;
}
@ExceptionHandler(value = RateLimitException.class)
@ResponseBody
public String exceptionHandler(HttpServletResponse response, RateLimitException e) {
response.setStatus(limiterProperties.getStatusCode());
response.setHeader("Retry-After", String.valueOf(e.getRetryAfter()));
return limiterProperties.getResponseBody();
}
}
3.4.2.3 自定義觸發(fā)限流處理函數(shù),限流降級
@RequestMapping("/test")
public class TestController {
@GetMapping("/get")
@RateLimit(rate = 5, rateInterval = "10s", fallbackFunction = "getFallback")
public String get(String name) {
return "get";
}
public String getFallback(String name) {
return "Too Many Requests" + name;
}
}
??這種方式實現(xiàn)和使用和 2.1.3、自定義 key 獲取函數(shù)類似。但是多一個要求,返回值的類型需要和原限流函數(shù)的返回值類型一致,當觸發(fā)限流時,框架會調用 fallbackFunction 配置的函數(shù)執(zhí)行并返回,達到限流降級的效果
3.4.3 動態(tài)設置限流大小
3.4.3.1 rateExpression 的使用
??從 v1.2
版本開始,在 @RateLimit
注解里新增了屬性 rateExpression。該屬性支持 Spel
表達式從 Spring 的配置上下文中獲取值。 當配置了 rateExpression 后,rate 屬性的配置就不生效了。使用方式如下:
@GetMapping("/get2")
@RateLimit(rate = 2, rateInterval = "10s", rateExpression = "${spring.ratelimiter.max}")
public String get2(){
return"get";
}
??集成 apollo 等配置中心后,可以做到限流大小的動態(tài)調整在線熱更。
3.5 直接使用限流器服務-RateLimiterService
??從 v1.3
版本開始,限流器框架內部提供了一個限流器服務,可以直接使用。當使用 RateLimiterService
后,則不用關心限流注解
的邏輯了,所有限流邏輯都可以高度定制,如下:
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private RateLimiterService limiterService;
@GetMapping("/limiterService/time-window")
public String limiterServiceTimeWindow(String key) {
Rule rule = new Rule(Mode.TIME_WINDOW); // 限流策略,設置為時間窗口
rule.setKey(key); //限流的 key
rule.setRate(5); //限流的速率
rule.setRateInterval(10); //時間窗口大小,單位為秒
Result result = limiterService.isAllowed(rule);
if (result.isAllow()) { //如果允許訪問
return "ok";
} else {
//觸發(fā)限流
return "no";
}
}
@GetMapping("/limiterService/token-bucket")
public String limiterServiceTokenBucket(String key) {
Rule rule = new Rule(Mode.TOKEN_BUCKET); // 限流策略,設置為令牌桶
rule.setKey(key); //限流的 key
rule.setRate(5); //每秒產(chǎn)生的令牌數(shù)
rule.setBucketCapacity(10); //令牌桶容量
rule.setRequestedTokens(1); //請求的令牌數(shù)
Result result = limiterService.isAllowed(rule);
if (result.isAllow()) { //如果允許訪問
return "ok";
} else {
//觸發(fā)限流
return "no";
}
}
}
3.6壓力測試
- 壓測工具 wrk: https://github.com/wg/wrk
- 測試環(huán)境: 8 核心 cpu ,jvm 內存給的 -Xms2048m -Xmx2048m ,鏈接的本地的 redis
#壓測數(shù)據(jù)
kldeMacBook-Pro-6:ratelimiter-spring-boot-starter kl$ wrk -t16 -c100 -d15s --latency http://localhost:8080/test/wrk
Running 15s test @ http://localhost:8080/test/wrk
16 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 6.18ms 20.70ms 281.21ms 98.17%
Req/Sec 1.65k 307.06 2.30k 76.44%
Latency Distribution
50% 3.57ms
75% 4.11ms
90% 5.01ms
99% 115.48ms
389399 requests in 15.03s, 43.15MB read
Requests/sec: 25915.91
Transfer/sec: 2.87MB
??壓測下,所有流量都過限流器,qps 可以達到 2w+。文章來源:http://www.zghlxwxcb.cn/news/detail-724584.html
3.7版本更新
3.7.1 (v1.1.1)版本更新內容
- 1、觸發(fā)限流時,header 的 Retry-After 值,單位由 ms ,調整成了 s
3.7.2(v1.2)版本更新內容
- 1、觸發(fā)限流時,響應的類型從
text/plain
變成了application/json
- 2、優(yōu)化了限流的 lua 腳本,將原來的兩步 lua 腳本請求,合并成了一個,減少了和 redis 的交互
- 3、限流的時間窗口大小,支持
Spel
從 Spring 的配置上下文中獲取,結合apollo
等配置中心后,支持規(guī)則的動態(tài)下發(fā)熱更新
3.7.3(v1.3)版本更新內容
- 1、配置策略變化,不在從應用的上下文中獲取 Redis 數(shù)據(jù)源,而是必須配置。但是配置的數(shù)據(jù)源在 Spring 上下文中聲明了
rateLimiterRedissonBeanName
,應用也可以獲取使用 - 2、代碼重構,新增了
令牌桶
的限流策略支持 - 3、抽象了限流器服務
RateLimiterService
,并在 Spring 上下文中聲明了,應用可以直接注入使用
4.總結
??這個也是在生產(chǎn)實踐后遇到的坑的一個總結,ratelimiter-spring-boot-starter、redisson-spring-boot-starter同時使用會有沖突,已經(jīng)RedisTemplate裝配上一篇文章的redisConfig配置會有報錯,所以這篇文章做了一個代碼調整,總結和分享,也是方便以后快速使用,不至于搞半天,所以總結成文是很有必要的,也是對以后的一種方便,ratelimiter-spring-boot-starter開源分布式限流組件(偏業(yè)務)的使用也是非常簡單,參看官網(wǎng)就可以學會的,源碼寫的也是很好的,就不需要自己重復的去制造輪子了,有這種開源好用的輪子直接拿來使用解決業(yè)務的燃眉之急,ratelimiter-spring-boot-starter可以針對一個接口使用令牌桶(接口總體上的限流)限流 + 時間窗口限流(針對一個用戶主鍵key,用戶唯一標識,對這個用戶限制在3s內只能點擊一次的操作,防止重復點擊) + redisson分布式鎖(比如說鎖一個用戶唯一標識3s鐘釋放鎖,這里存在一個問題就是3s內執(zhí)行的太快就容易點擊多次,取決于用戶3s內的手續(xù)和接口每次執(zhí)行的快慢),經(jīng)過這3個步驟,就可以讓系統(tǒng)接口的具有3保險,也就是系統(tǒng)接口魯棒性得到了大大的增強,希望我的分享對你有幫助,請一鍵三連,么么么噠!文章來源地址http://www.zghlxwxcb.cn/news/detail-724584.html
到了這里,關于自定義redission裝配和集成分布式開源限流業(yè)務組件ratelimiter-spring-boot-starter的正確姿勢的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!