redis-xsync 1.0.1__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.
- redis_xsync-1.0.1/PKG-INFO +333 -0
- redis_xsync-1.0.1/README.md +305 -0
- redis_xsync-1.0.1/pyproject.toml +58 -0
- redis_xsync-1.0.1/redis_xsync/__init__.py +82 -0
- redis_xsync-1.0.1/redis_xsync/awaitable/__init__.py +126 -0
- redis_xsync-1.0.1/redis_xsync/distributed_lock.py +202 -0
- redis_xsync-1.0.1/redis_xsync/rate_limited_pool.py +554 -0
- redis_xsync-1.0.1/redis_xsync/rtypes.py +85 -0
- redis_xsync-1.0.1/redis_xsync/rxcore.py +254 -0
- redis_xsync-1.0.1/redis_xsync.egg-info/PKG-INFO +333 -0
- redis_xsync-1.0.1/redis_xsync.egg-info/SOURCES.txt +17 -0
- redis_xsync-1.0.1/redis_xsync.egg-info/dependency_links.txt +1 -0
- redis_xsync-1.0.1/redis_xsync.egg-info/requires.txt +2 -0
- redis_xsync-1.0.1/redis_xsync.egg-info/top_level.txt +1 -0
- redis_xsync-1.0.1/setup.cfg +4 -0
- redis_xsync-1.0.1/test/test_acquire_random_container.py +37 -0
- redis_xsync-1.0.1/test/test_redis_lock.py +79 -0
- redis_xsync-1.0.1/test/test_redis_multi.py +61 -0
- redis_xsync-1.0.1/test/test_redis_rate_limit.py +48 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: redis-xsync
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Sync/Async unified Redis client with distributed lock and rate limiter
|
|
5
|
+
Author: Jay
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jaygarage/RedisXsync
|
|
8
|
+
Project-URL: Repository, https://github.com/jaygarage/RedisXsync
|
|
9
|
+
Project-URL: Issues, https://github.com/jaygarage/RedisXsync/issues
|
|
10
|
+
Keywords: redis,async,sync,distributed-lock,redis-xsync,rate-limit
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
14
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
23
|
+
Classifier: Operating System :: OS Independent
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: pydantic>=2.0
|
|
27
|
+
Requires-Dist: redis>=5.0.0
|
|
28
|
+
|
|
29
|
+
# RedisXsync
|
|
30
|
+
|
|
31
|
+
  
