自定义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 注解说明

属性单位默认值是否必填描述
modeenum(TIME_WINDOW/TOKEN_BUCKET)TIME_WINDOW否限流模式,目前可选时间窗口和令牌桶
rateint无是时间窗口模式表示每个时间窗口内的请求数量、令牌桶模式表示每秒的令牌生产数量
rateIntervalString1s否用于时间窗口模式,表示时间窗口
rateExpressionString无否通过 EL 表达式从 Spring Config 上下文中获取 rate 的值,rateExpression 的优先级比 rate 高
fallbackFunctionString无否自定义触发限流时的降级策略方法,默认触发限流会抛 RateLimitException 异常
customKeyFunctionString无否自定义获取限流 key 的方法
bucketCapacityint无否用于令牌桶模式,表示令牌桶的桶的大小,这个参数控制了请求最大并发数
bucketCapacityExpressionString无否通过 EL 表达式从 Spring Config 上下文中获取 bucketCapacity 的值,bucketCapacityExpression 的优先级比 bucketCapacity 高
requestedTokensint1否用于令牌桶模式,表示每次获取的令牌数,一般不用改动这个参数值,除非你知道你在干嘛

  @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保险,也就是系统接口鲁棒性得到了大大的增强,希望我的分享对你有帮助,请一键三连,么么么哒!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/news/107133.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库实验一:学生信息管理系统数据库结构搭建和表的创建

实验项目名称&#xff1a;学生信息管理系统数据库结构搭建和表的创建 实验目的与要求实验原理与内容1. 数据库的组织结构2. 数据库的分离和附加3. 数据库表的创建&#xff0c;修改和删除 实验过程与结果1. 根据学生信息管理系统创建相关的数据库2. 数据库表初步设计及实现3. 实…

自然语言处理(NLP)-概述

NLP 一、什么是自然语言处理&#xff08;NLP&#xff09;二、NLP的发展三、相关理论1 语言模型2 词向量表征和语义分析3 深度学习 一、什么是自然语言处理&#xff08;NLP&#xff09; 什么是自然语言处理 二、NLP的发展 三、相关理论 1 语言模型 序列数据形式多样&#xf…

构建高效问题解答平台:使用Cpolar和Tipas在Ubuntu上搭建专属问答网站

文章目录 前言2.Tipask网站搭建2.1 Tipask网站下载和安装2.2 Tipask网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3 Cpolar稳定隧道&#xff08;本地设置&#xff09; 4. 公网访问测试5. 结语 前…

kafka安装

win10 来源:https://blog.csdn.net/tianmanchn/article/details/78943147 进入:http://kafka.apache.org/downloads.html点击Scala 2.12 - kafka_2.12-2.1.0.tgz点击http://mirrors.tuna.tsinghua.edu.cn/apache/kafka/2.1.0/kafka_2.12-2.1.0.tgz下载后解压缩 &#x1f604;:\…

微信小程序使用阿里巴巴iconfont,报错Failed to load font http://at.alicdn.com/t/..........

介绍 上篇文章&#xff0c;介绍了&#xff0c;在微信小程序里导入并使用阿里巴巴iconfont图标&#xff1b;但是在页面里使用后&#xff0c;可以看到后台日志有打印错误信息&#xff0c;具体报错如下&#xff1a; 分析 报这个错&#xff0c;是因为项目里使用了 iconfont字体…

主机jvisualvm连接到tomcat服务器查看jvm状态

​使用JMX方式连接到tomcat&#xff0c;连接后能够查看前边的部分内容&#xff0c;但是不能查看Visual GC&#xff0c;显示不受此JVM支持&#xff0c; 对了&#xff0c;要显示Visual GC&#xff0c;首先要安装visualvm工具&#xff0c;具体安装方式就是根据自己的jdk版本下载…

基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(二)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 之前讲到了流程保存的时候还要看是否是自定义业务流程应用类型&#xff0c;若是保存的时候不再检查是否有关…

AWS香港Web3方案日,防御云安全实践案例受关注

9月26日&#xff0c;AWS合作伙伴之Web3解决方案日在香港举办。来自人工智能、Web3等领域的创业公司、技术专家、风险投资商&#xff0c;就元宇宙时代未来发展进行了深入交流。现场展示了顶象防御云在金融与Web3领域的安全实践案例。 Web3为互联网体系架构的一个整体演进和升级&…

SpringBoot配置输出的日志文件

