自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势
自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势
文章目录
- 1.说明
- 1.1 pom依赖
- 1.2 引入redisson不引入redisson-spring-boot-starter依赖
- 1.3 引入redisson-spring-boot-starter不引入redisson,启动类排除redisson-spring-boot-starter的自动装配
- 2.自定义redission装配
- 2.1 RedissonLockProperties
- 2.2 RedissonLockAutoConfiguration
- 2.4 RedisConfig
- 2.3 nacos配置
- 3.集成分布式开源限流组件ratelimiter-spring-boot-starter
- 3.1 引入依赖
- 3.2 nacos配置
- 3.3 基础使用
- 3.3.1 在需要加限流逻辑的方法上,添加注解 @RateLimit
- 3.3.2 @RateLimit 注解说明
- 3.3.3 限流的粒度,限流 key
- 3.3.4 触发限流后的行为
- 3.4 进阶用法
- 3.4.1 自定义限流的 key
- 3.4.1.1 @RateLimitKey 的方式
- 3.4.1.2 指定 keys 的方式
- 3.4.1.3 自定义 key 获取函数
- 3.4.2 自定义限流后的行为
- 3.4.2.1 配置响应内容
- 3.4.2.2 自定义限流触发异常处理器
- 3.4.2.3 自定义触发限流处理函数,限流降级
- 3.4.3 动态设置限流大小
- 3.4.3.1 rateExpression 的使用
- 3.5 直接使用限流器服务-RateLimiterService
- 3.6压力测试
- 3.7版本更新
- 3.7.1 (v1.1.1)版本更新内容
- 3.7.2(v1.2)版本更新内容
- 3.7.3(v1.3)版本更新内容
- 4.总结
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,所以就会产生冲突,容器中会有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@Primarypublic 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,这个是上一篇文章中的一个配置,这里需要修改一下的,不然有可能会报错的
自定义注解实现Redis分布式锁、手动控制事务和根据异常名字或内容限流的三合一的功能
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> 的 RedisTemplateRedisTemplate<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序列化方式采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash的value序列化方式采用jacksontemplate.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}@BeanJedisPool 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;}@BeanRedisTemplate<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}@Beanpublic static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer();placeholderConfigurer.setIgnoreUnresolvablePlaceholders(true);return placeholderConfigurer;}}
2.3 nacos配置
spring:redis:host: xxxport: 6379password: xxxxdatabase: 5# jedis配置jedis:pool:max-active: 200max-idle: 20max-wait: 2000min-idle: 5lettuce:shutdown-timeout: 0ms
redisson:lock:enabled: trueconfig:address: redis://xxx:6379password: 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: trueredis-address: redis://xxx:6379redis-password: xxxxresponse-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 | 无 | 是 | 时间窗口模式表示每个时间窗口内的请求数量、令牌桶模式表示每秒的令牌生产数量 |
rateInterval | String | 1s | 否 | 用于时间窗口模式,表示时间窗口 |
rateExpression | String | 无 | 否 | 通过 EL 表达式从 Spring Config 上下文中获取 rate 的值,rateExpression 的优先级比 rate 高 |
fallbackFunction | String | 无 | 否 | 自定义触发限流时的降级策略方法,默认触发限流会抛 RateLimitException 异常 |
customKeyFunction | String | 无 | 否 | 自定义获取限流 key 的方法 |
bucketCapacity | int | 无 | 否 | 用于令牌桶模式,表示令牌桶的桶的大小,这个参数控制了请求最大并发数 |
bucketCapacityExpression | String | 无 | 否 | 通过 EL 表达式从 Spring Config 上下文中获取 bucketCapacity 的值,bucketCapacityExpression 的优先级比 bucketCapacity 高 |
requestedTokens | int | 1 | 否 | 用于令牌桶模式,表示每次获取的令牌数,一般不用改动这个参数值,除非你知道你在干嘛 |
@RateLimit 注解可以添加到任意被 spring 管理的 bean 上,不局限于 controller ,service 、repository 也可以。在最基础限流功能使用上,以上三个步骤就已经完成了。
3.3.3 限流的粒度,限流 key
限流的粒度是通过限流的 key 来做的,在最基础的设置下,限流的 key 默认是通过方法名称拼出来的,规则如下:
key=RateLimiter_ + 类名 + 方法名
除了默认的 key 策略,ratelimiter-spring-boot-starter 充分考虑了业务限流时的复杂性,提供了多种方式。结合业务特征,达到更细粒度的限流控制。
3.3.4 触发限流后的行为
默认触发限流后 程序会返回一个 http 状态码为 429 的响应,响应值如下:
{"code": 429,"msg": "Too Many Requests"
}
同时,响应的 header 里会携带一个 Retry-After 的时间值,单位 s,用来告诉调用方多久后可以重试。当然这一切都是可以自定义的,进阶用法可以继续往下看
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 注解可以放在方法的入参上,要求入参是基础数据类型,上面的例子,如果 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 这个参数比 @RateLimitKey 注解更智能,基本可以包含 @RateLimitKey 的能力,只是简单场景下,使用起来没有 @RateLimitKey 那么便捷。keys 的语法来自 spring 的 Spel
,可以获取对象入参里的属性,支持获取多个,最后会拼接起来。使用过 spring-cache 的同学可能会更加熟悉 如果不清楚 Spel
的用法,可以参考 spring-cache 的注解文档
3.4.1.3 自定义 key 获取函数
@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 参数都没法满足时,比如入参的值是一个加密的值,需要解密后根据相关明文内容限流。可以通过在同一类里自定义获取 key 的函数,这个函数要求和被限流的方法入参一致,返回值为 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
添加如上配置后,触发限流时,http 的状态码就变成了 509 。响应的内容变成了 Too Many Requests 了
3.4.2.2 自定义限流触发异常处理器
默认的触发限流后,限流器会抛出一个异常,限流器框架内定义了一个异常处理器来处理。自定义限流触发处理器,需要先禁用系统默认的限流触发处理器,禁用方式如下:
spring.ratelimiter.exceptionHandler.enable=false
然后在项目里添加自定义处理器,如下:
@ControllerAdvice
public class RateLimitExceptionHandler {private final RateLimiterProperties limiterProperties;public RateLimitExceptionHandler(RateLimiterProperties limiterProperties) {this.limiterProperties = limiterProperties;}@ExceptionHandler(value = RateLimitException.class)@ResponseBodypublic 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 自定义触发限流处理函数,限流降级
@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;}}
这种方式实现和使用和 2.1.3、自定义 key 获取函数类似。但是多一个要求,返回值的类型需要和原限流函数的返回值类型一致,当触发限流时,框架会调用 fallbackFunction 配置的函数执行并返回,达到限流降级的效果
3.4.3 动态设置限流大小
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 等配置中心后,可以做到限流大小的动态调整在线热更。
3.5 直接使用限流器服务-RateLimiterService
从 v1.3
版本开始,限流器框架内部提供了一个限流器服务,可以直接使用。当使用 RateLimiterService
后,则不用关心限流注解
的逻辑了,所有限流逻辑都可以高度定制,如下:
@RestController
@RequestMapping("/test")
public class TestController {@Autowiredprivate RateLimiterService limiterService;@GetMapping("/limiterService/time-window")public String limiterServiceTimeWindow(String key) {Rule rule = new Rule(Mode.TIME_WINDOW); // 限流策略,设置为时间窗口rule.setKey(key); //限流的 keyrule.setRate(5); //限流的速率rule.setRateInterval(10); //时间窗口大小,单位为秒Result result = limiterService.isAllowed(rule);if (result.isAllow()) { //如果允许访问return "ok";} else {//触发限流return "no";}}@GetMapping("/limiterService/token-bucket")public String limiterServiceTokenBucket(String key) {Rule rule = new Rule(Mode.TOKEN_BUCKET); // 限流策略,设置为令牌桶rule.setKey(key); //限流的 keyrule.setRate(5); //每秒产生的令牌数rule.setBucketCapacity(10); //令牌桶容量rule.setRequestedTokens(1); //请求的令牌数Result result = limiterService.isAllowed(rule);if (result.isAllow()) { //如果允许访问return "ok";} else {//触发限流return "no";}}
}
3.6压力测试
- 压测工具 wrk: https://github.com/wg/wrk
- 测试环境: 8 核心 cpu ,jvm 内存给的 -Xms2048m -Xmx2048m ,链接的本地的 redis
#压测数据
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/wrk16 threads and 100 connectionsThread Stats Avg Stdev Max +/- StdevLatency 6.18ms 20.70ms 281.21ms 98.17%Req/Sec 1.65k 307.06 2.30k 76.44%Latency Distribution50% 3.57ms75% 4.11ms90% 5.01ms99% 115.48ms389399 requests in 15.03s, 43.15MB read
Requests/sec: 25915.91
Transfer/sec: 2.87MB
压测下,所有流量都过限流器,qps 可以达到 2w+。
3.7版本更新
3.7.1 (v1.1.1)版本更新内容
- 1、触发限流时,header 的 Retry-After 值,单位由 ms ,调整成了 s
3.7.2(v1.2)版本更新内容
- 1、触发限流时,响应的类型从
text/plain
变成了application/json
- 2、优化了限流的 lua 脚本,将原来的两步 lua 脚本请求,合并成了一个,减少了和 redis 的交互
- 3、限流的时间窗口大小,支持
Spel
从 Spring 的配置上下文中获取,结合apollo
等配置中心后,支持规则的动态下发热更新
3.7.3(v1.3)版本更新内容
- 1、配置策略变化,不在从应用的上下文中获取 Redis 数据源,而是必须配置。但是配置的数据源在 Spring 上下文中声明了
rateLimiterRedissonBeanName
,应用也可以获取使用 - 2、代码重构,新增了
令牌桶
的限流策略支持 - 3、抽象了限流器服务
RateLimiterService
,并在 Spring 上下文中声明了,应用可以直接注入使用
4.总结
这个也是在生产实践后遇到的坑的一个总结,ratelimiter-spring-boot-starter、redisson-spring-boot-starter同时使用会有冲突,已经RedisTemplate装配上一篇文章的redisConfig配置会有报错,所以这篇文章做了一个代码调整,总结和分享,也是方便以后快速使用,不至于搞半天,所以总结成文是很有必要的,也是对以后的一种方便,ratelimiter-spring-boot-starter开源分布式限流组件(偏业务)的使用也是非常简单,参看官网就可以学会的,源码写的也是很好的,就不需要自己重复的去制造轮子了,有这种开源好用的轮子直接拿来使用解决业务的燃眉之急,ratelimiter-spring-boot-starter可以针对一个接口使用令牌桶(接口总体上的限流)限流 + 时间窗口限流(针对一个用户主键key,用户唯一标识,对这个用户限制在3s内只能点击一次的操作,防止重复点击) + redisson分布式锁(比如说锁一个用户唯一标识3s钟释放锁,这里存在一个问题就是3s内执行的太快就容易点击多次,取决于用户3s内的手续和接口每次执行的快慢),经过这3个步骤,就可以让系统接口的具有3保险,也就是系统接口鲁棒性得到了大大的增强,希望我的分享对你有帮助,请一键三连,么么么哒!
相关文章:
自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势
自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势 文章目录 1.说明1.1 pom依赖1.2 引入redisson不引入redisson-spring-boot-starter依赖1.3 引入redisson-spring-boot-starter不引入redisson,启动类排除redisson-spring-boot-start…...
Ceph分布式存储的简单介绍与Ceph集群的部署搭建
文章目录 1. 存储的概述1.1 单机存储设备1.1.1 DAS(直接附加存储)1.1.2 NAS(网络附加存储)1.1.3 SAN(存储区域网络) 1.2 单机存储的缺陷1.3 分布式存储(软件定义的存储 SDS)1.4 分布…...
【环境搭建】linux docker安装nexus3
1、shell输入 docker run -dti \--nethost \--namenexus3 \--privilegedtrue \--restartalways \--ulimit nofile655350 \--ulimit memlock-1 \--memory1G \--memory-swap-1 \-e INSTALL4J_ADD_VM_PARAMS"-Xms512m -Xmx512m -XX:MaxDirectMemorySize1g" \-v /etc/lo…...
Java多线程下载文件
JVM是支持多线程程序的,当程序需要同时执行两个或多个任务,实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等多线程程序比单线程程序更具优势,可充分利用CPU资源,完成时间更短,提高应用程…...
oracle 同一张表同时insert多条数据 mysql 同一张表同时insert多条数据
oracle 同一张表同时insert多条数据 在Oracle数据库中,你可以使用INSERT ALL语句同时向同一张表插入多条数据。INSERT ALL语句允许你一次执行多个插入操作,可以提高插入的效率和速度。 以下是使用INSERT ALL语句插入多条数据的示例: INSERT…...
ROS键盘遥控机器人,通过参数服务器指定速度
1、引言 在上节的驱动机器人,我们知道是cmd_vel话题发布一串Twist类型消息来控制,我们可以输入如下命令查看这个Twist的详细信息:rosmsg show geometry_msgs/Twist geometry_msgs/Vector3 linear float64 x float64 y float64 z geome…...
具有快表的地址变换机构
1.快表(TLB) 快表,又称联想寄存器(TLB,translation lookaside buffer), 是一种访问速度比内存快很多的高速缓存(TLB不是内存! ), 用来存放最近访问的页表项的副本,可以加速地址变换的速度。 与…...
【使用python和flask建个人博客】修复侧边栏最新文章、最多阅读等链接不能打开的问题
自从上次因版本兼容问题修改过部分代码之后,好长时间没光顾woniunote这个个人博客模块了,最近发文章的时候发现侧边栏的文章打不开,定位了bug,并进行了修复。 <div class="col-12 side"><div class="tip" align...
ShareX使用说明——优秀的录屏软件
ShareX初识 ShareX 是一个自由及开放源代码的截图录像软件,目前仅支持Windows系统。 项目源代码在GitHub平台上发布, 软件可以在Microsoft商店和Steam上下载。 ShareX is a free and open source program that lets you capture or record any area of y…...
10.14~10.15verilog操作流程与Block Design
后面的那个是延时精度 verilog文件结构 文件名称与写的模板没有关系,这个文件名为P1,但模板名为andgate 但是如果是仿真文件,就需要开头的模板名和仿真文件名相同 .v是源文件,设计文件 .v在设计与sim里都有,静态共享࿰…...
小解C语言文件编译过程【linux】
小解C语言文件编译过程【linux】 库动态库静态库 C语言文件 程序编译过程整体预处理编译汇编链接动态链接静态链接两种方法对比 库 看到标题是文件编译过程 但是开头却是库,这可不是挂羊头卖狗肉,而是因为库也是代码不可缺少的一部分,并且在…...
[Python]黑色背景白色块滑动视频
黑色背景白色块滑动视频,单帧效果如下: 配置参数 1920 1080 400 400 300 60 1920x1080.avi import numpy as np import cv2 as cv import os import syswidth 1920 height 1080 rect_szx 400 rect_szy 300 sz_y_init 400 fps 24width int(sys.a…...
【linux kernel】对linux内核设备的注册机制和查找机制分析
文章目录 1、简介2、device_initialize分析3、device_add分析4、总结 🔺【linux内核系列文章】 👉对一些文章内容进行了勘误,本系列文章长期不定时更新,希望能分享出优质的文章! 1、《linux内核数据结构分析之哈希表》…...
asp.net酒店餐饮管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio
一、源码特点 asp.net酒店餐饮管理系统是一套完善的web设计管理系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c#语言 开发 ASP.NE 酒店餐饮管理系统 二、功能…...
38_Nginx 启动流程
文章目录 src/core/nginx.cint ngx_cdecl main(int argc, char *const *argv) {ngx_buf_t *b;...
数据特征选择 | Lasso特征选择(Python)
文章目录 效果一览文章概述源码设计小结效果一览 文章概述 Lasso算法是一种经典的线性回归算法,被广泛应用于特征选择和降维问题。相较于传统的线性回归算法,Lasso算法能够在保持预测准确性的同时,自动筛选出对目标变量影响较大的特征变量,从而达到降低模型复杂度、提高泛化…...
最小覆盖子串[困难]
优质博文:IT-BLOG-CN 一、题目 给你一个字符串s、一个字符串t。返回s中涵盖t所有字符的最小子串。如果s中不存在涵盖t所有字符的子串,则返回空字符串"" 。 对于t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量…...
保姆级搭建Mysql 并进行视图可视化操作
安装MySQL数据库 选择mysql5.7.36_x32.msi”,双击运行,如下图所示: 在此窗口中,选择“Custom”选项,点击“Next>”进入下一步; 在此窗口中,选择号下的MySQL Server 5.7.36 – x64&…...
设计模式的学习顺序
设计模式的学习顺序可以按照以下步骤进行: 掌握基础知识:先确保你对编程语言和软件开发的基本概念有深入的理解,包括面向对象编程、继承、多态等。学习常用设计模式:首先学习并理解一些常用的设计模式,例如单例模式、…...
数据结构和算法——树结构
二叉树 又叫二叉排序树。 节点是数量为,,n为层数。 满二叉树:所有的叶子节点都在最后一层。 完全二叉树:如果所有叶子节点都在最后一层和倒数第二层,而且每个叶子节点都有左右子节点。 完全二叉树 前序遍历 1、先输…...
【Java】Integer包装类
Integer:对基本数据类型 int 实现包装 方法名称说明public Integer(int value)根据 int 值创建 Integer 对象(JDK9以后过时)public integer(String s)根据 String 值创建 Integer 对象…...
Web后端开发登录校验及JWT令牌,过滤器,拦截器详解
如果用户名正确则成功进入 登录功能 代码 Controller Service Mapper 结果 若登录成功结果如下: 如果登录失败,结果如下 登录校验 为什么需要登录校验 有时再未登录情况下, 我们也可以直接访问部门管理, 员工管理等功能 因此我们需要一个登录校验操作, 只有确认用户登录…...
大语言模型迎来重大突破!找到解释神经网络行为方法
前不久,获得亚马逊40亿美元投资的ChatGPT主要竞争对手Anthropic在官网公布了一篇名为《朝向单义性:通过词典学习分解语言模型》的论文,公布了解释经网络行为的方法。 由于神经网络是基于海量数据训练而成,其开发的AI模型可以生成…...
zabbix内置宏、自动发现与注册
一、zabbix内置宏 1、概念: 在Zabbix中,内置宏是一种特殊的变量,通常用在 Trigger 名称和表达式中,引用有关监控对象的信息。 2、种类: {HOST.NAME} 主机名 {HOST.IP} 主机 IP 地址 {TRIGGER.DESCRIPTION} 触…...
Oracle与Mysql语法区别
database 一、数据类型二、update..select语句三、upsert语句四、常见函数五、自动更新列时间戳一、数据类型 OracleMysqlnumberint/decimal变长字符:varchar2varchardatedatetime/timestampinttinyint/smallint/mediumint/int/bigint二、update…select语句 Oracle update t…...
Jetpack:008-Icon与Image
文章目录 1. 概念介绍2. 使用方法2.1 Icon2.2 Image 3. 示例代码4. 内容总结 我们在上一章回中介绍了Jetpack中与Button相关的内容,本章回中主要I con与Image。闲话休提,让我们一起Talk Android Jetpack吧! 1. 概念介绍 我们在本章回中介绍…...
参数解析(牛客)
目录 一、题目 二、代码 一、题目 二、代码 #include <iostream> #include <vector> using namespace std;int main() {string s;getline(cin, s);int i 0;vector<string>ret;while (i < s.size()){if (s[i] )//遇到空格直接跳过{i;}else if (s[i] …...
Linux网络编程系列之服务器编程——阻塞IO模型
Linux网络编程系列 (够吃,管饱) 1、Linux网络编程系列之网络编程基础 2、Linux网络编程系列之TCP协议编程 3、Linux网络编程系列之UDP协议编程 4、Linux网络编程系列之UDP广播 5、Linux网络编程系列之UDP组播 6、Linux网络编程系列之服务器编…...
排序算法-基数排序法(RadixSort)
排序算法-基数排序法(RadixSort) 1、说明 基数排序法与我们之前讨论的排序法不太一样,并不需要进行元素之间的比较操作,而是属于一种分配模式排序方式。 基数排序法比较的方向可分为最高位优先(Most Significant Di…...
nginx绑定tomcat与tomcat联合使用的配置(nginx反向代理tomcat的配置说明)
nginx反向代理tomcat通信配置 (内容来自网上,注解部分才是原创) 切记: url的意思就是 unifed resource location 统一资源定位 其中location就是定位的意思 所以上文中的location就有 对应匹配的 url 标识的资源的相关配置之…...
化妆品网站建设策划书/农夫山泉软文300字
Python目前已经成为最前沿的编程语言了,Python的时代来临。与多年前的Perl,PHP和Ruby等脚本语言一样,如今Python在许多知名技术社区,已经成为大家探讨的焦点。如:GitHub、Stack Overflow中。 更重要的是,它…...
移动网站优化 sousuoyouhua.com/百度推广代运营
1、watch深度监听 watch主要用于监听页面值的变化,watch前后(newValue和oldValue)值一样的情况下,app监听不会被调用,H5会被调用。 export default {props: {isTag: {type: Boolean,default: true}},watch: {// newV…...
wordpress无法预览/今日头条官网首页
1、配置文件 #整合jms测试,安装在别的机器,防火墙和端口号记得开放 spring.activemq.broker-urltcp://127.0.0.1:61616 #集群配置 #spring.activemq.broker-urlfailover:(tcp://localhost:61616,tcp://localhost:61617) spring.activemq.useradmin spri…...
云南网站制作一条龙全包/深圳网络营销推广招聘网
题目大意:给出一些圆,求一个可以将所有圆装下的箱子,输出箱子的长,圆摆放的要求是必须至少与另一个圆相接,并且所有圆必须接触地面。 解题思路:一开始吧这道题想的太简单了,直接用DFS将所有排列…...
主机屋安装wordpress/店铺推广渠道有哪些
std map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数…...
wordpress分菜单/职业技能培训有哪些
这个帖子里的方法有点过时了,不推荐继续使用。有的时候会碰到这么一种情况,带着电脑和手机出去蹭网,无奈只有一个账号,手机上了电脑就没得用了,电脑用了手机就上不了网。如果能用电脑连接 Wifi 然后再开热点给手机用该…...