|
|
32
|
+
|
|
33
|
+
RedisXsync 是一个 **同步 / 异步统一 Redis 客户端**,专为 **分布式锁** 和 **爬虫账号池限流** 场景设计,支持多实例、多
|
|
34
|
+
DB,线程安全和协程安全。
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## ✨ 核心特性
|
|
39
|
+
|
|
40
|
+
| 特性 | 描述 |
|
|
41
|
+
|-------------|----------------------------------|
|
|
42
|
+
| 同步 / 异步统一接口 | 同一套 API 支持 `with` 和 `async with` |
|
|
43
|
+
| 多实例 / 多 DB | 支持不同 Redis 实例与数据库同时管理 |
|
|
44
|
+
| 分布式锁 | 单键锁、组合锁,阻塞 / 非阻塞模式 |
|
|
45
|
+
| 账号池限流 | 基于 Lua 原子操作的令牌桶限流,可等待可用 token |
|
|
46
|
+
| 线程 & 协程安全 | 避免多线程 / 协程冲突 |
|
|
47
|
+
| 自动连接管理 | Context manager 自动释放连接 |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 🏗 架构概览
|
|
52
|
+
|
|
53
|
+
| RedisXsync |
|
|
54
|
+
|--------------------------|
|
|
55
|
+
| Sync / Async Interfaces |
|
|
56
|
+
| Distributed Lock Manager |
|
|
57
|
+
| Rate Limiter / TokenPool |
|
|
58
|
+
|
|
59
|
+
- 分布式锁:通过单键或多键原子操作保证并发安全
|
|
60
|
+
- 限流/账号池:Lua 脚本保证跨进程/跨机器的原子性,精确到毫秒
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 📦 安装
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install redis-xsync
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 🚀 快速开始
|
|
72
|
+
|
|
73
|
+
### 1️⃣ 注册 Redis
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from redis_xsync import register_redis, resolve_redis, redisXsync
|
|
77
|
+
|
|
78
|
+
redis1 = register_redis(
|
|
79
|
+
label="redis_xsync",
|
|
80
|
+
host="127.0.0.1",
|
|
81
|
+
port=6379,
|
|
82
|
+
db=0,
|
|
83
|
+
password=None,
|
|
84
|
+
decode_responses=True
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# 通过 label 获取
|
|
88
|
+
redis2 = resolve_redis("redis_xsync")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# 使用方式说明:
|
|
92
|
+
# 1️⃣ 全局默认实例 redisXsync
|
|
93
|
+
# - 在首次调用 register_redis 注册任意 Redis 时,首次注册会自动赋值给 redisXsync,非并发安全
|
|
94
|
+
# - 后续可以直接使用 redisXsync,无需通过 label 获取
|
|
95
|
+
#
|
|
96
|
+
# 2️⃣ 通过 label 显式管理 Redis
|
|
97
|
+
# - register_redis(label=..., ...) 注册 Redis 实例
|
|
98
|
+
# - resolve_redis(label) 根据 label 获取对应 Redis 实例
|
|
99
|
+
#
|
|
100
|
+
# 3️⃣ 独立实例
|
|
101
|
+
# - 可以直接创建 RedisXsync() 实例,不必参与 register_redis 、resolve_redis、 redisXsync 全局管理
|
|
102
|
+
# - 适合临时或隔离使用场景
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 🔄 同步 / 异步使用
|
|
106
|
+
|
|
107
|
+
### ✅ 同步
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
with redis1(db=1) as r:
|
|
111
|
+
r.setnx("key", "value")
|
|
112
|
+
print(r.get("key"))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### ✅ 异步
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import asyncio
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def main():
|
|
124
|
+
async with redis1(db=1) as r:
|
|
125
|
+
await r.setnx("key", "value")
|
|
126
|
+
print(await r.get("key"))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
asyncio.run(main())
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 🔐 分布式锁(支持组合锁)
|
|
135
|
+
|
|
136
|
+
### 特性
|
|
137
|
+
|
|
138
|
+
* 多 key 原子加锁
|
|
139
|
+
* 所有 key 释放才算解锁
|
|
140
|
+
* 支持阻塞等待
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### 示例(多线程 + 协程混合)
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import threading
|
|
148
|
+
import asyncio
|
|
149
|
+
from redis_xsync import register_redis
|
|
150
|
+
|
|
151
|
+
cx = 0
|
|
152
|
+
redis = register_redis(label="redis_xsync", host="127.0.0.1", port=6379)
|
|
153
|
+
Lock = redis.AsyncRedisAtomicMultiLock
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def sync_worker():
|
|
157
|
+
global cx
|
|
158
|
+
for _ in range(5):
|
|
159
|
+
with Lock("lock_key", db=0, ttl_ms=10, blocking=True):
|
|
160
|
+
with redis(db=2) as r:
|
|
161
|
+
r.setnx("db2", str(cx))
|
|
162
|
+
cx += 1
|
|
163
|
+
print(f"[SYNC] {cx=}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def async_worker():
|
|
167
|
+
global cx
|
|
168
|
+
for _ in range(5):
|
|
169
|
+
with Lock("lock_key", db=0, ttl_ms=10, blocking=True):
|
|
170
|
+
async with redis(db=3) as r:
|
|
171
|
+
await r.setnx("db3", str(cx))
|
|
172
|
+
cx += 1
|
|
173
|
+
print(f"[ASYNC] {cx=}")
|
|
174
|
+
await asyncio.sleep(0.2)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# 启动线程
|
|
178
|
+
threading.Thread(target=sync_worker).start()
|
|
179
|
+
|
|
180
|
+
# 启动协程
|
|
181
|
+
asyncio.run(async_worker())
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 🚦 爬虫账号池限流
|
|
187
|
+
|
|
188
|
+
基于 Redis + Lua,实现**分布式限流 + 自动等待可用账号**
|
|
189
|
+
|
|
190
|
+
📦 Limited — Redis 容器操作方法对照表
|
|
191
|
+
|
|
192
|
+
| 方法 | 异步 | 阻塞 | 遵循限流 | 数量 | 描述 |
|
|
193
|
+
|-----------------------------------------------------|----|----|------|----|------------------------|
|
|
194
|
+
| `set_available_containers(*containers)` | ✅ | ❌ | ✅ | N | 存储/更新容器配置(生产者) |
|
|
195
|
+
| `ask_available_containers(quantity)` | ✅ | ❌ | ✅ | N | 获取 N 个可用容器,若无返回 `None` |
|
|
196
|
+
| `ask_available_container()` | ✅ | ❌ | ✅ | 1 | 获取单个可用容器,若无返回 `None` |
|
|
197
|
+
| `wait_ask_available_containers(quantity, timeout)` | ✅ | ✅ | ✅ | N | 阻塞等待 N 个可用容器 |
|
|
198
|
+
| `wait_ask_available_container(timeout)` | ✅ | ✅ | ✅ | 1 | 阻塞等待单个可用容器 |
|
|
199
|
+
| `acquire_random_containers(quantity)` | ✅ | ❌ | ❌ | N | 随机获取 N 个容器,忽略限流 |
|
|
200
|
+
| `acquire_random_container()` | ✅ | ❌ | ❌ | 1 | 随机获取单个容器,忽略限流 |
|
|
201
|
+
| `wait_acquire_random_containers(quantity, timeout)` | ✅ | ✅ | ❌ | N | 阻塞等待 N 个随机容器,忽略限流 |
|
|
202
|
+
| `wait_acquire_random_container(timeout)` | ✅ | ✅ | ❌ | 1 | 阻塞等待单个随机容器,忽略限流 |
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### 写入账号(生产者)
|
|
207
|
+
|
|
208
|
+
## LimitedModel 说明
|
|
209
|
+
|
|
210
|
+
`LimitedModel` 用于表示受限访问(rate-limited)的容器条目,每条数据包含唯一标识、时间戳、TTL、访问间隔和访问计数等信息。
|
|
211
|
+
|
|
212
|
+
### 字段说明
|
|
213
|
+
|
|
214
|
+
| 字段 | 类型 | 默认值 | 描述 |
|
|
215
|
+
|-----------------------|-------|-----------|---------------------|
|
|
216
|
+
| `id` | `str` | — | 数据唯一标识符,必须提供 |
|
|
217
|
+
| `ct` | `int` | 当前时间戳(毫秒) | 记录条目的收集时间,无需设置 |
|
|
218
|
+
| `ttl_ms` | `int` | `0` | 条目的生存时间(毫秒),0 表示无限期 |
|
|
219
|
+
| `interval_ms` | `int` | `0` | 访问间隔(毫秒),0 表示不限速 |
|
|
220
|
+
| `next_time_available` | `int` | 当前时间戳(毫秒) | 下次可访问时间,用于限流控制,无需设置 |
|
|
221
|
+
| `containers` | `str` | `None` | 存储容器数据,可为空 |
|
|
222
|
+
| `usage_count` | `int` | `0` | 条目被访问的次数,无需设置 |
|
|
223
|
+
### 使用说明
|
|
224
|
+
|
|
225
|
+
- `ct` 和 `next_time_available` 都以毫秒为单位,便于高精度限流。
|
|
226
|
+
- `interval_ms > 0` 时,访问条目会更新 `next_time_available` 为当前时间 + 间隔。
|
|
227
|
+
- 每次访问条目时,都会通过 `usage_count` 记录被访问次数,以统计和调度。
|
|
228
|
+
- `ttl_ms` 可配合 Redis 等存储设置过期时间,实现自动清理。
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
import random
|
|
232
|
+
from redis_xsync import register_redis
|
|
233
|
+
from redis_xsync.rtypes import LimitedModel
|
|
234
|
+
|
|
235
|
+
redis = register_redis(label="redis_xsync", host="127.0.0.1", port=6379)
|
|
236
|
+
Limited = redis.Limited
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
async def produce_container():
|
|
240
|
+
async with Limited(redis_key="crawler:identity:flow_limit") as lt:
|
|
241
|
+
for i in range(10):
|
|
242
|
+
token = LimitedModel(
|
|
243
|
+
id=f"user_{i}",
|
|
244
|
+
ttl_ms=random.randint(30000, 60000),
|
|
245
|
+
interval_ms=random.randint(1000, 6000),
|
|
246
|
+
containers="cookies"
|
|
247
|
+
)
|
|
248
|
+
await lt.set_available_containers(token)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### 获取账号(消费者,阻塞等待)
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
async def consume_token():
|
|
257
|
+
async with Limited(redis_key="crawler:identity:flow_limit") as lt:
|
|
258
|
+
# 等待可用 token,超时 10 秒
|
|
259
|
+
token = await lt.wait_ask_available_containers(quantity=1, timeout=10)
|
|
260
|
+
print("获取到账号:", token)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### 同步调用
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
def consume_sync():
|
|
269
|
+
with Limited(redis_key="crawler:identity:flow_limit") as lt:
|
|
270
|
+
token = lt.wait_ask_available_containers(quantity=1, timeout=10)
|
|
271
|
+
print("获取到账号:", token)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## ⚡ 说明:
|
|
275
|
+
|
|
276
|
+
- produce_container 写入 token 到 Redis,带过期时间和间隔限流
|
|
277
|
+
- consume_token 或 consume_sync 从 Redis 中获取可用 token,如果当前没有可用,会等待直到 timeout
|
|
278
|
+
- 支持异步和同步,且保证跨进程 / 跨机器原子性
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 🧠 设计说明
|
|
283
|
+
|
|
284
|
+
### 🔐 分布式锁
|
|
285
|
+
|
|
286
|
+
* 基于 Redis 原子操作
|
|
287
|
+
* 支持多 key(组合锁)
|
|
288
|
+
* 防止并发冲突
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
### 🚦 限流模型
|
|
293
|
+
|
|
294
|
+
* 每个 token:
|
|
295
|
+
|
|
296
|
+
* 独立 TTL
|
|
297
|
+
* 独立间隔(interval)
|
|
298
|
+
* Lua 保证原子性
|
|
299
|
+
* 支持多进程 / 多机器共享
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## ⚠️ 注意事项
|
|
304
|
+
|
|
305
|
+
### Windows 建议
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
import asyncio
|
|
309
|
+
|
|
310
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 📌 适用场景
|
|
316
|
+
|
|
317
|
+
* 单机非集群
|
|
318
|
+
* 爬虫账号池
|
|
319
|
+
* 接口限流
|
|
320
|
+
* 分布式任务调度
|
|
321
|
+
* 多服务并发控制
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## 🤝 贡献
|
|
326
|
+
|
|
327
|
+
欢迎 Issue / PR!
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## 📄 License
|
|
332
|
+
|
|
333
|
+
MIT License
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# RedisXsync
|
|
2
|
+
|
|
3
|
+
  
|
|
4
|
+
|
|
5
|
+
RedisXsync 是一个 **同步 / 异步统一 Redis 客户端**,专为 **分布式锁** 和 **爬虫账号池限流** 场景设计,支持多实例、多
|
|
6
|
+
DB,线程安全和协程安全。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ 核心特性
|
|
11
|
+
|
|
12
|
+
| 特性 | 描述 |
|
|
13
|
+
|-------------|----------------------------------|
|
|
14
|
+
| 同步 / 异步统一接口 | 同一套 API 支持 `with` 和 `async with` |
|
|
15
|
+
| 多实例 / 多 DB | 支持不同 Redis 实例与数据库同时管理 |
|
|
16
|
+
| 分布式锁 | 单键锁、组合锁,阻塞 / 非阻塞模式 |
|
|
17
|
+
| 账号池限流 | 基于 Lua 原子操作的令牌桶限流,可等待可用 token |
|
|
18
|
+
| 线程 & 协程安全 | 避免多线程 / 协程冲突 |
|
|
19
|
+
| 自动连接管理 | Context manager 自动释放连接 |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🏗 架构概览
|
|
24
|
+
|
|
25
|
+
| RedisXsync |
|
|
26
|
+
|--------------------------|
|
|
27
|
+
| Sync / Async Interfaces |
|
|
28
|
+
| Distributed Lock Manager |
|
|
29
|
+
| Rate Limiter / TokenPool |
|
|
30
|
+
|
|
31
|
+
- 分布式锁:通过单键或多键原子操作保证并发安全
|
|
32
|
+
- 限流/账号池:Lua 脚本保证跨进程/跨机器的原子性,精确到毫秒
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 📦 安装
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install redis-xsync
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🚀 快速开始
|
|
44
|
+
|
|
45
|
+
### 1️⃣ 注册 Redis
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from redis_xsync import register_redis, resolve_redis, redisXsync
|
|
49
|
+
|
|
50
|
+
redis1 = register_redis(
|
|
51
|
+
label="redis_xsync",
|
|
52
|
+
host="127.0.0.1",
|
|
53
|
+
port=6379,
|
|
54
|
+
db=0,
|
|
55
|
+
password=None,
|
|
56
|
+
decode_responses=True
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# 通过 label 获取
|
|
60
|
+
redis2 = resolve_redis("redis_xsync")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# 使用方式说明:
|
|
64
|
+
# 1️⃣ 全局默认实例 redisXsync
|
|
65
|
+
# - 在首次调用 register_redis 注册任意 Redis 时,首次注册会自动赋值给 redisXsync,非并发安全
|
|
66
|
+
# - 后续可以直接使用 redisXsync,无需通过 label 获取
|
|
67
|
+
#
|
|
68
|
+
# 2️⃣ 通过 label 显式管理 Redis
|
|
69
|
+
# - register_redis(label=..., ...) 注册 Redis 实例
|
|
70
|
+
# - resolve_redis(label) 根据 label 获取对应 Redis 实例
|
|
71
|
+
#
|
|
72
|
+
# 3️⃣ 独立实例
|
|
73
|
+
# - 可以直接创建 RedisXsync() 实例,不必参与 register_redis 、resolve_redis、 redisXsync 全局管理
|
|
74
|
+
# - 适合临时或隔离使用场景
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 🔄 同步 / 异步使用
|
|
78
|
+
|
|
79
|
+
### ✅ 同步
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
with redis1(db=1) as r:
|
|
83
|
+
r.setnx("key", "value")
|
|
84
|
+
print(r.get("key"))
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### ✅ 异步
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import asyncio
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def main():
|
|
96
|
+
async with redis1(db=1) as r:
|
|
97
|
+
await r.setnx("key", "value")
|
|
98
|
+
print(await r.get("key"))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
asyncio.run(main())
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 🔐 分布式锁(支持组合锁)
|
|
107
|
+
|
|
108
|
+
### 特性
|
|
109
|
+
|
|
110
|
+
* 多 key 原子加锁
|
|
111
|
+
* 所有 key 释放才算解锁
|
|
112
|
+
* 支持阻塞等待
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### 示例(多线程 + 协程混合)
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import threading
|
|
120
|
+
import asyncio
|
|
121
|
+
from redis_xsync import register_redis
|
|
122
|
+
|
|
123
|
+
cx = 0
|
|
124
|
+
redis = register_redis(label="redis_xsync", host="127.0.0.1", port=6379)
|
|
125
|
+
Lock = redis.AsyncRedisAtomicMultiLock
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def sync_worker():
|
|
129
|
+
global cx
|
|
130
|
+
for _ in range(5):
|
|
131
|
+
with Lock("lock_key", db=0, ttl_ms=10, blocking=True):
|
|
132
|
+
with redis(db=2) as r:
|
|
133
|
+
r.setnx("db2", str(cx))
|
|
134
|
+
cx += 1
|
|
135
|
+
print(f"[SYNC] {cx=}")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def async_worker():
|
|
139
|
+
global cx
|
|
140
|
+
for _ in range(5):
|
|
141
|
+
with Lock("lock_key", db=0, ttl_ms=10, blocking=True):
|
|
142
|
+
async with redis(db=3) as r:
|
|
143
|
+
await r.setnx("db3", str(cx))
|
|
144
|
+
cx += 1
|
|
145
|
+
print(f"[ASYNC] {cx=}")
|
|
146
|
+
await asyncio.sleep(0.2)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# 启动线程
|
|
150
|
+
threading.Thread(target=sync_worker).start()
|
|
151
|
+
|
|
152
|
+
# 启动协程
|
|
153
|
+
asyncio.run(async_worker())
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 🚦 爬虫账号池限流
|
|
159
|
+
|
|
160
|
+
基于 Redis + Lua,实现**分布式限流 + 自动等待可用账号**
|
|
161
|
+
|
|
162
|
+
📦 Limited — Redis 容器操作方法对照表
|
|
163
|
+
|
|
164
|
+
| 方法 | 异步 | 阻塞 | 遵循限流 | 数量 | 描述 |
|
|
165
|
+
|-----------------------------------------------------|----|----|------|----|------------------------|
|
|
166
|
+
| `set_available_containers(*containers)` | ✅ | ❌ | ✅ | N | 存储/更新容器配置(生产者) |
|
|
167
|
+
| `ask_available_containers(quantity)` | ✅ | ❌ | ✅ | N | 获取 N 个可用容器,若无返回 `None` |
|
|
168
|
+
| `ask_available_container()` | ✅ | ❌ | ✅ | 1 | 获取单个可用容器,若无返回 `None` |
|
|
169
|
+
| `wait_ask_available_containers(quantity, timeout)` | ✅ | ✅ | ✅ | N | 阻塞等待 N 个可用容器 |
|
|
170
|
+
| `wait_ask_available_container(timeout)` | ✅ | ✅ | ✅ | 1 | 阻塞等待单个可用容器 |
|
|
171
|
+
| `acquire_random_containers(quantity)` | ✅ | ❌ | ❌ | N | 随机获取 N 个容器,忽略限流 |
|
|
172
|
+
| `acquire_random_container()` | ✅ | ❌ | ❌ | 1 | 随机获取单个容器,忽略限流 |
|
|
173
|
+
| `wait_acquire_random_containers(quantity, timeout)` | ✅ | ✅ | ❌ | N | 阻塞等待 N 个随机容器,忽略限流 |
|
|
174
|
+
| `wait_acquire_random_container(timeout)` | ✅ | ✅ | ❌ | 1 | 阻塞等待单个随机容器,忽略限流 |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### 写入账号(生产者)
|
|
179
|
+
|
|
180
|
+
## LimitedModel 说明
|
|
181
|
+
|
|
182
|
+
`LimitedModel` 用于表示受限访问(rate-limited)的容器条目,每条数据包含唯一标识、时间戳、TTL、访问间隔和访问计数等信息。
|
|
183
|
+
|
|
184
|
+
### 字段说明
|
|
185
|
+
|
|
186
|
+
| 字段 | 类型 | 默认值 | 描述 |
|
|
187
|
+
|-----------------------|-------|-----------|---------------------|
|
|
188
|
+
| `id` | `str` | — | 数据唯一标识符,必须提供 |
|
|
189
|
+
| `ct` | `int` | 当前时间戳(毫秒) | 记录条目的收集时间,无需设置 |
|
|
190
|
+
| `ttl_ms` | `int` | `0` | 条目的生存时间(毫秒),0 表示无限期 |
|
|
191
|
+
| `interval_ms` | `int` | `0` | 访问间隔(毫秒),0 表示不限速 |
|
|
192
|
+
| `next_time_available` | `int` | 当前时间戳(毫秒) | 下次可访问时间,用于限流控制,无需设置 |
|
|
193
|
+
| `containers` | `str` | `None` | 存储容器数据,可为空 |
|
|
194
|
+
| `usage_count` | `int` | `0` | 条目被访问的次数,无需设置 |
|
|
195
|
+
### 使用说明
|
|
196
|
+
|
|
197
|
+
- `ct` 和 `next_time_available` 都以毫秒为单位,便于高精度限流。
|
|
198
|
+
- `interval_ms > 0` 时,访问条目会更新 `next_time_available` 为当前时间 + 间隔。
|
|
199
|
+
- 每次访问条目时,都会通过 `usage_count` 记录被访问次数,以统计和调度。
|
|
200
|
+
- `ttl_ms` 可配合 Redis 等存储设置过期时间,实现自动清理。
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
import random
|
|
204
|
+
from redis_xsync import register_redis
|
|
205
|
+
from redis_xsync.rtypes import LimitedModel
|
|
206
|
+
|
|
207
|
+
redis = register_redis(label="redis_xsync", host="127.0.0.1", port=6379)
|
|
208
|
+
Limited = redis.Limited
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def produce_container():
|
|
212
|
+
async with Limited(redis_key="crawler:identity:flow_limit") as lt:
|
|
213
|
+
for i in range(10):
|
|
214
|
+
token = LimitedModel(
|
|
215
|
+
id=f"user_{i}",
|
|
216
|
+
ttl_ms=random.randint(30000, 60000),
|
|
217
|
+
interval_ms=random.randint(1000, 6000),
|
|
218
|
+
containers="cookies"
|
|
219
|
+
)
|
|
220
|
+
await lt.set_available_containers(token)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### 获取账号(消费者,阻塞等待)
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
async def consume_token():
|
|
229
|
+
async with Limited(redis_key="crawler:identity:flow_limit") as lt:
|
|
230
|
+
# 等待可用 token,超时 10 秒
|
|
231
|
+
token = await lt.wait_ask_available_containers(quantity=1, timeout=10)
|
|
232
|
+
print("获取到账号:", token)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### 同步调用
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
def consume_sync():
|
|
241
|
+
with Limited(redis_key="crawler:identity:flow_limit") as lt:
|
|
242
|
+
token = lt.wait_ask_available_containers(quantity=1, timeout=10)
|
|
243
|
+
print("获取到账号:", token)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## ⚡ 说明:
|
|
247
|
+
|
|
248
|
+
- produce_container 写入 token 到 Redis,带过期时间和间隔限流
|
|
249
|
+
- consume_token 或 consume_sync 从 Redis 中获取可用 token,如果当前没有可用,会等待直到 timeout
|
|
250
|
+
- 支持异步和同步,且保证跨进程 / 跨机器原子性
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 🧠 设计说明
|
|
255
|
+
|
|
256
|
+
### 🔐 分布式锁
|
|
257
|
+
|
|
258
|
+
* 基于 Redis 原子操作
|
|
259
|
+
* 支持多 key(组合锁)
|
|
260
|
+
* 防止并发冲突
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### 🚦 限流模型
|
|
265
|
+
|
|
266
|
+
* 每个 token:
|
|
267
|
+
|
|
268
|
+
* 独立 TTL
|
|
269
|
+
* 独立间隔(interval)
|
|
270
|
+
* Lua 保证原子性
|
|
271
|
+
* 支持多进程 / 多机器共享
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## ⚠️ 注意事项
|
|
276
|
+
|
|
277
|
+
### Windows 建议
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
import asyncio
|
|
281
|
+
|
|
282
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 📌 适用场景
|
|
288
|
+
|
|
289
|
+
* 单机非集群
|
|
290
|
+
* 爬虫账号池
|
|
291
|
+
* 接口限流
|
|
292
|
+
* 分布式任务调度
|
|
293
|
+
* 多服务并发控制
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## 🤝 贡献
|
|
298
|
+
|
|
299
|
+
欢迎 Issue / PR!
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 📄 License
|
|
304
|
+
|
|
305
|
+
MIT License
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "redis-xsync"
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "Sync/Async unified Redis client with distributed lock and rate limiter"
|
|
9
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jay" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
license = { text = "MIT" }
|
|
17
|
+
|
|
18
|
+
keywords = [
|
|
19
|
+
"redis",
|
|
20
|
+
"async",
|
|
21
|
+
"sync",
|
|
22
|
+
"distributed-lock",
|
|
23
|
+
"redis-xsync",
|
|
24
|
+
"rate-limit",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
dependencies = [
|
|
28
|
+
"pydantic>=2.0",
|
|
29
|
+
"redis>=5.0.0"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
classifiers = [
|
|
33
|
+
"Development Status :: 3 - Alpha",
|
|
34
|
+
"Intended Audience :: Developers",
|
|
35
|
+
"Topic :: Software Development :: Libraries",
|
|
36
|
+
"Topic :: System :: Distributed Computing",
|
|
37
|
+
"Programming Language :: Python :: 3",
|
|
38
|
+
"Programming Language :: Python :: 3.8",
|
|
39
|
+
"Programming Language :: Python :: 3.9",
|
|
40
|
+
"Programming Language :: Python :: 3.10",
|
|
41
|
+
"Programming Language :: Python :: 3.11",
|
|
42
|
+
"Programming Language :: Python :: 3.12",
|
|
43
|
+
"Programming Language :: Python :: 3.13",
|
|
44
|
+
"License :: OSI Approved :: MIT License",
|
|
45
|
+
"Operating System :: OS Independent"
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/jaygarage/RedisXsync"
|
|
50
|
+
Repository = "https://github.com/jaygarage/RedisXsync"
|
|
51
|
+
Issues = "https://github.com/jaygarage/RedisXsync/issues"
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = ["."]
|
|
55
|
+
include = ["redis_xsync*"]
|
|
56
|
+
|
|
57
|
+
[tool.setuptools]
|
|
58
|
+
include-package-data = true
|