自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势

原创 Java大飞哥 Java大飞哥 2023-10-15 17:20 发表于云南

收录于合集

#redis3个

#redision1个

图片

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
   @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,这个是上一篇文章中的一个配置,这里需要修改一下的,不然有可能会报错的

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

属性单位默认值是否必填描述
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)
   @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 自定义触发限流处理函数,限流降级
@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 {

   @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 {
           //触发限流
           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); //每秒产生的令牌数
       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/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+。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
grpc-server-spring-boot-starter是一个基于Spring Boot框架的gRPC服务的启动。gRPC(Google Remote Procedure Call)是一种高性能的远程过程调用框架,它使用Protocol Buffers作为接口定义语言,并支持多种编程语言。 grpc-server-spring-boot-starter提供了一系列简化配置和集成的功能,使得在Spring Boot应用中启动和配置gRPC服务变得更加容易。它提供了自动装配的功能,可以根据应用的配置自动创建和启动gRPC服务。用户只需要在配置文件中设置相应的参数,如服务的端口号、TLS证书等,即可完成服务的启动配置。 在使用grpc-server-spring-boot-starter时,用户可以方便地定义服务接口和实现类。通过使用gRPC的接口定义语言(protobuf)定义接口,并生成对应的Java代码。然后,用户只需要在实现类中实现相应的接口方法即可。 在服务启动后,grpc-server-spring-boot-starter会根据定义的接口和实现类,自动创建相应的gRPC服务,并将其注册到服务中。当客户端发起远程调用时,服务会根据接口定义和方法参数,将请求转发给对应的实现类,并返回执行结果给客户端。 grpc-server-spring-boot-starter还支持对gRPC服务进行拦截的配置。拦截可以在请求和响应的过程中拦截和修改消息,用于实现日志记录、鉴权、性能监控等功能。 总之,grpc-server-spring-boot-starter简化了在Spring Boot应用中使用gRPC的配置和集成过程,使得开发者可以更加便捷地构建和部署gRPC服务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值