# lightweight-rate-limiter
**Repository Path**: Redick01/lightweight-rate-limiter
## Basic Information
- **Project Name**: lightweight-rate-limiter
- **Description**: 基于分布式配置中心配置限流参数的Redis轻量级分布式限流组件 轻量级,限流核心基于Redis Lua脚本实现 支持令牌桶、漏桶、滑动窗口限流算法 支持Zookeeper、Naco、Apollo、Consul配置中心配置限流指标 支持Spel表达式,能够实现多维度限流 限流情况可观测性支持
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: https://github.com/Redick01/lightweight-rate-limiter
- **GVP Project**: No
## Statistics
- **Stars**: 8
- **Forks**: 6
- **Created**: 2023-01-30
- **Last Updated**: 2025-08-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 基于分布式配置中心的Redis的轻量级分布式限流组件
## 特点
- 轻量级,限流核心基于Redis Lua脚本实现
- 支持令牌桶、漏桶、滑动窗口限流算法
- 支持Zookeeper、Naco、Apollo、Consul配置中心配置限流指标
- 支持Spel表达式,能够实现多维度限流
- 限流情况可观测性支持
## 算法介绍
### 令牌桶算法
- 系统以恒定的速率产生令牌,然后将令牌放入令牌桶中。
- 令牌桶有一个容量,当令牌桶满了的时候,再向其中放入的令牌就会被丢弃。
- 每次一个请求过来,需要从令牌桶中获取一个令牌,如果有令牌,则提供服务;如果没有令牌,则拒绝服务。

特点:
- 能够限制调用的平均速率
- 允许一定程度的突发调用
### 漏桶算法
水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出(拒绝服务)

缺点:
- 无法面对突发的大流量
比如:请求处理速率为1000/s,容量为5000,来了一波2000/s的请求持续10s,那么5s后会有大量的请求被丢弃。
- 无法有效利用网络资源
比如:服务器的处理能力是1000/s,连续5s每秒请求量分别为1200、1300、1200、500、800,平均下来QPS也是1000/s,但是有700个请求被拒绝。
### 计数器滑动窗口算法
说到`计数器滑动窗口算法`,先来介绍一下`计数器固定窗口算法`,该算法如下:
通过维护一个单位时间内的计数值,每当一个请求通过时,就将计数值加1,当计数值超过预先设定的阈值时,就拒绝单位时间内的其他请求。如果单位时间已经结束,则将计数器清零,开启下一轮的计数。

`计数器固定窗口算法`有一个缺点,就是在窗口切换时可能会产生两倍于阈值流量的请求,如下图:

`计数器滑动窗口算法`是`计数器固定窗口算法`的改进,解决了固定窗口切换时可能会产生两倍于阈值流量请求的缺点

