throttled-py 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- throttled_py-1.0.0/LICENSE +21 -0
- throttled_py-1.0.0/PKG-INFO +297 -0
- throttled_py-1.0.0/README.md +279 -0
- throttled_py-1.0.0/pyproject.toml +90 -0
- throttled_py-1.0.0/throttled/__init__.py +56 -0
- throttled_py-1.0.0/throttled/constants.py +21 -0
- throttled_py-1.0.0/throttled/exceptions.py +36 -0
- throttled_py-1.0.0/throttled/rate_limter/__init__.py +39 -0
- throttled_py-1.0.0/throttled/rate_limter/base.py +221 -0
- throttled_py-1.0.0/throttled/rate_limter/fixed_window.py +112 -0
- throttled_py-1.0.0/throttled/rate_limter/gcra.py +248 -0
- throttled_py-1.0.0/throttled/rate_limter/leaking_bucket.py +158 -0
- throttled_py-1.0.0/throttled/rate_limter/lua/fixed_window.lua +10 -0
- throttled_py-1.0.0/throttled/rate_limter/lua/gcra.lua +49 -0
- throttled_py-1.0.0/throttled/rate_limter/lua/gcra_peek.lua +39 -0
- throttled_py-1.0.0/throttled/rate_limter/lua/leaking_bucket.lua +27 -0
- throttled_py-1.0.0/throttled/rate_limter/lua/sliding_window.lua +21 -0
- throttled_py-1.0.0/throttled/rate_limter/lua/token_bucket.lua +27 -0
- throttled_py-1.0.0/throttled/rate_limter/sliding_window.py +140 -0
- throttled_py-1.0.0/throttled/rate_limter/token_bucket.py +157 -0
- throttled_py-1.0.0/throttled/store/__init__.py +23 -0
- throttled_py-1.0.0/throttled/store/base.py +140 -0
- throttled_py-1.0.0/throttled/store/memory.py +179 -0
- throttled_py-1.0.0/throttled/store/redis.py +95 -0
- throttled_py-1.0.0/throttled/store/redis_pool.py +215 -0
- throttled_py-1.0.0/throttled/throttled.py +109 -0
- throttled_py-1.0.0/throttled/types.py +13 -0
- throttled_py-1.0.0/throttled/utils.py +59 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 crayon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: throttled-py
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 🔧 High-performance Python rate limiting library with multiple algorithms (Fixed Window, Sliding Window, Token Bucket, Leaky Bucket & GCRA) and storage backends (Redis, In-Memory).
|
|
5
|
+
Author: ZhuoZhuoCrayon
|
|
6
|
+
Author-email: crayon.ccxx@gmail.com
|
|
7
|
+
Requires-Python: >=3.8,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Dist: redis (>=5.2.1,<6.0.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
<h1 align="center">throttled-py</h1>
|
|
19
|
+
<p align="center">
|
|
20
|
+
<em>🔧 支持多种算法(固定窗口,滑动窗口,令牌桶,漏桶 & GCRA)及存储(Redis、内存)的高性能 Python 限流库。</em>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<a href="https://github.com/ZhuoZhuoCrayon/throttled-py">
|
|
25
|
+
<img src="https://badgen.net/badge/python/%3E=3.8/green?icon=github" alt="Python">
|
|
26
|
+
</a>
|
|
27
|
+
<a href="https://github.com/ZhuoZhuoCrayon/throttled-py">
|
|
28
|
+
<img src="https://codecov.io/gh/ZhuoZhuoCrayon/throttled-py/graph/badge.svg" alt="Coverage Status">
|
|
29
|
+
</a>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
[English Documents Available](./README_EN.md) | 简体中文
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## :rocket: 功能
|
|
36
|
+
|
|
37
|
+
### 1)存储
|
|
38
|
+
|
|
39
|
+
| Redis | 内存(线程安全) |
|
|
40
|
+
|--------------------|--------------------|
|
|
41
|
+
| :white_check_mark: | :white_check_mark: |
|
|
42
|
+
|
|
43
|
+
### 2)限流算法
|
|
44
|
+
|
|
45
|
+
| [固定窗口](https://github.com/ZhuoZhuoCrayon/throttled-py/tree/main/docs/basic#21-%E5%9B%BA%E5%AE%9A%E7%AA%97%E5%8F%A3%E8%AE%A1%E6%95%B0%E5%99%A8) | [滑动窗口](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#22-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3) | [令牌桶](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#23-%E4%BB%A4%E7%89%8C%E6%A1%B6) | [漏桶](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#24-%E6%BC%8F%E6%A1%B6) | [通用信元速率算法(Generic Cell Rate Algorithm, GCRA)](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#25-gcra) |
|
|
46
|
+
|------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
|
|
47
|
+
| :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
|
48
|
+
|
|
49
|
+
我们提供以上限流算法的原理分析,你可以点击具体的限流算法跳转到相应的介绍。
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## :beginner: 安装
|
|
53
|
+
|
|
54
|
+
```shell
|
|
55
|
+
$ pip install throttled-py
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## :memo: 使用
|
|
60
|
+
|
|
61
|
+
### 1)基础
|
|
62
|
+
|
|
63
|
+
#### 通用 API
|
|
64
|
+
|
|
65
|
+
* `limit`:消耗请求,返回 [**RateLimitResult**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#1ratelimitresult)。
|
|
66
|
+
* `peek`:获取指定 Key 的限流器状态,返回 [**RateLimitState**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#2ratelimitstate)。
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from throttled import Throttled
|
|
70
|
+
|
|
71
|
+
# 参数全部缺省时,默认初始化一个基于「内存」、每秒允许通过 60 个请求、使用「令牌桶算法」的限流器。
|
|
72
|
+
throttle = Throttled()
|
|
73
|
+
|
|
74
|
+
# 消耗 1 次请求,输出:RateLimitResult(limited=False,
|
|
75
|
+
# state=RateLimitState(limit=60, remaining=59, reset_after=1))
|
|
76
|
+
print(throttle.limit("key", 1))
|
|
77
|
+
# 获取限流器状态,输出:RateLimitState(limit=60, remaining=59, reset_after=1)
|
|
78
|
+
print(throttle.peek("key"))
|
|
79
|
+
|
|
80
|
+
# 消耗 60 次请求,触发限流,输出:RateLimitResult(limited=True,
|
|
81
|
+
# # state=RateLimitState(limit=60, remaining=59, reset_after=1))
|
|
82
|
+
print(throttle.limit("key", 60))
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### 作为装饰器
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from throttled import Throttled, rate_limter, exceptions
|
|
89
|
+
|
|
90
|
+
# 创建一个每秒允许通过 1 次的限流器。
|
|
91
|
+
@Throttled(key="/ping", quota=rate_limter.per_min(1))
|
|
92
|
+
def ping() -> str:
|
|
93
|
+
return "ping"
|
|
94
|
+
|
|
95
|
+
ping()
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
ping() # 当触发限流时,抛出 LimitedError。
|
|
99
|
+
except exceptions.LimitedError as exc:
|
|
100
|
+
print(exc) # Rate limit exceeded: remaining=0, reset_after=60
|
|
101
|
+
# 在异常中获取限流结果:RateLimitResult(limited=True,
|
|
102
|
+
# state=RateLimitState(limit=1, remaining=0, reset_after=60))
|
|
103
|
+
print(exc.rate_limit_result)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 2)指定存储后端
|
|
107
|
+
|
|
108
|
+
#### Redis
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from throttled import RateLimiterType, Throttled, rate_limter, store
|
|
112
|
+
|
|
113
|
+
@Throttled(
|
|
114
|
+
key="/api/products",
|
|
115
|
+
using=RateLimiterType.TOKEN_BUCKET.value,
|
|
116
|
+
quota=rate_limter.per_min(1),
|
|
117
|
+
# 🌟 使用 Redis 作为存储后端
|
|
118
|
+
store=store.RedisStore(server="redis://127.0.0.1:6379/0", options={"PASSWORD": ""}),
|
|
119
|
+
)
|
|
120
|
+
def products() -> list:
|
|
121
|
+
return [{"name": "iPhone"}, {"name": "MacBook"}]
|
|
122
|
+
|
|
123
|
+
products()
|
|
124
|
+
# raise LimitedError: Rate limit exceeded: remaining=0, reset_after=60
|
|
125
|
+
products()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Memory
|
|
129
|
+
|
|
130
|
+
如果你希望在程序的不同位置,对同一个 Key 进行限流,请确保 `Throttled` 接收到的是同一个 `MemoryStore`,并使用一致的 [`Quota`](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#3quota)。
|
|
131
|
+
|
|
132
|
+
下方样例使用内存作为存储后端,并在 `ping`、`pong` 上对同一个 Key 进行限流:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from throttled import Throttled, rate_limter, store
|
|
136
|
+
|
|
137
|
+
# 🌟 使用 Memory 作为存储后端
|
|
138
|
+
mem_store = store.MemoryStore()
|
|
139
|
+
|
|
140
|
+
@Throttled(key="ping-pong", quota=rate_limter.per_min(1), store=mem_store)
|
|
141
|
+
def ping() -> str:
|
|
142
|
+
return "ping"
|
|
143
|
+
|
|
144
|
+
@Throttled(key="ping-pong", quota=rate_limter.per_min(1), store=mem_store)
|
|
145
|
+
def pong() -> str:
|
|
146
|
+
return "pong"
|
|
147
|
+
|
|
148
|
+
ping()
|
|
149
|
+
# raise LimitedError: Rate limit exceeded: remaining=0, reset_after=60
|
|
150
|
+
pong()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 3)指定限流算法
|
|
154
|
+
|
|
155
|
+
通过 **`using`** 参数指定限流算法,支持算法如下:
|
|
156
|
+
|
|
157
|
+
* [固定窗口](https://github.com/ZhuoZhuoCrayon/throttled-py/tree/main/docs/basic#21-%E5%9B%BA%E5%AE%9A%E7%AA%97%E5%8F%A3%E8%AE%A1%E6%95%B0%E5%99%A8):`RateLimiterType.FIXED_WINDOW.value`
|
|
158
|
+
* [滑动窗口](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#22-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3):`RateLimiterType.SLIDING_WINDOW.value`
|
|
159
|
+
* [令牌桶](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#23-%E4%BB%A4%E7%89%8C%E6%A1%B6):`RateLimiterType.TOKEN_BUCKET.value`
|
|
160
|
+
* [漏桶](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#24-%E6%BC%8F%E6%A1%B6):`RateLimiterType.LEAKING_BUCKET.value`
|
|
161
|
+
* [通用信元速率算法(Generic Cell Rate Algorithm, GCRA)](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#25-gcra):`RateLimiterType.GCRA.value`
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from throttled import RateLimiterType, Throttled, rate_limter, store
|
|
165
|
+
|
|
166
|
+
throttle = Throttled(
|
|
167
|
+
# 🌟指定限流算法
|
|
168
|
+
using=RateLimiterType.FIXED_WINDOW.value,
|
|
169
|
+
quota=rate_limter.per_min(1),
|
|
170
|
+
store=store.MemoryStore()
|
|
171
|
+
)
|
|
172
|
+
assert throttle.limit("key", 2).limited is True
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 4)指定容量
|
|
176
|
+
|
|
177
|
+
#### 快捷创建方式
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from throttled import rate_limter
|
|
181
|
+
|
|
182
|
+
rate_limter.per_sec(60) # 60 / sec
|
|
183
|
+
rate_limter.per_min(60) # 60 / min
|
|
184
|
+
rate_limter.per_hour(60) # 60 / hour
|
|
185
|
+
rate_limter.per_day(60) # 60 / day
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### 调整突发限制
|
|
189
|
+
|
|
190
|
+
通过 **`burst`** 参数,可以调节限流对象处理突发流量的能力 ,对以下算法有效:
|
|
191
|
+
|
|
192
|
+
* `TOKEN_BUCKET`
|
|
193
|
+
* `LEAKING_BUCKET`
|
|
194
|
+
* `GCRA`
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from throttled import rate_limter
|
|
198
|
+
|
|
199
|
+
# 允许突发处理 120 个请求
|
|
200
|
+
# 未指定 burst 时,默认设置为 limit 传入值
|
|
201
|
+
rate_limter.per_min(60, burst=120)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### 自定义配额
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from datetime import timedelta
|
|
208
|
+
from throttled.rate_limter import Quota, Rate
|
|
209
|
+
|
|
210
|
+
# 两分钟一共允许 120 个请求,允许突发处理 150 个请求
|
|
211
|
+
Quota(Rate(period=timedelta(minutes=2), limit=120), burst=150)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## :gear: 数据模型与配置
|
|
215
|
+
|
|
216
|
+
### 1)RateLimitResult
|
|
217
|
+
|
|
218
|
+
RateLimitResult 表示对给定 Key 执行 `limit` 操作后返回的结果。
|
|
219
|
+
|
|
220
|
+
| 字段 | 类型 | 描述 |
|
|
221
|
+
|-----------|----------------|--------------------|
|
|
222
|
+
| `limited` | bool | 表示此次请求是否被允许通过。 |
|
|
223
|
+
| `state` | RateLimitState | 表示给定 Key 的限流器当前状态。 |
|
|
224
|
+
|
|
225
|
+
### 2)RateLimitState
|
|
226
|
+
|
|
227
|
+
RateLimitState 表示给定 Key 的限流器当前状态。
|
|
228
|
+
|
|
229
|
+
| 字段 | 类型 | 描述 |
|
|
230
|
+
|---------------|-------|---------------------------------------------------------|
|
|
231
|
+
| `limit` | int | 表示在初始状态下允许通过的最大请求数量。 |
|
|
232
|
+
| `remaining` | int | 表示在当前状态下,针对给定键允许通过的最大请求数量。 |
|
|
233
|
+
| `reset_after` | float | 表示限流器恢复到初始状态所需的时间(以秒为单位)。在初始状态下,`limit` 等于 `remaining`。 |
|
|
234
|
+
|
|
235
|
+
### 3)Quota
|
|
236
|
+
|
|
237
|
+
Quota 表示限流配额(基础速率 + 突发容量)。
|
|
238
|
+
|
|
239
|
+
| 字段 | 类型 | 描述 |
|
|
240
|
+
|---------|------|-------------------------------------------------------------------------------------|
|
|
241
|
+
| `burst` | int | 突发容量配置(可临时突破基础速率限制),仅对以下算法生效:<br />`TOEKN_BUCKET`<br />`LEAKING_BUCKET`<br />`GCRA` |
|
|
242
|
+
| `rate` | Rate | 基础速率配置。 |
|
|
243
|
+
|
|
244
|
+
### 4)Rate
|
|
245
|
+
|
|
246
|
+
Rate 表示限流速率配置((时间窗口内允许的请求量)。
|
|
247
|
+
|
|
248
|
+
| 字段 | 类型 | 描述 |
|
|
249
|
+
|----------|--------------------|----------------|
|
|
250
|
+
| `period` | datetime.timedelta | 限流时间窗口。 |
|
|
251
|
+
| `limit` | Rate | 时间窗口内允许的最大请求数。 |
|
|
252
|
+
|
|
253
|
+
### 5)Store
|
|
254
|
+
|
|
255
|
+
#### 通用参数
|
|
256
|
+
|
|
257
|
+
| 参数 | 描述 | 默认值 |
|
|
258
|
+
|-----------|-----------------------------------------------------------------------------------------------------|------------------------------|
|
|
259
|
+
| `server` | 标准的 [Redis URL](https://github.com/redis/lettuce/wiki/Redis-URI-and-connection-details#uri-syntax)。 | `"redis://localhost:6379/0"` |
|
|
260
|
+
| `options` | 存储相关配置项,见下文。 | `{}` |
|
|
261
|
+
|
|
262
|
+
#### RedisStore Options
|
|
263
|
+
|
|
264
|
+
RedisStore 基于 [redis-py](https://github.com/redis/redis-py) 提供的 Redis API 进行开发。
|
|
265
|
+
|
|
266
|
+
在 Redis 连接配置管理上,基本沿用 [django-redis](https://github.com/jazzband/django-redis) 的配置命名,减少学习成本。
|
|
267
|
+
|
|
268
|
+
| 参数 | 描述 | 默认值 |
|
|
269
|
+
|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------|
|
|
270
|
+
| `CONNECTION_FACTORY_CLASS` | ConnectionFactory 用于创建并维护 [ConnectionPool](https://redis-py.readthedocs.io/en/stable/connections.html#redis.connection.ConnectionPool)。 | `"throttled.store.ConnectionFactory"` |
|
|
271
|
+
| `CONNECTION_POOL_CLASS` | ConnectionPool 导入路径。 | `"redis.connection.ConnectionPool"` |
|
|
272
|
+
| `CONNECTION_POOL_KWARGS` | [ConnectionPool 构造参数](https://redis-py.readthedocs.io/en/stable/connections.html#connectionpool)。 | `{}` |
|
|
273
|
+
| `REDIS_CLIENT_CLASS` | RedisClient 导入路径,默认使用 [redis.client.Redis](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis)。 | `"redis.client.Redis"` |
|
|
274
|
+
| `REDIS_CLIENT_KWARGS` | [RedisClient 构造参数](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis)。 | `{}` |
|
|
275
|
+
| `PASSWORD` | 密码。 | `null` |
|
|
276
|
+
| `SOCKET_TIMEOUT` | ConnectionPool 参数。 | `null` |
|
|
277
|
+
| `SOCKET_CONNECT_TIMEOUT` | ConnectionPool 参数。 | `null` |
|
|
278
|
+
| `SENTINELS` | `(host, port)` 元组列表,哨兵模式请使用 `SentinelConnectionFactory` 并提供该配置。 | `[]` |
|
|
279
|
+
| `SENTINEL_KWARGS` | [Sentinel 构造参数](https://redis-py.readthedocs.io/en/stable/connections.html#id1)。 | `{}` |
|
|
280
|
+
|
|
281
|
+
#### MemoryStore Options
|
|
282
|
+
|
|
283
|
+
MemoryStore 本质是一个基于内存实现的,带过期时间的 [LRU Cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU) 。
|
|
284
|
+
|
|
285
|
+
| 参数 | 描述 | 默认值 |
|
|
286
|
+
|------------|-------------------------------------------|--------|
|
|
287
|
+
| `MAX_SIZE` | 最大容量,存储的键值对数量超过 `MAX_SIZE` 时,将按 LRU 策略淘汰。 | `1024` |
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
## :books: Version History
|
|
291
|
+
|
|
292
|
+
[See CHANGELOG.md](./CHANGELOG.md)
|
|
293
|
+
|
|
294
|
+
## :page_facing_up: License
|
|
295
|
+
|
|
296
|
+
[The MIT License](./LICENSE)
|
|
297
|
+
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
<h1 align="center">throttled-py</h1>
|
|
2
|
+
<p align="center">
|
|
3
|
+
<em>🔧 支持多种算法(固定窗口,滑动窗口,令牌桶,漏桶 & GCRA)及存储(Redis、内存)的高性能 Python 限流库。</em>
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://github.com/ZhuoZhuoCrayon/throttled-py">
|
|
8
|
+
<img src="https://badgen.net/badge/python/%3E=3.8/green?icon=github" alt="Python">
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://github.com/ZhuoZhuoCrayon/throttled-py">
|
|
11
|
+
<img src="https://codecov.io/gh/ZhuoZhuoCrayon/throttled-py/graph/badge.svg" alt="Coverage Status">
|
|
12
|
+
</a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
[English Documents Available](./README_EN.md) | 简体中文
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## :rocket: 功能
|
|
19
|
+
|
|
20
|
+
### 1)存储
|
|
21
|
+
|
|
22
|
+
| Redis | 内存(线程安全) |
|
|
23
|
+
|--------------------|--------------------|
|
|
24
|
+
| :white_check_mark: | :white_check_mark: |
|
|
25
|
+
|
|
26
|
+
### 2)限流算法
|
|
27
|
+
|
|
28
|
+
| [固定窗口](https://github.com/ZhuoZhuoCrayon/throttled-py/tree/main/docs/basic#21-%E5%9B%BA%E5%AE%9A%E7%AA%97%E5%8F%A3%E8%AE%A1%E6%95%B0%E5%99%A8) | [滑动窗口](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#22-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3) | [令牌桶](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#23-%E4%BB%A4%E7%89%8C%E6%A1%B6) | [漏桶](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#24-%E6%BC%8F%E6%A1%B6) | [通用信元速率算法(Generic Cell Rate Algorithm, GCRA)](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#25-gcra) |
|
|
29
|
+
|------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
|
|
30
|
+
| :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
|
31
|
+
|
|
32
|
+
我们提供以上限流算法的原理分析,你可以点击具体的限流算法跳转到相应的介绍。
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## :beginner: 安装
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
$ pip install throttled-py
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## :memo: 使用
|
|
43
|
+
|
|
44
|
+
### 1)基础
|
|
45
|
+
|
|
46
|
+
#### 通用 API
|
|
47
|
+
|
|
48
|
+
* `limit`:消耗请求,返回 [**RateLimitResult**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#1ratelimitresult)。
|
|
49
|
+
* `peek`:获取指定 Key 的限流器状态,返回 [**RateLimitState**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#2ratelimitstate)。
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from throttled import Throttled
|
|
53
|
+
|
|
54
|
+
# 参数全部缺省时,默认初始化一个基于「内存」、每秒允许通过 60 个请求、使用「令牌桶算法」的限流器。
|
|
55
|
+
throttle = Throttled()
|
|
56
|
+
|
|
57
|
+
# 消耗 1 次请求,输出:RateLimitResult(limited=False,
|
|
58
|
+
# state=RateLimitState(limit=60, remaining=59, reset_after=1))
|
|
59
|
+
print(throttle.limit("key", 1))
|
|
60
|
+
# 获取限流器状态,输出:RateLimitState(limit=60, remaining=59, reset_after=1)
|
|
61
|
+
print(throttle.peek("key"))
|
|
62
|
+
|
|
63
|
+
# 消耗 60 次请求,触发限流,输出:RateLimitResult(limited=True,
|
|
64
|
+
# # state=RateLimitState(limit=60, remaining=59, reset_after=1))
|
|
65
|
+
print(throttle.limit("key", 60))
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### 作为装饰器
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from throttled import Throttled, rate_limter, exceptions
|
|
72
|
+
|
|
73
|
+
# 创建一个每秒允许通过 1 次的限流器。
|
|
74
|
+
@Throttled(key="/ping", quota=rate_limter.per_min(1))
|
|
75
|
+
def ping() -> str:
|
|
76
|
+
return "ping"
|
|
77
|
+
|
|
78
|
+
ping()
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
ping() # 当触发限流时,抛出 LimitedError。
|
|
82
|
+
except exceptions.LimitedError as exc:
|
|
83
|
+
print(exc) # Rate limit exceeded: remaining=0, reset_after=60
|
|
84
|
+
# 在异常中获取限流结果:RateLimitResult(limited=True,
|
|
85
|
+
# state=RateLimitState(limit=1, remaining=0, reset_after=60))
|
|
86
|
+
print(exc.rate_limit_result)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 2)指定存储后端
|
|
90
|
+
|
|
91
|
+
#### Redis
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from throttled import RateLimiterType, Throttled, rate_limter, store
|
|
95
|
+
|
|
96
|
+
@Throttled(
|
|
97
|
+
key="/api/products",
|
|
98
|
+
using=RateLimiterType.TOKEN_BUCKET.value,
|
|
99
|
+
quota=rate_limter.per_min(1),
|
|
100
|
+
# 🌟 使用 Redis 作为存储后端
|
|
101
|
+
store=store.RedisStore(server="redis://127.0.0.1:6379/0", options={"PASSWORD": ""}),
|
|
102
|
+
)
|
|
103
|
+
def products() -> list:
|
|
104
|
+
return [{"name": "iPhone"}, {"name": "MacBook"}]
|
|
105
|
+
|
|
106
|
+
products()
|
|
107
|
+
# raise LimitedError: Rate limit exceeded: remaining=0, reset_after=60
|
|
108
|
+
products()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Memory
|
|
112
|
+
|
|
113
|
+
如果你希望在程序的不同位置,对同一个 Key 进行限流,请确保 `Throttled` 接收到的是同一个 `MemoryStore`,并使用一致的 [`Quota`](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#3quota)。
|
|
114
|
+
|
|
115
|
+
下方样例使用内存作为存储后端,并在 `ping`、`pong` 上对同一个 Key 进行限流:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from throttled import Throttled, rate_limter, store
|
|
119
|
+
|
|
120
|
+
# 🌟 使用 Memory 作为存储后端
|
|
121
|
+
mem_store = store.MemoryStore()
|
|
122
|
+
|
|
123
|
+
@Throttled(key="ping-pong", quota=rate_limter.per_min(1), store=mem_store)
|
|
124
|
+
def ping() -> str:
|
|
125
|
+
return "ping"
|
|
126
|
+
|
|
127
|
+
@Throttled(key="ping-pong", quota=rate_limter.per_min(1), store=mem_store)
|
|
128
|
+
def pong() -> str:
|
|
129
|
+
return "pong"
|
|
130
|
+
|
|
131
|
+
ping()
|
|
132
|
+
# raise LimitedError: Rate limit exceeded: remaining=0, reset_after=60
|
|
133
|
+
pong()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 3)指定限流算法
|
|
137
|
+
|
|
138
|
+
通过 **`using`** 参数指定限流算法,支持算法如下:
|
|
139
|
+
|
|
140
|
+
* [固定窗口](https://github.com/ZhuoZhuoCrayon/throttled-py/tree/main/docs/basic#21-%E5%9B%BA%E5%AE%9A%E7%AA%97%E5%8F%A3%E8%AE%A1%E6%95%B0%E5%99%A8):`RateLimiterType.FIXED_WINDOW.value`
|
|
141
|
+
* [滑动窗口](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#22-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3):`RateLimiterType.SLIDING_WINDOW.value`
|
|
142
|
+
* [令牌桶](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#23-%E4%BB%A4%E7%89%8C%E6%A1%B6):`RateLimiterType.TOKEN_BUCKET.value`
|
|
143
|
+
* [漏桶](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#24-%E6%BC%8F%E6%A1%B6):`RateLimiterType.LEAKING_BUCKET.value`
|
|
144
|
+
* [通用信元速率算法(Generic Cell Rate Algorithm, GCRA)](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#25-gcra):`RateLimiterType.GCRA.value`
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from throttled import RateLimiterType, Throttled, rate_limter, store
|
|
148
|
+
|
|
149
|
+
throttle = Throttled(
|
|
150
|
+
# 🌟指定限流算法
|
|
151
|
+
using=RateLimiterType.FIXED_WINDOW.value,
|
|
152
|
+
quota=rate_limter.per_min(1),
|
|
153
|
+
store=store.MemoryStore()
|
|
154
|
+
)
|
|
155
|
+
assert throttle.limit("key", 2).limited is True
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 4)指定容量
|
|
159
|
+
|
|
160
|
+
#### 快捷创建方式
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from throttled import rate_limter
|
|
164
|
+
|
|
165
|
+
rate_limter.per_sec(60) # 60 / sec
|
|
166
|
+
rate_limter.per_min(60) # 60 / min
|
|
167
|
+
rate_limter.per_hour(60) # 60 / hour
|
|
168
|
+
rate_limter.per_day(60) # 60 / day
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### 调整突发限制
|
|
172
|
+
|
|
173
|
+
通过 **`burst`** 参数,可以调节限流对象处理突发流量的能力 ,对以下算法有效:
|
|
174
|
+
|
|
175
|
+
* `TOKEN_BUCKET`
|
|
176
|
+
* `LEAKING_BUCKET`
|
|
177
|
+
* `GCRA`
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from throttled import rate_limter
|
|
181
|
+
|
|
182
|
+
# 允许突发处理 120 个请求
|
|
183
|
+
# 未指定 burst 时,默认设置为 limit 传入值
|
|
184
|
+
rate_limter.per_min(60, burst=120)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### 自定义配额
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from datetime import timedelta
|
|
191
|
+
from throttled.rate_limter import Quota, Rate
|
|
192
|
+
|
|
193
|
+
# 两分钟一共允许 120 个请求,允许突发处理 150 个请求
|
|
194
|
+
Quota(Rate(period=timedelta(minutes=2), limit=120), burst=150)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## :gear: 数据模型与配置
|
|
198
|
+
|
|
199
|
+
### 1)RateLimitResult
|
|
200
|
+
|
|
201
|
+
RateLimitResult 表示对给定 Key 执行 `limit` 操作后返回的结果。
|
|
202
|
+
|
|
203
|
+
| 字段 | 类型 | 描述 |
|
|
204
|
+
|-----------|----------------|--------------------|
|
|
205
|
+
| `limited` | bool | 表示此次请求是否被允许通过。 |
|
|
206
|
+
| `state` | RateLimitState | 表示给定 Key 的限流器当前状态。 |
|
|
207
|
+
|
|
208
|
+
### 2)RateLimitState
|
|
209
|
+
|
|
210
|
+
RateLimitState 表示给定 Key 的限流器当前状态。
|
|
211
|
+
|
|
212
|
+
| 字段 | 类型 | 描述 |
|
|
213
|
+
|---------------|-------|---------------------------------------------------------|
|
|
214
|
+
| `limit` | int | 表示在初始状态下允许通过的最大请求数量。 |
|
|
215
|
+
| `remaining` | int | 表示在当前状态下,针对给定键允许通过的最大请求数量。 |
|
|
216
|
+
| `reset_after` | float | 表示限流器恢复到初始状态所需的时间(以秒为单位)。在初始状态下,`limit` 等于 `remaining`。 |
|
|
217
|
+
|
|
218
|
+
### 3)Quota
|
|
219
|
+
|
|
220
|
+
Quota 表示限流配额(基础速率 + 突发容量)。
|
|
221
|
+
|
|
222
|
+
| 字段 | 类型 | 描述 |
|
|
223
|
+
|---------|------|-------------------------------------------------------------------------------------|
|
|
224
|
+
| `burst` | int | 突发容量配置(可临时突破基础速率限制),仅对以下算法生效:<br />`TOEKN_BUCKET`<br />`LEAKING_BUCKET`<br />`GCRA` |
|
|
225
|
+
| `rate` | Rate | 基础速率配置。 |
|
|
226
|
+
|
|
227
|
+
### 4)Rate
|
|
228
|
+
|
|
229
|
+
Rate 表示限流速率配置((时间窗口内允许的请求量)。
|
|
230
|
+
|
|
231
|
+
| 字段 | 类型 | 描述 |
|
|
232
|
+
|----------|--------------------|----------------|
|
|
233
|
+
| `period` | datetime.timedelta | 限流时间窗口。 |
|
|
234
|
+
| `limit` | Rate | 时间窗口内允许的最大请求数。 |
|
|
235
|
+
|
|
236
|
+
### 5)Store
|
|
237
|
+
|
|
238
|
+
#### 通用参数
|
|
239
|
+
|
|
240
|
+
| 参数 | 描述 | 默认值 |
|
|
241
|
+
|-----------|-----------------------------------------------------------------------------------------------------|------------------------------|
|
|
242
|
+
| `server` | 标准的 [Redis URL](https://github.com/redis/lettuce/wiki/Redis-URI-and-connection-details#uri-syntax)。 | `"redis://localhost:6379/0"` |
|
|
243
|
+
| `options` | 存储相关配置项,见下文。 | `{}` |
|
|
244
|
+
|
|
245
|
+
#### RedisStore Options
|
|
246
|
+
|
|
247
|
+
RedisStore 基于 [redis-py](https://github.com/redis/redis-py) 提供的 Redis API 进行开发。
|
|
248
|
+
|
|
249
|
+
在 Redis 连接配置管理上,基本沿用 [django-redis](https://github.com/jazzband/django-redis) 的配置命名,减少学习成本。
|
|
250
|
+
|
|
251
|
+
| 参数 | 描述 | 默认值 |
|
|
252
|
+
|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------|
|
|
253
|
+
| `CONNECTION_FACTORY_CLASS` | ConnectionFactory 用于创建并维护 [ConnectionPool](https://redis-py.readthedocs.io/en/stable/connections.html#redis.connection.ConnectionPool)。 | `"throttled.store.ConnectionFactory"` |
|
|
254
|
+
| `CONNECTION_POOL_CLASS` | ConnectionPool 导入路径。 | `"redis.connection.ConnectionPool"` |
|
|
255
|
+
| `CONNECTION_POOL_KWARGS` | [ConnectionPool 构造参数](https://redis-py.readthedocs.io/en/stable/connections.html#connectionpool)。 | `{}` |
|
|
256
|
+
| `REDIS_CLIENT_CLASS` | RedisClient 导入路径,默认使用 [redis.client.Redis](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis)。 | `"redis.client.Redis"` |
|
|
257
|
+
| `REDIS_CLIENT_KWARGS` | [RedisClient 构造参数](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis)。 | `{}` |
|
|
258
|
+
| `PASSWORD` | 密码。 | `null` |
|
|
259
|
+
| `SOCKET_TIMEOUT` | ConnectionPool 参数。 | `null` |
|
|
260
|
+
| `SOCKET_CONNECT_TIMEOUT` | ConnectionPool 参数。 | `null` |
|
|
261
|
+
| `SENTINELS` | `(host, port)` 元组列表,哨兵模式请使用 `SentinelConnectionFactory` 并提供该配置。 | `[]` |
|
|
262
|
+
| `SENTINEL_KWARGS` | [Sentinel 构造参数](https://redis-py.readthedocs.io/en/stable/connections.html#id1)。 | `{}` |
|
|
263
|
+
|
|
264
|
+
#### MemoryStore Options
|
|
265
|
+
|
|
266
|
+
MemoryStore 本质是一个基于内存实现的,带过期时间的 [LRU Cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU) 。
|
|
267
|
+
|
|
268
|
+
| 参数 | 描述 | 默认值 |
|
|
269
|
+
|------------|-------------------------------------------|--------|
|
|
270
|
+
| `MAX_SIZE` | 最大容量,存储的键值对数量超过 `MAX_SIZE` 时,将按 LRU 策略淘汰。 | `1024` |
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
## :books: Version History
|
|
274
|
+
|
|
275
|
+
[See CHANGELOG.md](./CHANGELOG.md)
|
|
276
|
+
|
|
277
|
+
## :page_facing_up: License
|
|
278
|
+
|
|
279
|
+
[The MIT License](./LICENSE)
|