{"id":33178989,"url":"https://github.com/taptap/ratelimiter-spring-boot-starter","last_synced_at":"2025-12-24T05:26:56.943Z","repository":{"id":57723509,"uuid":"348694551","full_name":"taptap/ratelimiter-spring-boot-starter","owner":"taptap","description":"基于 redis 的偏业务应用的分布式限流组件，使得项目拥有分布式限流能力变得很简单。","archived":false,"fork":false,"pushed_at":"2022-08-25T07:54:16.000Z","size":131,"stargazers_count":119,"open_issues_count":5,"forks_count":35,"subscribers_count":3,"default_branch":"master","last_synced_at":"2023-12-08T09:04:47.441Z","etag":null,"topics":["ratelimit","ratelimit-middleware","ratelimiter","redis","redisson"],"latest_commit_sha":null,"homepage":"https://gitee.com/kailing/ratelimiter-spring-boot-starter","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/taptap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-17T12:05:24.000Z","updated_at":"2023-11-27T09:08:17.000Z","dependencies_parsed_at":"2022-08-28T15:34:46.702Z","dependency_job_id":null,"html_url":"https://github.com/taptap/ratelimiter-spring-boot-starter","commit_stats":null,"previous_names":[],"tags_count":2,"template":null,"template_full_name":null,"purl":"pkg:github/taptap/ratelimiter-spring-boot-starter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taptap%2Fratelimiter-spring-boot-starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taptap%2Fratelimiter-spring-boot-starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taptap%2Fratelimiter-spring-boot-starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taptap%2Fratelimiter-spring-boot-starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/taptap","download_url":"https://codeload.github.com/taptap/ratelimiter-spring-boot-starter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taptap%2Fratelimiter-spring-boot-starter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285511775,"owners_count":27184237,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-20T02:00:05.334Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ratelimit","ratelimit-middleware","ratelimiter","redis","redisson"],"created_at":"2025-11-16T03:00:36.817Z","updated_at":"2025-11-20T21:03:08.218Z","avatar_url":"https://github.com/taptap.png","language":"Java","funding_links":[],"categories":["容错组件"],"sub_categories":[],"readme":"# ratelimiter-spring-boot-starter\n\n基于 redis 的偏业务应用的分布式限流组件，目前支持`时间窗口`、`令牌桶`\n两种限流算法。使得项目拥有分布式限流能力变得很简单。限流的场景有很多，常说的限流一般指网关限流，控制好洪峰流量，以免打垮后方应用。这里突出`偏业务应用的分布式限流`\n的原因，是因为区别于网关限流，业务侧限流可以轻松根据业务性质做到细粒度的流量控制。比如如下场景，\n\n- 案例一：有一个公开的 openApi 接口， openApi 会给接入方派发一个 appId，此时，如果需要根据各个接入方的 appId 限流，网关限流就不好做了，只能在业务侧实现\n\n- 案例二：公司内部的短信接口，内部对接了多个第三方的短信通道，每个短信通道对流量的控制都不尽相同，假设有的第三方根据手机号和短信模板组合限流，网关限流就更不好做了\n\n以上举例的场景，通过 ratelimiter-spring-boot-starter 可以轻松解决限流问题\n\n## 限流算法说明\n\n- `时间窗口限流`：偏向控制请求数量，比如每秒请求数量不超过 100，每分钟请求数量不超过 1000，每小时请求数量不超过 10000，每天请求数量不超过 100000。\n- `令牌桶限流`：偏向控制请求频率，比如最大请求并发不超过 100，且 QPS 限制在一定范围内，比如 QPS 限制在 50。\n\n关于限流算法更详细的信息：http://www.kailing.pub/article/index/arcid/251.html\n\n## 1、快速开始\n\n### 1.1、添加组件依赖，已上传到maven中央仓库\n\nmaven\n\n```xml\n\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.taptap\u003c/groupId\u003e\n  \u003cartifactId\u003eratelimiter-spring-boot-starter\u003c/artifactId\u003e\n  \u003cversion\u003e1.3\u003c/version\u003e\n\u003c/dependency\u003e\n\n```\n\ngradle\n\n```groovy\nimplementation 'com.github.taptap:ratelimiter-spring-boot-starter:1.3'\n```\n\n### 1.2、application.properties 配置\n\n```properties\nspring.ratelimiter.enabled=true\nspring.ratelimiter.redis-address=redis://127.0.0.1:6379\nspring.ratelimiter.redis-password=xxx\n```\n\n启用 ratelimiter 的配置必须加，默认不会加载。\n\n### 1.3、在需要加限流逻辑的方法上，添加注解 @RateLimit，如：\n\n```java\n\n@RestController\n@RequestMapping(\"/test\")\npublic class TestController {\n\n    @GetMapping(\"/get\")\n    @RateLimit(rate = 5, rateInterval = \"10s\")\n    public String get(String name) {\n        return \"hello\";\n    }\n}\n```\n\n#### 1.3.1 @RateLimit 注解说明\n\n|属性               | 单位 | 默认值 |是否必填|描述|\n| :----            | :---- | :---- |:---- |:---- |\n| mode             | enum（TIME_WINDOW/TOKEN_BUCKET） | TIME_WINDOW |否|限流模式,目前可选时间窗口和令牌桶|\n| rate             | int     | 无 |是|时间窗口模式表示每个时间窗口内的请求数量、令牌桶模式表示每秒的令牌生产数量|\n| rateInterval     | String | 1s |否|用于时间窗口模式，表示时间窗口|\n| rateExpression   | String | 无 |否|通过 EL 表达式从 Spring Config 上下文中获取 `rate` 的值，`rateExpression` 的优先级比 `rate` 高|\n| fallbackFunction | String | 无 |否|自定义触发限流时的降级策略方法，默认触发限流会抛 `RateLimitException` 异常|\n| customKeyFunction | String | 无 |否|自定义获取限流 `key` 的方法|\n| bucketCapacity    | int | 无 |否|用于令牌桶模式，表示令牌桶的桶的大小，这个参数控制了请求最大并发数|\n| bucketCapacityExpression | String | 无 |否|通过 EL 表达式从 Spring Config 上下文中获取 `bucketCapacity` 的值，`bucketCapacityExpression` 的优先级比 `bucketCapacity` 高|\n| requestedTokens   | int | 1 |否|用于令牌桶模式，表示每次获取的令牌数，一般不用改动这个参数值，除非你知道你在干嘛|\n\n@RateLimit 注解可以添加到任意被 spring 管理的 bean 上，不局限于 controller ，service 、repository 也可以。在最基础限流功能使用上，以上三个步骤就已经完成了。\n\n#### 1.3.2 限流的粒度，限流 key\n\n限流的粒度是通过限流的 key 来做的，在最基础的设置下，限流的 key 默认是通过方法名称拼出来的，规则如下：\n\n```properties\nkey=RateLimiter_ + 类名 + 方法名\n```\n\n除了默认的 key 策略，ratelimiter-spring-boot-starter 充分考虑了业务限流时的复杂性，提供了多种方式。结合业务特征，达到更细粒度的限流控制。\n\n#### 1.3.3 触发限流后的行为\n\n默认触发限流后 程序会返回一个 http 状态码为 429 的响应，响应值如下：\n\n```json\n{\n  \"code\": 429,\n  \"msg\": \"Too Many Requests\"\n}\n```\n\n同时，响应的 header 里会携带一个 Retry-After 的时间值，单位 s，用来告诉调用方多久后可以重试。当然这一切都是可以自定义的，进阶用法可以继续往下看\n\n## 2、进阶用法\n\n### 2.1、自定义限流的 key\n\n自定义限流 key 有三种方式，当自定义限流的 key 生效时，限流的 key 就变成了（默认的 key + 自定义的 key）。下面依次给出示例\n\n#### 2.1.1、@RateLimitKey 的方式\n\n```java\n\n@RestController\n@RequestMapping(\"/test\")\npublic class TestController {\n\n    @GetMapping(\"/get\")\n    @RateLimit(rate = 5, rateInterval = \"10s\")\n    public String get(@RateLimitKey String name) {\n        return \"get\";\n    }\n}\n```\n\n@RateLimitKey 注解可以放在方法的入参上，要求入参是基础数据类型，上面的例子，如果 name = kl。那么最终限流的 key 如下：\n\n```properties\nkey=RateLimiter_com.taptap.ratelimiter.web.TestController.get-kl\n```\n\n#### 2.1.2、指定 keys 的方式\n\n```java\n\n@RestController\n@RequestMapping(\"/test\")\npublic class TestController {\n\n    @GetMapping(\"/get\")\n    @RateLimit(rate = 5, rateInterval = \"10s\", keys = {\"#name\"})\n    public String get(String name) {\n        return \"get\";\n    }\n\n    @GetMapping(\"/hello\")\n    @RateLimit(rate = 5, rateInterval = \"10s\", keys = {\"#user.name\", \"user.id\"})\n    public String hello(User user) {\n        return \"hello\";\n    }\n}\n```\n\nkeys 这个参数比 @RateLimitKey 注解更智能，基本可以包含 @RateLimitKey 的能力，只是简单场景下，使用起来没有 @RateLimitKey 那么便捷。keys 的语法来自 spring 的 `Spel`\n，可以获取对象入参里的属性，支持获取多个，最后会拼接起来。使用过 spring-cache 的同学可能会更加熟悉 如果不清楚 `Spel` 的用法，可以参考 spring-cache 的注解文档\n\n#### 2.1.3、自定义 key 获取函数\n\n```java\n\n@RestController\n@RequestMapping(\"/test\")\npublic class TestController {\n\n    @GetMapping(\"/get\")\n    @RateLimit(rate = 5, rateInterval = \"10s\", customKeyFunction = \"keyFunction\")\n    public String get(String name) {\n        return \"get\";\n    }\n\n    public String keyFunction(String name) {\n        return \"keyFunction\" + name;\n    }\n}\n```\n\n当 @RateLimitKey 和 keys 参数都没法满足时，比如入参的值是一个加密的值，需要解密后根据相关明文内容限流。可以通过在同一类里自定义获取 key 的函数，这个函数要求和被限流的方法入参一致，返回值为 String\n类型。返回值不能为空，为空时，会回退到默认的 key 获取策略。\n\n### 2.2、自定义限流后的行为\n\n#### 2.2.1、配置响应内容\n\n```properties\nspring.ratelimiter.enabled=true\nspring.ratelimiter.response-body=Too Many Requests\nspring.ratelimiter.status-code=509\n```\n\n添加如上配置后，触发限流时，http 的状态码就变成了 509 。响应的内容变成了 Too Many Requests 了\n\n#### 2.2.2、自定义限流触发异常处理器\n\n默认的触发限流后，限流器会抛出一个异常，限流器框架内定义了一个异常处理器来处理。自定义限流触发处理器，需要先禁用系统默认的限流触发处理器，禁用方式如下：\n\n```properties\nspring.ratelimiter.exceptionHandler.enable=false\n```\n\n然后在项目里添加自定义处理器，如下：\n\n```java\n\n@ControllerAdvice\npublic class RateLimitExceptionHandler {\n\n    private final RateLimiterProperties limiterProperties;\n\n    public RateLimitExceptionHandler(RateLimiterProperties limiterProperties) {\n        this.limiterProperties = limiterProperties;\n    }\n\n    @ExceptionHandler(value = RateLimitException.class)\n    @ResponseBody\n    public String exceptionHandler(HttpServletResponse response, RateLimitException e) {\n        response.setStatus(limiterProperties.getStatusCode());\n        response.setHeader(\"Retry-After\", String.valueOf(e.getRetryAfter()));\n        return limiterProperties.getResponseBody();\n    }\n}\n```\n\n#### 2.2.3、自定义触发限流处理函数，限流降级\n\n```java\n\n@RequestMapping(\"/test\")\npublic class TestController {\n\n    @GetMapping(\"/get\")\n    @RateLimit(rate = 5, rateInterval = \"10s\", fallbackFunction = \"getFallback\")\n    public String get(String name) {\n        return \"get\";\n    }\n\n    public String getFallback(String name) {\n        return \"Too Many Requests\" + name;\n    }\n\n}\n```\n\n这种方式实现和使用和 2.1.3、自定义 key 获取函数类似。但是多一个要求，返回值的类型需要和原限流函数的返回值类型一致，当触发限流时，框架会调用 fallbackFunction 配置的函数执行并返回，达到限流降级的效果\n\n### 2.3、 动态设置限流大小\n\n#### 2.3.1、rateExpression 的使用\n\n从 `v1.2` 版本开始，在 `@RateLimit` 注解里新增了属性 rateExpression。该属性支持 `Spel` 表达式从 Spring 的配置上下文中获取值。 当配置了 rateExpression 后，rate\n属性的配置就不生效了。使用方式如下：\n\n```java\n    @GetMapping(\"/get2\")\n@RateLimit(rate = 2, rateInterval = \"10s\", rateExpression = \"${spring.ratelimiter.max}\")\npublic String get2(){\n        return\"get\";\n        }\n```\n\n集成 apollo 等配置中心后，可以做到限流大小的动态调整在线热更。\n\n### 2.4、直接使用限流器服务-`RateLimiterService`\n从 `v1.3` 版本开始，限流器框架内部提供了一个限流器服务，可以直接使用。当使用 `RateLimiterService` 后，则不用关心`限流注解`的逻辑了，所有限流逻辑都可以高度定制，如下：\n```java\n@RestController\n@RequestMapping(\"/test\")\npublic class TestController {\n\n    @Autowired\n    private RateLimiterService limiterService;\n\n    @GetMapping(\"/limiterService/time-window\")\n    public String limiterServiceTimeWindow(String key) {\n        Rule rule = new Rule(Mode.TIME_WINDOW); // 限流策略,设置为时间窗口\n        rule.setKey(key); //限流的 key\n        rule.setRate(5); //限流的速率\n        rule.setRateInterval(10); //时间窗口大小，单位为秒\n        Result result = limiterService.isAllowed(rule);\n        if (result.isAllow()) { //如果允许访问\n            return \"ok\";\n        } else {\n            //触发限流\n            return \"no\";\n        }\n    }\n\n    @GetMapping(\"/limiterService/token-bucket\")\n    public String limiterServiceTokenBucket(String key) {\n        Rule rule = new Rule(Mode.TOKEN_BUCKET); // 限流策略,设置为令牌桶\n        rule.setKey(key); //限流的 key\n        rule.setRate(5); //每秒产生的令牌数\n        rule.setBucketCapacity(10); //令牌桶容量\n        rule.setRequestedTokens(1); //请求的令牌数\n        Result result = limiterService.isAllowed(rule);\n        if (result.isAllow()) { //如果允许访问\n            return \"ok\";\n        } else {\n            //触发限流\n            return \"no\";\n        }\n    }\n}\n```\n\n## 3、集成示例、测验\n\n### 3.1、集成测验\n\n启动 src/test/java/com/taptap/ratelimiter/Application.java 后，访问 http://localhost:8080/swagger-ui.html\n\n### 3.2、压力测试\n\n- 压测工具 wrk： https://github.com/wg/wrk\n- 测试环境: 8 核心 cpu ，jvm 内存给的 -Xms2048m -Xmx2048m ，链接的本地的 redis\n\n```shell\n#压测数据\nkldeMacBook-Pro-6:ratelimiter-spring-boot-starter kl$ wrk -t16 -c100 -d15s --latency http://localhost:8080/test/wrk\nRunning 15s test @ http://localhost:8080/test/wrk\n  16 threads and 100 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     6.18ms   20.70ms 281.21ms   98.17%\n    Req/Sec     1.65k   307.06     2.30k    76.44%\n  Latency Distribution\n     50%    3.57ms\n     75%    4.11ms\n     90%    5.01ms\n     99%  115.48ms\n  389399 requests in 15.03s, 43.15MB read\nRequests/sec:  25915.91\nTransfer/sec:      2.87MB\n```\n\n压测下，所有流量都过限流器，qps 可以达到 2w+。\n\n## 4、版本更新\n\n### 4.1、（v1.1.1）版本更新内容\n\n- 1、触发限流时，header 的 Retry-After 值，单位由 ms ，调整成了 s\n\n### 4.2、（v1.2）版本更新内容\n\n- 1、触发限流时，响应的类型从 `text/plain` 变成了 `application/json`\n- 2、优化了限流的 lua 脚本，将原来的两步 lua 脚本请求，合并成了一个，减少了和 redis 的交互\n- 3、限流的时间窗口大小，支持 `Spel` 从 Spring 的配置上下文中获取，结合 `apollo` 等配置中心后，支持规则的动态下发热更新\n\n### 4.3、（v1.3）版本更新内容\n\n- 1、配置策略变化，不在从应用的上下文中获取 Redis 数据源，而是必须配置。但是配置的数据源在 Spring 上下文中声明了 `rateLimiterRedissonBeanName`，应用也可以获取使用\n- 2、代码重构，新增了`令牌桶`的限流策略支持\n- 3、抽象了限流器服务 `RateLimiterService`，并在 Spring 上下文中声明了，应用可以直接注入使用\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaptap%2Fratelimiter-spring-boot-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftaptap%2Fratelimiter-spring-boot-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaptap%2Fratelimiter-spring-boot-starter/lists"}