### 限流算法总结
- 计数器固定窗口算法:
实现简单,容易理解,但是在窗口切换时可能会产生两倍于阈值流量的请求。
- 计数器滑动窗口算法:
作为计数器固定窗口算法的一种改进,有效解决了窗口切换时可能会产生两倍于阈值流量请求的问题
- 漏桶算法:
能够对流量起到整流的作用,让随机不稳定的流量以固定的速率流出,但是不能解决流量突发的问题。
- 令牌桶算法:
作为漏桶算法的一种改进,除了能够起到平滑流量的作用,还允许一定程度的流量突发。
## 快速开始
rate-limiter限流组件核心配置介绍,参考的配置格式为properties,yml或yaml格式同理
- algorithmName:支持的限流算法,配置值如下:
1. concurrent_request_rate_limiter(高并发限流算法)
2. token_bucket_rate_limiter(令牌桶算法)
3. leaky_bucket_rate_limiter(漏桶算法)
4. sliding_window_rate_limiter(滑动窗口算法)
- redis-config.url:redis配置
- redis-config.database:redis db编号
- config-type:配置文件格式,支持properties,yaml,yml
- rateLimiterKey:限流的key,与接口注解的key保持一致
- expressionType:表达式类型,多维度限流的支持,默认是spel表达式,目前只支持spel表达式
```properties
# redis库编号
spring.ratelimiter.redis-config.database=0
# redis地址
spring.ratelimiter.redis-config.url=127.0.0.1
spring.ratelimiter.config-type=properties
# 限流算法名
spring.ratelimiter.rate-limiter-configs[0].algorithmName=sliding_window_rate_limiter
# 容量
spring.ratelimiter.rate-limiter-configs[0].capacity=200
# 令牌生成速率
spring.ratelimiter.rate-limiter-configs[0].rate=200
# 限流key
spring.ratelimiter.rate-limiter-configs[0].rateLimiterKey=zk-rate-test1
# 表达式 spel
spring.ratelimiter.rate-limiter-configs[0].expressionType=spel
```
### Spring Boot应用接入
可参考lightweight-rate-limiter-example/springboot-example工程
未使用分布式配置中心的springboot接入
- pom依赖应用
```xml
io.redick01.ratelimiter
ratelimiter-spring-boot-starter-common
1.0-SNAPSHOT
```
- application.yml
```yaml
server:
port: 8103
spring:
ratelimiter:
redis-config:
url: 127.0.0.1
database: 0
rate-limiter-configs:
- algorithmName: token_bucket_rate_limiter
rateLimiterKey : key1
capacity: 1000
rate: 200
expressionType: spel
- algorithmName: token_bucket_rate_limiter
rateLimiterKey: "'/Rate/spelTest:' + #args[0].userId"
capacity: 1000
rate: 200
expressionType: spel
```
- 代码中使用
```java
@RestController()
public class TestController {
@GetMapping("/Rate/rateTest")
@RateLimiter(key = "key1", clazz = RateLimiterResponse.class)
public String rateTest() {
return "111";
}
@PostMapping("/Rate/spelTest")
@RateLimiter(key = "'/Rate/spelTest:' + #args[0].userId", clazz = RateLimiterResponse1.class)
public Response spelTest(@RequestBody Request request) {
return new Response<>("0000", "成功", "success");
}
}
```
### Zookeeper配置中心的Spring Boot应用接入
可参考lightweight-rate-limiter-example/zookeeper-example工程
- pom依赖引用
```xml
io.redick01.ratelimiter
ratelimiter-spring-boot-starter-zookeeper
1.0-SNAPSHOT
```
- application.yml配置
```yaml
server:
port: 8104
spring:
ratelimiter:
zookeeper:
zk-connect-str: 127.0.0.1:2181
root-node: /configserver/userproject
node: ratelimit-group
config-version: 1.0.0
config-type: properties
```
- Zookeeper配置中心配置文件ratelimit-group配置内容:
```properties
# Export from zookeeper configuration group: [/configserver/userproject] - [1.0.0] - [ratelimit-group].
spring.ratelimiter.config-type=properties
# 限流算法名
spring.ratelimiter.rate-limiter-configs[0].algorithmName=sliding_window_rate_limiter
# 容量
spring.ratelimiter.rate-limiter-configs[0].capacity=200
# 令牌生成速率
spring.ratelimiter.rate-limiter-configs[0].rate=200
# 限流key
spring.ratelimiter.rate-limiter-configs[0].rateLimiterKey=zk-rate-test1
# 表达式 spel
spring.ratelimiter.rate-limiter-configs[0].spelParserType=spel
spring.ratelimiter.rate-limiter-configs[1].algorithmName=token_bucket_rate_limiter
spring.ratelimiter.rate-limiter-configs[1].capacity=1000
spring.ratelimiter.rate-limiter-configs[1].rate=300
spring.ratelimiter.rate-limiter-configs[1].rateLimiterKey='/zk-rate/test2:' + #args[0].userId
spring.ratelimiter.rate-limiter-configs[1].expressionType=spel
# redis库编号
spring.ratelimiter.redis-config.database=0
# redis地址
spring.ratelimiter.redis-config.url=127.0.0.1
```
- 代码中使用
在需要限流的接口上声明`@RateLimiter`注解,并指定key和限流后降级的class。
```java
@RestController
@Slf4j
public class ZookeeperTestController {
@PostMapping("/zk-rate/test1")
@RateLimiter(key = "zk-rate-test1", clazz = RateLimiterResponse1.class)
public Response test1(@RequestBody Request request) {
log.info("请求参数是:{}", request.toString());
return new Response<>("0000", "成功", "success");
}
@PostMapping("/zk-rate/test2")
@RateLimiter(key = "'/zk-rate/test2:' + #args[0].userId", clazz = RateLimiterResponse1.class)
public Response test2(@RequestBody Request request) {
log.info("请求参数是:{}", request.toString());
return new Response<>("0000", "成功", "success");
}
}
```
### Zookeeper配置中心的Spring Cloud应用接入
可参考lightweight-rate-limiter-example/cloud-zookeeper-example工程
- pom依赖引用
```xml
io.redick01.ratelimiter
ratelimiter-spring-boot-starter-cloud-zookeeper
1.0-SNAPSHOT
```
- bootstrap.yml配置
```yaml
server:
port: 8105
spring:
application:
name: cloud-zookeeper-example
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
config:
root: /configserver/userproject/1.0.0
enabled: true
profiles:
active: dev
```
- Zookeeper配置中心配置文件cloud-zookeeper-example,dev.properties配置内容:
```properties
# Zookeeper配置示例
# Export from zookeeper configuration group: [/configserver/userproject] - [1.0.0] - [ratelimit-group].
spring.ratelimiter.config-type=properties
# 限流算法名
spring.ratelimiter.rate-limiter-configs[0].algorithmName=sliding_window_rate_limiter
# 容量
spring.ratelimiter.rate-limiter-configs[0].capacity=200
spring.ratelimiter.rate-limiter-configs[0].expressionType=spel
# 令牌生成速率
spring.ratelimiter.rate-limiter-configs[0].rate=200
# 限流key
spring.ratelimiter.rate-limiter-configs[0].rateLimiterKey=zk-rate-test1
spring.ratelimiter.rate-limiter-configs[1].algorithmName=token_bucket_rate_limiter
spring.ratelimiter.rate-limiter-configs[1].capacity=1000
spring.ratelimiter.rate-limiter-configs[1].expressionType=spel
spring.ratelimiter.rate-limiter-configs[1].rate=300
spring.ratelimiter.rate-limiter-configs[1].rateLimiterKey='/zk-rate/test2:' + #args[0].userId
# redis库编号
spring.ratelimiter.redis-config.database=0
# redis地址
spring.ratelimiter.redis-config.url=127.0.0.1
```
### Nacos配置中心的Spring Boot应用接入
可参考lightweight-rate-limiter-example/nacos-example工程
- pom依赖引用
```xml
io.redick01.ratelimiter
ratelimiter-spring-boot-starter-nacos
1.0-SNAPSHOT
```
- application.yml配置
```yaml
server:
port: 8106
spring:
application:
name: nacos-example
profiles:
active: dev
nacos:
config:
server-addr: 127.0.0.1:8848
type: yaml
data-id: nacos-example-dev.yml
auto-refresh: true
group: DEFAULT_GROUP
bootstrap:
enable: true
log-enable: true
```
- Nacos配置中心配置文件nacos-example-dev.yml配置内容:
```yaml
# nacos配置示例
spring:
ratelimiter:
redis-config:
url: 127.0.0.1
database: 0
rate-limiter-configs:
- algorithmName: token_bucket_rate_limiter
rateLimiterKey : nacos-rate-test1
capacity: 100
rate: 10
expressionType: spel
- algorithmName: sliding_window_rate_limiter
rateLimiterKey: "'/nacos-rate/test2:' + #args[0].userId"
capacity: 1000
rate: 200
expressionType: spel
```
### Nacos配置中心的Spring Cloud应用接入
可参考lightweight-rate-limiter-example/cloud-nacos-example工程
- pom依赖引用
```xml
io.redick01.ratelimiter
ratelimiter-spring-boot-starter-cloud-nacos
1.0-SNAPSHOT
```
- bootstrap.yml配置
```yaml
server:
port: 8107
spring:
application:
name: cloud-nacos-example
profiles:
active: dev
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
refresh-enabled: true
extension-configs:
- dataId: cloud-nacos-example-dev.yml
group: DEFAULT_GROUP
refresh: true
```
- Nacos配置中心配置文件cloud-nacos-example-dev.yml配置内容:
```yaml
# nacos配置示例
spring:
ratelimiter:
redis-config:
url: 127.0.0.1
database: 0
rate-limiter-configs:
- algorithmName: token_bucket_rate_limiter
rateLimiterKey : nacos-rate-test1
capacity: 1000
rate: 10
expressionType: spel
- algorithmName: sliding_window_rate_limiter
rateLimiterKey: "'/nacos-rate/test2:' + #args[0].userId"
capacity: 1000
rate: 200
expressionType: spel
```
### Apollo配置中心的Spring Boot应用接入
可参考lightweight-rate-limiter-example/apollo-example工程
- pom依赖引用
```xml
io.redick01.ratelimiter
ratelimiter-spring-boot-starter-apollo
1.0-SNAPSHOT
```
- application.yml配置
```yaml
server:
port: 8108
apollo:
bootstrap:
namespaces: rate-limiter.yml
enabled: true
meta: http://127.0.0.1:8080
config-service: http://127.0.0.1:8080
app:
id: apollo-example
spring:
application:
name: apollo-example
ratelimiter:
apollo:
namespace: rate-limiter.yml
```
- Apollo配置中心配置文件rate-limiter.yml配置内容:
```yaml
# apollo配置示例
spring:
ratelimiter:
redis-config:
url: 127.0.0.1
database: 0
rate-limiter-configs:
- algorithmName: token_bucket_rate_limiter
rateLimiterKey : apollo-rate-test1
capacity: 1000
rate: 10
expressionType: spel
- algorithmName: sliding_window_rate_limiter
rateLimiterKey: "'/apollo-rate/test2:' + #args[0].userId"
capacity: 1000
rate: 200
expressionType: spel
```
## 可观测性
### Prometheus + Grafana可观测性支持
- pom配置
增加prometheus依赖
```xml
io.micrometer
micrometer-registry-prometheus
1.6.4
org.springframework.boot
spring-boot-starter-actuator
```
- application.yml配置
```yaml
# 对接prometheus
management:
metrics:
tags:
application: ${spring.application.name}
endpoints:
web:
exposure:
include: '*'
```
- Prometheus端服务发现配置
修改prometheus.yml文件,在`scrape_configs`标签下增加指标拉取配置`job`ratelimiter--zookeeper-demo-prometheus
```yaml
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=` to any timeseries scraped from this config.
- job_name: "prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ["localhost:9090"]
- job_name: "ratelimiter--zookeeper-demo-prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
scrape_interval: 5s
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ["localhost:8104"]
```
- 效果图

## 压测
### 令牌桶算法
- 限流指标:
1. rate:200(每秒生成200个令牌)
2. capacity:200(令牌桶容量200个)
- 压测指标:
1. 并发:200
2. 循环次数:2
- 压测结果:
令牌生成速率为200/s,令牌桶容量200,并发200,循环两次,总共400个请求,压测会有50%的请求被限流调,与压测结果一致

### 漏桶算法
- 限流指标:
1. rate:200(每秒通过200个请求)
2. capacity:200(桶最大容量200个)
- 压测指标:
1. 并发:200
2. 循环次数:2
- 压测结果:
每秒通过200个请求,桶的容量为200,压测的指标为200并发循环两次,第一个循环将桶装满,第二个循环全部被限流,与压测结果一致

### 滑动窗口算法
- 限流指标:
1. rate:200(滑动窗口流量阈值,每秒通过200个请求)
2. capacity:200(限流窗口总请求数200个)
3. 滑动窗口数 = capacity / rate
- 压测指标:
1. 并发:200
2. 循环次数:2
- 压测结果:
滑动窗口阈值200,限流窗口200,可知限流窗口数量为1个,一个限流窗口就能通过200请求,所以会有200个请求被限流,符合压测结果