SpringBoot配置输出的日志文件 1、无需导入依赖&#xff0c;因为我们创建springboot时&#xff0c;导入的关于springboot的依赖中已经包含了。 2、我们在项目的resources 资源文件下创建logback.xml文件&#xff0c;文件内容如下 作用&#xff1a; 如果是开发时启动的项目&a…

【Apollo】感知工程安装测试

安装基础软件 安装Linux - Ubuntu 安装 Ubuntu 操作系统&#xff0c;请参见官方安装指南。 注意&#xff1a;推荐您使用 Ubuntu 18.04.5 或以上的版本作为您主机的操作系统&#xff0c;若采用18.04版本可使用&#xff1a;Ubuntu 18.04.5 LTS (Bionic Beaver) Ubuntu系统安装完…

私有git仓库只支持http情况下go mod tidy 和 go get 默认走https的问题处理 GOINSECURE

1 go mod tidy go mod tidy默认情况下在拉取go的依赖包时都是走的https协议&#xff0c;但是go的私有git仓库都是只支持http协议&#xff0c;所以当你的go.mod里面有私有仓库的依赖时&#xff0c;在使用go mod tidy拉取依赖时&#xff0c;一定会遇到这么个问题&#xff0c;就是…

Android Handler/Looper视角看UI线程的原理

概述 Handler/Looper机制是android系统非重要且基础的机制&#xff0c;即使在rtos或者linux操作系统上开发应用框架时&#xff0c;也经常借鉴这个机制。通过该机制机制可以让一个线程循环处理事件&#xff0c;事件处理逻辑即在Handler的handleMessge种。本文建议android8.1源码…

【22】c++设计模式——>外观模式

外观模式定义 为复杂系统提供一个简化接口&#xff0c;它通过创建一个高层接口(外观)&#xff0c;将多个子系统的复杂操作封装起来&#xff0c;以便客户端更容易使用。 简单实现 #include<iostream>// 子系统类 class SubsystemA { public:void operationA() {std::co…

保姆式教程:MAC安装Android studio(包括安装JDK,Android SDK),解决gradle下载慢的问题

文章目录 参考文章安装JDK并配置环境变量安装JDK配置JDK相关的环境变量 Android studio 安装下载Android studiogradle下载慢解决方法 安装Android SDK选择jdk版本安装SDK并配置环境变量 参考文章 原文链接 原文链接 安装JDK并配置环境变量 安装JDK 下载地址 下载后双击安装…

gitignore文件的语法规则

行注释&#xff1a;以"#"符号开头的行表示注释&#xff0c;Git会忽略这些行。空行&#xff1a;空行会被忽略。文件和目录规则&#xff1a; 可以使用通配符来匹配文件和目录。常用的通配符有&#xff1a; “*”&#xff1a;匹配0个或多个字符。“?”&#xff1a;匹配…

nodejs + express 实现 http文件下载服务程序

nodejs express 实现 http文件下载服务程序&#xff0c; 主要包括两个功能&#xff1a;指定目录的文件列表&#xff0c;某个文件的下载。 假设已经安装好 nodejs ; cd /js/node_js ; 安装在当前目录的 node_modules/ npm install express --save npm install express-gene…

Docker开启远程访问+idea配置docker+dockerfile发布java项目

一、docker开启远程访问 1.编辑docker服务文件 vim /usr/lib/systemd/system/docker.servicedocker.service原文件如下&#xff1a; [Unit] DescriptionDocker Application Container Engine Documentationhttps://docs.docker.com Afternetwork-online.target docker.socke…

【设计模式】十、组合模式

文章目录 案例组合模式基本介绍类图代码 组合模式在 JDK 集合的源码分析组合模式的注意事项和细节 案例 编写程序展示一个学校院系结构&#xff1a;需求是这样&#xff0c;要在一个页面中展示出学校的院系组成&#xff0c;一个学校有多个学院&#xff0c;一个学院有多个系。如…

Kotlin笔记(四):高阶函数

1. 高阶函数 1.1 定义高阶函数 高阶函数和Lambda的关系是密不可分的。一些与集合相关的函数式API的用法&#xff0c;如map、filter函数等,Kotlin的标准函数&#xff0c;如run、apply函数等。这几个函数有一个共同的特点&#xff1a;它们都会要求我们传入一个Lambda表达式作为参…

4.查询用户的累计消费金额及VIP等级

思路分析&#xff1a; &#xff08;1&#xff09;按照user_id及create_date 分组求消费金额total_amount &#xff08;2&#xff09;开窗计算同user_id下的累计销售金额sum(total_amount) over(partition by user_id order by create_date ROWS BETWEEN UNBOUNDED PRECEDING AN…