redqueue 0.13.0__tar.gz → 0.14.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.
- {redqueue-0.13.0 → redqueue-0.14.0}/CHANGELOG.md +57 -22
- {redqueue-0.13.0 → redqueue-0.14.0}/PKG-INFO +30 -7
- {redqueue-0.13.0 → redqueue-0.14.0}/README-zh-CN.md +63 -43
- {redqueue-0.13.0 → redqueue-0.14.0}/README.md +67 -44
- {redqueue-0.13.0 → redqueue-0.14.0}/docs/API.md +97 -60
- redqueue-0.14.0/docs/superpowers/plans/2026-06-27-message-deduplication.md +357 -0
- redqueue-0.14.0/docs/superpowers/specs/2026-06-27-message-deduplication-design.md +170 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/pyproject.toml +2 -2
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/__init__.py +8 -7
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/_version.py +1 -1
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/async_client.py +115 -45
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/backends/async_delay.py +372 -372
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/backends/async_list.py +12 -12
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/backends/async_stream.py +601 -601
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/backends/base.py +160 -160
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/backends/delay.py +380 -380
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/backends/list.py +12 -12
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/backends/stream.py +575 -575
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/cli.py +19 -19
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/client.py +106 -43
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/config.py +30 -0
- redqueue-0.14.0/src/redqueue/deduplication.py +334 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/message.py +181 -176
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/monitoring.py +187 -186
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/fakes.py +68 -26
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/test_availability.py +255 -255
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/test_cli.py +61 -61
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/test_project_skeleton.py +418 -146
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/test_real_redis_availability.py +162 -99
- {redqueue-0.13.0 → redqueue-0.14.0}/.github/workflows/ci.yml +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/.gitignore +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/CODE_OF_CONDUCT.md +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/CONTRIBUTING.md +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/LICENSE +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/NOTICE +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/docs/RELEASE.md +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/README.md +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/__init__.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/async_list_queue.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/common.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/compatibility_check.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/custom_serializer.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/delayed_tasks.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/monitoring_hooks.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/stream_queue.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/examples/sync_list_queue.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/requirements.txt +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/scripts/check.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/__main__.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/backends/__init__.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/compat.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/connection.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/exceptions.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/src/redqueue/serialization.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/README.md +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/__init__.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/test_backend_contracts.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/test_integration_redis.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/test_performance.py +0 -0
- {redqueue-0.13.0 → redqueue-0.14.0}/tests/test_real_redis_performance.py +0 -0
|
@@ -7,35 +7,70 @@ All notable public release changes are documented here.
|
|
|
7
7
|
Development versions are tracked separately from formal release versions.
|
|
8
8
|
开发版本与正式版本分开管理。
|
|
9
9
|
|
|
10
|
-
## [0.
|
|
10
|
+
## [0.14.0] - 2026-06-27
|
|
11
11
|
|
|
12
12
|
### Added
|
|
13
13
|
|
|
14
|
-
- Added
|
|
15
|
-
`
|
|
16
|
-
|
|
17
|
-
- Added
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- Added `trace_id` to monitoring events and CLI message output for lifecycle
|
|
21
|
-
correlation.
|
|
22
|
-
- Added CLI `--trace-id` support for `publish` and `delay`.
|
|
23
|
-
- Added `new_trace_id()` helper for callers that want RedQueue-generated trace
|
|
24
|
-
identifiers.
|
|
14
|
+
- Added opt-in Redis-backed message deduplication with
|
|
15
|
+
`DeduplicationConfig`.
|
|
16
|
+
- Added `dedup_key` support to sync and async `publish()` and `delay()`.
|
|
17
|
+
- Added duplicate-hit behavior that returns the first message id without
|
|
18
|
+
enqueueing or scheduling another message.
|
|
19
|
+
- Added `message.deduplicated` monitoring events.
|
|
25
20
|
|
|
26
21
|
### 新增
|
|
27
22
|
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
- 监控事件和 CLI 消息输出新增 `trace_id`,方便按链路聚合生命周期事件。
|
|
33
|
-
- CLI 的 `publish` 和 `delay` 命令新增 `--trace-id`。
|
|
34
|
-
- 新增 `new_trace_id()` helper,方便调用方生成 RedQueue trace 标识。
|
|
23
|
+
- 新增基于 Redis 的可选消息去重能力,通过 `DeduplicationConfig` 启用。
|
|
24
|
+
- 同步和异步 `publish()`、`delay()` 新增 `dedup_key` 参数。
|
|
25
|
+
- 重复命中时返回第一次消息 ID,不再重复入队或重复调度。
|
|
26
|
+
- 新增 `message.deduplicated` 监控事件。
|
|
35
27
|
|
|
36
|
-
## [0.
|
|
37
|
-
|
|
38
|
-
###
|
|
28
|
+
## [0.13.1] - 2026-06-27
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Fixed a compatibility regression where legacy messages with blank
|
|
33
|
+
`headers["trace_id"]` values could fail to construct or consume after
|
|
34
|
+
upgrading to `0.13.0`.
|
|
35
|
+
- Fixed sync and async `publish(..., delay=..., message_id=...)` so delayed
|
|
36
|
+
publishing preserves caller-provided message ids.
|
|
37
|
+
|
|
38
|
+
### 修复
|
|
39
|
+
|
|
40
|
+
- 修复升级到 `0.13.0` 后,历史消息中空白 `headers["trace_id"]` 可能导致
|
|
41
|
+
消息构造或消费失败的兼容性回归。
|
|
42
|
+
- 修复同步和异步 `publish(..., delay=..., message_id=...)`,现在延迟发布会
|
|
43
|
+
保留调用方传入的消息 ID。
|
|
44
|
+
|
|
45
|
+
## [0.13.0] - 2026-06-27
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- Added first-class `trace_id` support to `Message`, sync and async
|
|
50
|
+
`publish()`, sync and async `delay()`, List, Streams, and delayed task
|
|
51
|
+
backends.
|
|
52
|
+
- Added trace propagation through message envelopes, retry, dead-letter,
|
|
53
|
+
delayed release, and requeue flows while remaining compatible with existing
|
|
54
|
+
`headers["trace_id"]` messages.
|
|
55
|
+
- Added `trace_id` to monitoring events and CLI message output for lifecycle
|
|
56
|
+
correlation.
|
|
57
|
+
- Added CLI `--trace-id` support for `publish` and `delay`.
|
|
58
|
+
- Added `new_trace_id()` helper for callers that want RedQueue-generated trace
|
|
59
|
+
identifiers.
|
|
60
|
+
|
|
61
|
+
### 新增
|
|
62
|
+
|
|
63
|
+
- `Message`、同步和异步 `publish()`、同步和异步 `delay()`、List、Streams
|
|
64
|
+
和延迟任务后端新增一等 `trace_id` 支持。
|
|
65
|
+
- trace 会穿过消息 envelope、重试、死信、延迟释放和重放流程,并兼容已有的
|
|
66
|
+
`headers["trace_id"]` 消息。
|
|
67
|
+
- 监控事件和 CLI 消息输出新增 `trace_id`,方便按链路聚合生命周期事件。
|
|
68
|
+
- CLI 的 `publish` 和 `delay` 命令新增 `--trace-id`。
|
|
69
|
+
- 新增 `new_trace_id()` helper,方便调用方生成 RedQueue trace 标识。
|
|
70
|
+
|
|
71
|
+
## [0.12.0] - 2026-06-23
|
|
72
|
+
|
|
73
|
+
### Added
|
|
39
74
|
|
|
40
75
|
- Added the `redqueue` CLI and `python -m redqueue` module entry point for
|
|
41
76
|
developer debugging.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redqueue
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Redis-backed Python message queue library with List, Streams, delayed tasks, and monitoring.
|
|
3
|
+
Version: 0.14.0
|
|
4
|
+
Summary: Redis-backed Python message queue library with List, Streams, delayed tasks, deduplication, and monitoring.
|
|
5
5
|
Project-URL: Homepage, https://github.com/SpringMirror-pear/redqueue
|
|
6
6
|
Project-URL: Repository, https://github.com/SpringMirror-pear/redqueue.git
|
|
7
7
|
Project-URL: Issues, https://github.com/SpringMirror-pear/redqueue/issues
|
|
@@ -52,6 +52,7 @@ https://github.com/SpringMirror-pear/redqueue.git
|
|
|
52
52
|
- Redis connection pool managers for shared sync and async resources.
|
|
53
53
|
- `redqueue` CLI for local debugging and operational checks.
|
|
54
54
|
- First-class `trace_id` propagation for lifecycle tracing.
|
|
55
|
+
- Opt-in message deduplication with Redis `SET NX` and TTL windows.
|
|
55
56
|
- Unified exception hierarchy with structured context.
|
|
56
57
|
- Monitoring events for publish, consume, ack, nack, retry, dead letter, delay,
|
|
57
58
|
and backend errors.
|
|
@@ -75,6 +76,7 @@ Redis:
|
|
|
75
76
|
| Streams | `>=5.0` | Uses `XADD`, `XGROUP CREATE`, `XREADGROUP` |
|
|
76
77
|
| Streams auto claim | `>=6.2` | Uses `XAUTOCLAIM`; Redis 5.x uses `XPENDING`/`XCLAIM` fallback |
|
|
77
78
|
| Delayed tasks | `>=1.2` | Uses `ZADD` and timestamp scores |
|
|
79
|
+
| Message deduplication | `>=2.6.12` | Uses `SET` with `NX` and `EX`/`PX` |
|
|
78
80
|
|
|
79
81
|
## Installation
|
|
80
82
|
|
|
@@ -210,6 +212,23 @@ client.delay({"to": "later@example.com"}, delay_seconds=60, trace_id="trace-123"
|
|
|
210
212
|
released = client.schedule_due(limit=100)
|
|
211
213
|
```
|
|
212
214
|
|
|
215
|
+
Message deduplication:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from redqueue import DeduplicationConfig, QueueClient
|
|
219
|
+
|
|
220
|
+
client = QueueClient.from_url(
|
|
221
|
+
"redis://127.0.0.1:6379/0",
|
|
222
|
+
queue="emails",
|
|
223
|
+
deduplication=DeduplicationConfig(enabled=True, ttl_seconds=3600),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
first_id = client.publish({"order": 1}, dedup_key="order-1")
|
|
227
|
+
second_id = client.publish({"order": 1}, dedup_key="order-1")
|
|
228
|
+
|
|
229
|
+
assert second_id == first_id
|
|
230
|
+
```
|
|
231
|
+
|
|
213
232
|
Trace IDs:
|
|
214
233
|
|
|
215
234
|
```python
|
|
@@ -367,17 +386,21 @@ REDQUEUE_REDIS_URL=redis://127.0.0.1:6379/0 PYTHONPATH=src python -m pytest -m "
|
|
|
367
386
|
|
|
368
387
|
Latest local run on Python `3.14.5`:
|
|
369
388
|
|
|
370
|
-
- Full test suite without `REDQUEUE_REDIS_URL`: `
|
|
371
|
-
- Real Redis
|
|
389
|
+
- Full test suite without `REDQUEUE_REDIS_URL`: `114 passed, 10 skipped`.
|
|
390
|
+
- Real Redis integration suite: `10 passed` with
|
|
391
|
+
`redis://127.0.0.1:6379/0`.
|
|
392
|
+
- Real Redis availability suite: `5 passed` with
|
|
372
393
|
`redis://127.0.0.1:6379/0`.
|
|
373
394
|
- Real Redis server: Redis for Windows `5.0.14.1`.
|
|
374
395
|
- Availability suite: covers List processing recovery, List dead-letter
|
|
375
396
|
requeue, Streams Redis `<5.0` compatibility rejection, Streams Redis 5.x
|
|
376
397
|
pending recovery fallback, Streams dead-letter requeue, delayed task publish
|
|
377
|
-
failure rollback, missing delayed payload errors,
|
|
378
|
-
|
|
398
|
+
failure rollback, missing delayed payload errors, publish-time deduplication,
|
|
399
|
+
delayed task deduplication, async List recovery, async Streams dead-letter
|
|
400
|
+
requeue, and async delayed task rollback.
|
|
379
401
|
- Real Redis availability suite additionally validates List recovery, Streams
|
|
380
|
-
dead-letter requeue,
|
|
402
|
+
dead-letter requeue, delayed task rollback, List deduplication, and delayed
|
|
403
|
+
task deduplication against a running Redis server.
|
|
381
404
|
|
|
382
405
|
## Performance Results
|
|
383
406
|
|
|
@@ -14,10 +14,11 @@ https://github.com/SpringMirror-pear/redqueue.git
|
|
|
14
14
|
`BRPOPLPUSH`。
|
|
15
15
|
- 基于 Redis Streams 的消费组后端,Streams 要求 Redis `>=5.0`。
|
|
16
16
|
- 基于 Redis Sorted Set 的延迟任务。
|
|
17
|
-
- 同步客户端 `QueueClient` 与异步客户端 `AsyncQueueClient`。
|
|
17
|
+
- 同步客户端 `QueueClient` 与异步客户端 `AsyncQueueClient`。
|
|
18
18
|
- 支持同步和异步 Redis 连接池管理器,方便多个客户端共享连接池。
|
|
19
19
|
- 提供 `redqueue` CLI,用于本地调试和运行时检查。
|
|
20
20
|
- 一等 `trace_id` 链路追踪能力,贯穿消息生命周期。
|
|
21
|
+
- 可选消息去重能力,基于 Redis `SET NX` 和 TTL 窗口。
|
|
21
22
|
- 带结构化上下文的统一异常体系。
|
|
22
23
|
- 针对发布、消费、确认、拒绝、重试、死信、延迟和后端错误的监控事件。
|
|
23
24
|
- 通过 `INFO server` 探测 Redis 能力。
|
|
@@ -37,9 +38,10 @@ Redis:
|
|
|
37
38
|
| --- | --- | --- |
|
|
38
39
|
| List 阻塞消费 | `>=2.0` | 以 `BLPOP` 系列能力为基础 |
|
|
39
40
|
| List 可靠搬移 | `>=2.2` | 使用 `BRPOPLPUSH`;Redis `>=6.2` 优先使用 `BLMOVE` |
|
|
40
|
-
| Streams | `>=5.0` | 使用 `XADD`、`XGROUP CREATE`、`XREADGROUP` |
|
|
41
|
-
| Streams 自动认领 | `>=6.2` | 使用 `XAUTOCLAIM`;Redis 5.x 回退 `XPENDING`/`XCLAIM` |
|
|
42
|
-
| 延迟任务 | `>=1.2` | 使用 `ZADD` 和时间戳 score |
|
|
41
|
+
| Streams | `>=5.0` | 使用 `XADD`、`XGROUP CREATE`、`XREADGROUP` |
|
|
42
|
+
| Streams 自动认领 | `>=6.2` | 使用 `XAUTOCLAIM`;Redis 5.x 回退 `XPENDING`/`XCLAIM` |
|
|
43
|
+
| 延迟任务 | `>=1.2` | 使用 `ZADD` 和时间戳 score |
|
|
44
|
+
| 消息去重 | `>=2.6.12` | 使用带 `NX` 和 `EX`/`PX` 的 `SET` |
|
|
43
45
|
|
|
44
46
|
## 安装
|
|
45
47
|
|
|
@@ -72,16 +74,16 @@ redqueue stats --url redis://127.0.0.1:6379/0 --queue emails
|
|
|
72
74
|
|
|
73
75
|
发布和消费消息:
|
|
74
76
|
|
|
75
|
-
```bash
|
|
76
|
-
redqueue publish --queue emails --payload '{"to":"user@example.com"}'
|
|
77
|
-
redqueue consume --queue emails --timeout 1 --ack
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
发布时指定 trace:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
redqueue publish --queue emails --payload '{"to":"user@example.com"}' --trace-id trace-123
|
|
84
|
-
```
|
|
77
|
+
```bash
|
|
78
|
+
redqueue publish --queue emails --payload '{"to":"user@example.com"}'
|
|
79
|
+
redqueue consume --queue emails --timeout 1 --ack
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
发布时指定 trace:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
redqueue publish --queue emails --payload '{"to":"user@example.com"}' --trace-id trace-123
|
|
86
|
+
```
|
|
85
87
|
|
|
86
88
|
调试延迟任务:
|
|
87
89
|
|
|
@@ -102,8 +104,8 @@ redqueue dead-letters --queue emails --limit 20
|
|
|
102
104
|
|
|
103
105
|
同步 List 队列:
|
|
104
106
|
|
|
105
|
-
```python
|
|
106
|
-
from redqueue import QueueClient, new_trace_id
|
|
107
|
+
```python
|
|
108
|
+
from redqueue import QueueClient, new_trace_id
|
|
107
109
|
|
|
108
110
|
client = QueueClient.from_url(
|
|
109
111
|
"redis://127.0.0.1:6379/0",
|
|
@@ -111,15 +113,15 @@ client = QueueClient.from_url(
|
|
|
111
113
|
backend="list",
|
|
112
114
|
)
|
|
113
115
|
|
|
114
|
-
trace_id = new_trace_id()
|
|
115
|
-
message_id = client.publish({"to": "user@example.com"}, trace_id=trace_id)
|
|
116
|
-
message = client.consume(timeout=1)
|
|
117
|
-
|
|
118
|
-
if message is not None:
|
|
119
|
-
try:
|
|
120
|
-
print(message.trace_id)
|
|
121
|
-
print(message.payload)
|
|
122
|
-
client.ack(message)
|
|
116
|
+
trace_id = new_trace_id()
|
|
117
|
+
message_id = client.publish({"to": "user@example.com"}, trace_id=trace_id)
|
|
118
|
+
message = client.consume(timeout=1)
|
|
119
|
+
|
|
120
|
+
if message is not None:
|
|
121
|
+
try:
|
|
122
|
+
print(message.trace_id)
|
|
123
|
+
print(message.payload)
|
|
124
|
+
client.ack(message)
|
|
123
125
|
except Exception:
|
|
124
126
|
client.retry(message, reason="handler failed")
|
|
125
127
|
```
|
|
@@ -165,35 +167,52 @@ async def main() -> None:
|
|
|
165
167
|
asyncio.run(main())
|
|
166
168
|
```
|
|
167
169
|
|
|
168
|
-
延迟任务:
|
|
170
|
+
延迟任务:
|
|
169
171
|
|
|
170
172
|
```python
|
|
171
173
|
from redqueue import QueueClient
|
|
172
174
|
|
|
173
|
-
client = QueueClient.from_url("redis://127.0.0.1:6379/0", queue="emails")
|
|
174
|
-
client.delay({"to": "later@example.com"}, delay_seconds=60, trace_id="trace-123")
|
|
175
|
+
client = QueueClient.from_url("redis://127.0.0.1:6379/0", queue="emails")
|
|
176
|
+
client.delay({"to": "later@example.com"}, delay_seconds=60, trace_id="trace-123")
|
|
175
177
|
released = client.schedule_due(limit=100)
|
|
176
178
|
```
|
|
177
179
|
|
|
178
|
-
|
|
180
|
+
消息去重:
|
|
179
181
|
|
|
180
182
|
```python
|
|
181
|
-
from redqueue import
|
|
183
|
+
from redqueue import DeduplicationConfig, QueueClient
|
|
182
184
|
|
|
183
|
-
hook = InMemoryMonitoringHook()
|
|
184
185
|
client = QueueClient.from_url(
|
|
185
186
|
"redis://127.0.0.1:6379/0",
|
|
186
187
|
queue="emails",
|
|
187
|
-
|
|
188
|
+
deduplication=DeduplicationConfig(enabled=True, ttl_seconds=3600),
|
|
188
189
|
)
|
|
189
190
|
|
|
190
|
-
|
|
191
|
-
client.publish({"
|
|
192
|
-
message = client.consume(timeout=1)
|
|
191
|
+
first_id = client.publish({"order": 1}, dedup_key="order-1")
|
|
192
|
+
second_id = client.publish({"order": 1}, dedup_key="order-1")
|
|
193
193
|
|
|
194
|
-
assert
|
|
195
|
-
assert hook.events[-1].trace_id == trace_id
|
|
194
|
+
assert second_id == first_id
|
|
196
195
|
```
|
|
196
|
+
|
|
197
|
+
链路追踪:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from redqueue import InMemoryMonitoringHook, QueueClient, new_trace_id
|
|
201
|
+
|
|
202
|
+
hook = InMemoryMonitoringHook()
|
|
203
|
+
client = QueueClient.from_url(
|
|
204
|
+
"redis://127.0.0.1:6379/0",
|
|
205
|
+
queue="emails",
|
|
206
|
+
monitoring=hook,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
trace_id = new_trace_id()
|
|
210
|
+
client.publish({"to": "user@example.com"}, trace_id=trace_id)
|
|
211
|
+
message = client.consume(timeout=1)
|
|
212
|
+
|
|
213
|
+
assert message.trace_id == trace_id
|
|
214
|
+
assert hook.events[-1].trace_id == trace_id
|
|
215
|
+
```
|
|
197
216
|
|
|
198
217
|
连接池管理:
|
|
199
218
|
|
|
@@ -329,15 +348,16 @@ REDQUEUE_REDIS_URL=redis://127.0.0.1:6379/0 PYTHONPATH=src python -m pytest -m "
|
|
|
329
348
|
|
|
330
349
|
Python `3.14.5` 最新本地运行结果:
|
|
331
350
|
|
|
332
|
-
- 未设置 `REDQUEUE_REDIS_URL` 时的完整测试套件:`
|
|
333
|
-
- 真实 Redis
|
|
351
|
+
- 未设置 `REDQUEUE_REDIS_URL` 时的完整测试套件:`114 passed, 10 skipped`。
|
|
352
|
+
- 真实 Redis 集成套件:使用 `redis://127.0.0.1:6379/0` 时 `10 passed`。
|
|
353
|
+
- 真实 Redis 可用性套件:使用 `redis://127.0.0.1:6379/0` 时 `5 passed`。
|
|
334
354
|
- 真实 Redis 服务端:Redis for Windows `5.0.14.1`。
|
|
335
355
|
- 可用性套件覆盖:List processing 恢复、List 死信重放、Streams Redis
|
|
336
356
|
`<5.0` 兼容性拒绝、Streams Redis 5.x pending 恢复回退、Streams 死信
|
|
337
|
-
重放、延迟任务发布失败回滚、延迟 payload
|
|
338
|
-
Streams 死信重放和异步延迟任务回滚。
|
|
339
|
-
- 真实 Redis 可用性套件额外验证了运行中 Redis 服务上的 List 恢复、Streams
|
|
340
|
-
|
|
357
|
+
重放、延迟任务发布失败回滚、延迟 payload 缺失错误、发布时去重、延迟任务
|
|
358
|
+
去重、异步 List 恢复、异步 Streams 死信重放和异步延迟任务回滚。
|
|
359
|
+
- 真实 Redis 可用性套件额外验证了运行中 Redis 服务上的 List 恢复、Streams
|
|
360
|
+
死信重放、延迟任务回滚、List 去重和延迟任务去重。
|
|
341
361
|
|
|
342
362
|
## 性能测试结果
|
|
343
363
|
|
|
@@ -15,10 +15,11 @@ https://github.com/SpringMirror-pear/redqueue.git
|
|
|
15
15
|
fallback on older compatible Redis versions.
|
|
16
16
|
- Redis Streams backend with consumer groups. Streams require Redis `>=5.0`.
|
|
17
17
|
- Delayed tasks based on Redis Sorted Set.
|
|
18
|
-
- Sync client `QueueClient` and async client `AsyncQueueClient`.
|
|
18
|
+
- Sync client `QueueClient` and async client `AsyncQueueClient`.
|
|
19
19
|
- Redis connection pool managers for shared sync and async resources.
|
|
20
20
|
- `redqueue` CLI for local debugging and operational checks.
|
|
21
21
|
- First-class `trace_id` propagation for lifecycle tracing.
|
|
22
|
+
- Opt-in message deduplication with Redis `SET NX` and TTL windows.
|
|
22
23
|
- Unified exception hierarchy with structured context.
|
|
23
24
|
- Monitoring events for publish, consume, ack, nack, retry, dead letter, delay,
|
|
24
25
|
and backend errors.
|
|
@@ -39,9 +40,10 @@ Redis:
|
|
|
39
40
|
| --- | --- | --- |
|
|
40
41
|
| List blocking consume | `>=2.0` | Uses `BLPOP` family compatibility baseline |
|
|
41
42
|
| List reliable move | `>=2.2` | Uses `BRPOPLPUSH`; `BLMOVE` preferred on `>=6.2` |
|
|
42
|
-
| Streams | `>=5.0` | Uses `XADD`, `XGROUP CREATE`, `XREADGROUP` |
|
|
43
|
-
| Streams auto claim | `>=6.2` | Uses `XAUTOCLAIM`; Redis 5.x uses `XPENDING`/`XCLAIM` fallback |
|
|
44
|
-
| Delayed tasks | `>=1.2` | Uses `ZADD` and timestamp scores |
|
|
43
|
+
| Streams | `>=5.0` | Uses `XADD`, `XGROUP CREATE`, `XREADGROUP` |
|
|
44
|
+
| Streams auto claim | `>=6.2` | Uses `XAUTOCLAIM`; Redis 5.x uses `XPENDING`/`XCLAIM` fallback |
|
|
45
|
+
| Delayed tasks | `>=1.2` | Uses `ZADD` and timestamp scores |
|
|
46
|
+
| Message deduplication | `>=2.6.12` | Uses `SET` with `NX` and `EX`/`PX` |
|
|
45
47
|
|
|
46
48
|
## Installation
|
|
47
49
|
|
|
@@ -74,16 +76,16 @@ redqueue stats --url redis://127.0.0.1:6379/0 --queue emails
|
|
|
74
76
|
|
|
75
77
|
Publish and consume messages:
|
|
76
78
|
|
|
77
|
-
```bash
|
|
78
|
-
redqueue publish --queue emails --payload '{"to":"user@example.com"}'
|
|
79
|
-
redqueue consume --queue emails --timeout 1 --ack
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Publish with trace correlation:
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
redqueue publish --queue emails --payload '{"to":"user@example.com"}' --trace-id trace-123
|
|
86
|
-
```
|
|
79
|
+
```bash
|
|
80
|
+
redqueue publish --queue emails --payload '{"to":"user@example.com"}'
|
|
81
|
+
redqueue consume --queue emails --timeout 1 --ack
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Publish with trace correlation:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
redqueue publish --queue emails --payload '{"to":"user@example.com"}' --trace-id trace-123
|
|
88
|
+
```
|
|
87
89
|
|
|
88
90
|
Delayed task debugging:
|
|
89
91
|
|
|
@@ -104,8 +106,8 @@ All command output is JSON so it can be piped into scripts or log processors.
|
|
|
104
106
|
|
|
105
107
|
Synchronous List queue:
|
|
106
108
|
|
|
107
|
-
```python
|
|
108
|
-
from redqueue import QueueClient, new_trace_id
|
|
109
|
+
```python
|
|
110
|
+
from redqueue import QueueClient, new_trace_id
|
|
109
111
|
|
|
110
112
|
client = QueueClient.from_url(
|
|
111
113
|
"redis://127.0.0.1:6379/0",
|
|
@@ -113,15 +115,15 @@ client = QueueClient.from_url(
|
|
|
113
115
|
backend="list",
|
|
114
116
|
)
|
|
115
117
|
|
|
116
|
-
trace_id = new_trace_id()
|
|
117
|
-
message_id = client.publish({"to": "user@example.com"}, trace_id=trace_id)
|
|
118
|
-
message = client.consume(timeout=1)
|
|
119
|
-
|
|
120
|
-
if message is not None:
|
|
121
|
-
try:
|
|
122
|
-
print(message.trace_id)
|
|
123
|
-
print(message.payload)
|
|
124
|
-
client.ack(message)
|
|
118
|
+
trace_id = new_trace_id()
|
|
119
|
+
message_id = client.publish({"to": "user@example.com"}, trace_id=trace_id)
|
|
120
|
+
message = client.consume(timeout=1)
|
|
121
|
+
|
|
122
|
+
if message is not None:
|
|
123
|
+
try:
|
|
124
|
+
print(message.trace_id)
|
|
125
|
+
print(message.payload)
|
|
126
|
+
client.ack(message)
|
|
125
127
|
except Exception:
|
|
126
128
|
client.retry(message, reason="handler failed")
|
|
127
129
|
```
|
|
@@ -167,35 +169,52 @@ async def main() -> None:
|
|
|
167
169
|
asyncio.run(main())
|
|
168
170
|
```
|
|
169
171
|
|
|
170
|
-
Delayed task:
|
|
172
|
+
Delayed task:
|
|
171
173
|
|
|
172
174
|
```python
|
|
173
175
|
from redqueue import QueueClient
|
|
174
176
|
|
|
175
|
-
client = QueueClient.from_url("redis://127.0.0.1:6379/0", queue="emails")
|
|
176
|
-
client.delay({"to": "later@example.com"}, delay_seconds=60, trace_id="trace-123")
|
|
177
|
+
client = QueueClient.from_url("redis://127.0.0.1:6379/0", queue="emails")
|
|
178
|
+
client.delay({"to": "later@example.com"}, delay_seconds=60, trace_id="trace-123")
|
|
177
179
|
released = client.schedule_due(limit=100)
|
|
178
180
|
```
|
|
179
181
|
|
|
180
|
-
|
|
182
|
+
Message deduplication:
|
|
181
183
|
|
|
182
184
|
```python
|
|
183
|
-
from redqueue import
|
|
185
|
+
from redqueue import DeduplicationConfig, QueueClient
|
|
184
186
|
|
|
185
|
-
hook = InMemoryMonitoringHook()
|
|
186
187
|
client = QueueClient.from_url(
|
|
187
188
|
"redis://127.0.0.1:6379/0",
|
|
188
189
|
queue="emails",
|
|
189
|
-
|
|
190
|
+
deduplication=DeduplicationConfig(enabled=True, ttl_seconds=3600),
|
|
190
191
|
)
|
|
191
192
|
|
|
192
|
-
|
|
193
|
-
client.publish({"
|
|
194
|
-
message = client.consume(timeout=1)
|
|
193
|
+
first_id = client.publish({"order": 1}, dedup_key="order-1")
|
|
194
|
+
second_id = client.publish({"order": 1}, dedup_key="order-1")
|
|
195
195
|
|
|
196
|
-
assert
|
|
197
|
-
assert hook.events[-1].trace_id == trace_id
|
|
196
|
+
assert second_id == first_id
|
|
198
197
|
```
|
|
198
|
+
|
|
199
|
+
Trace IDs:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from redqueue import InMemoryMonitoringHook, QueueClient, new_trace_id
|
|
203
|
+
|
|
204
|
+
hook = InMemoryMonitoringHook()
|
|
205
|
+
client = QueueClient.from_url(
|
|
206
|
+
"redis://127.0.0.1:6379/0",
|
|
207
|
+
queue="emails",
|
|
208
|
+
monitoring=hook,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
trace_id = new_trace_id()
|
|
212
|
+
client.publish({"to": "user@example.com"}, trace_id=trace_id)
|
|
213
|
+
message = client.consume(timeout=1)
|
|
214
|
+
|
|
215
|
+
assert message.trace_id == trace_id
|
|
216
|
+
assert hook.events[-1].trace_id == trace_id
|
|
217
|
+
```
|
|
199
218
|
|
|
200
219
|
Connection pool management:
|
|
201
220
|
|
|
@@ -334,17 +353,21 @@ REDQUEUE_REDIS_URL=redis://127.0.0.1:6379/0 PYTHONPATH=src python -m pytest -m "
|
|
|
334
353
|
|
|
335
354
|
Latest local run on Python `3.14.5`:
|
|
336
355
|
|
|
337
|
-
- Full test suite without `REDQUEUE_REDIS_URL`: `
|
|
338
|
-
- Real Redis
|
|
339
|
-
`redis://127.0.0.1:6379/0`.
|
|
356
|
+
- Full test suite without `REDQUEUE_REDIS_URL`: `114 passed, 10 skipped`.
|
|
357
|
+
- Real Redis integration suite: `10 passed` with
|
|
358
|
+
`redis://127.0.0.1:6379/0`.
|
|
359
|
+
- Real Redis availability suite: `5 passed` with
|
|
360
|
+
`redis://127.0.0.1:6379/0`.
|
|
340
361
|
- Real Redis server: Redis for Windows `5.0.14.1`.
|
|
341
362
|
- Availability suite: covers List processing recovery, List dead-letter
|
|
342
363
|
requeue, Streams Redis `<5.0` compatibility rejection, Streams Redis 5.x
|
|
343
364
|
pending recovery fallback, Streams dead-letter requeue, delayed task publish
|
|
344
|
-
failure rollback, missing delayed payload errors,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
365
|
+
failure rollback, missing delayed payload errors, publish-time deduplication,
|
|
366
|
+
delayed task deduplication, async List recovery, async Streams dead-letter
|
|
367
|
+
requeue, and async delayed task rollback.
|
|
368
|
+
- Real Redis availability suite additionally validates List recovery, Streams
|
|
369
|
+
dead-letter requeue, delayed task rollback, List deduplication, and delayed
|
|
370
|
+
task deduplication against a running Redis server.
|
|
348
371
|
|
|
349
372
|
## Performance Results
|
|
350
373
|
|