Refactor Kafka publishing without DB outbox
This commit is contained in:
+6
-14
@@ -722,14 +722,14 @@ IM Server 已接入 `im.message.persist`,用于把在线发送链路和最终
|
||||
|
||||
当前处理方式:
|
||||
|
||||
1. WS/HTTP 发送入口先同步完成权限校验、业务幂等、`seq_id` 分配和 Topic 游标占用。
|
||||
2. Kafka 开启时,发送入口先写租户库 `im_kafka_outbox`,由 Outbox worker 可靠生产 `im.message.persist`,客户端仍立即拿到已分配的 `seq_id`。
|
||||
1. WS/HTTP 发送入口先完成权限校验,并由 topic 内存态分配 `seq_id`。
|
||||
2. Kafka 开启时,发送入口直接生产 `im.message.persist`,不查库、不落库、不写 DB outbox;客户端在 Kafka 写入成功后拿到已分配的 `seq_id`。
|
||||
3. Kafka 关闭时,继续走原同步落库路径,兼容本地和未接 Kafka 环境。
|
||||
4. 消费者按 `message_kind` 区分私聊与群聊:
|
||||
- `p2p` 写入 `im_messages`。
|
||||
- `group` 写入 `im_group_messages`。
|
||||
5. 消费侧复用 `im_kafka_consume_log` 做 `event_id + consumer_group` 幂等。
|
||||
6. 失败按统一退避重试,超过上限写入 `im_kafka_dead_letters` 并投递 `im.dead.letter`。
|
||||
6. 失败按统一退避重试 3 次,超过上限先投递 `im.dead.letter`,再由 dead letter consumer 写入 `im_kafka_dead_letters`。
|
||||
|
||||
### 11.10 IM Server 已接入的缓存失效消费
|
||||
|
||||
@@ -874,17 +874,9 @@ IM Server 已在 `im.admin.command` 中接入用户治理类后台动作。Admin
|
||||
}
|
||||
```
|
||||
|
||||
### 11.9 IM Server 已接入的 Kafka Outbox Worker
|
||||
### 11.9 IM Server 已移除 Kafka DB Outbox
|
||||
|
||||
IM Server 已接入租户库 `im_kafka_outbox` 通用发送 worker,用于避免“业务库写成功但 Kafka 未发送”的事件丢失。
|
||||
|
||||
当前能力:
|
||||
|
||||
1. 启动后按 active 租户扫描每个租户库的 `im_kafka_outbox`。
|
||||
2. 扫描 `pending/failed` 且已到 `next_retry_at` 的事件。
|
||||
3. 发送成功后标记 `sent`、写入 `sent_at`。
|
||||
4. 发送失败后标记 `failed`,按 Kafka 配置的 `retry_backoff_ms` 写入下一次重试时间。
|
||||
5. 首个接入方为 `im.message.persist`,后续文件生命周期、工作台事件、审计事件可复用同一 outbox。
|
||||
IM Server 不再维护租户库 `im_kafka_outbox`,Kafka 事件统一由发送入口直接生产。生产侧使用 Kafka writer 的 `max_retry_count` 控制发送重试;消费侧使用 `im_kafka_consume_log` 做处理幂等和失败重试,超过上限后进入 `im.dead.letter`。
|
||||
|
||||
本轮暂不实现 `im.message.push` 离线 Push Kafka 化,已记录到 [IM 第一至十一章后续增强项](./IM-未实现后续增强项.md)。
|
||||
|
||||
@@ -930,7 +922,7 @@ IM Server 已接入 `im.workbench.event` 的首批生产与消费。
|
||||
|
||||
### 11.12 IM Server 已接入的审计事件
|
||||
|
||||
IM Server 已接入 `im.audit.event` 的可靠生产,审计事件写入租户库 `im_kafka_outbox` 后由 Kafka Outbox worker 发送。
|
||||
IM Server 已接入 `im.audit.event` 的直接生产,审计事件不写 DB outbox。
|
||||
|
||||
当前审计范围:
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ SOURCE docs/sql/IM-Kafka基础表SQL.sql;
|
||||
| 表 | 用途 |
|
||||
| --- | --- |
|
||||
| `im_kafka_consume_log` | 消费幂等、重试状态。 |
|
||||
| `im_kafka_outbox` | IM 侧可靠生产 Kafka 事件。 |
|
||||
| `im_kafka_dead_letters` | 死信记录与人工处理状态。 |
|
||||
| `im_messages` | 私聊消息存储。 |
|
||||
| `im_group_messages` | 群聊消息存储。 |
|
||||
@@ -886,15 +885,7 @@ GET /v1/internal/admin-query/tenants/default/kafka/dead-letters/{dead_id}
|
||||
|
||||
### TC-KAFKA-030 Outbox 可靠投递
|
||||
|
||||
目标:验证 IM 侧 Kafka Outbox 防止业务成功但 Kafka 丢事件。
|
||||
|
||||
步骤:
|
||||
|
||||
1. 临时停止 Kafka。
|
||||
2. 执行会产生 outbox 的操作,如发送消息、提交工作台事件。
|
||||
3. 查询 `im_kafka_outbox`。
|
||||
4. 恢复 Kafka。
|
||||
5. 等待 outbox worker 投递。
|
||||
目标:验证 IM 侧 Kafka 直接生产失败时不会产生 DB outbox,调用方收到发送失败。
|
||||
|
||||
预期:
|
||||
|
||||
@@ -958,7 +949,7 @@ GET /v1/internal/admin-query/tenants/default/kafka/dead-letters/{dead_id}
|
||||
| 进入死信 | 查 `im_kafka_dead_letters.envelope/last_error`,修复后用重放命令。 |
|
||||
| 用户没有被踢下线 | 查目标 `user_id` 是否是 IM 用户 ID;是否连接在当前 IM 节点;跨节点踢人是否需要后续 Redis fanout 扩展。 |
|
||||
| 群治理未生效 | 查 `topic/version/actor_user_id` 是否正确,目标用户是否为群成员。 |
|
||||
| 消息未落库 | 查 `im_kafka_outbox` 是否 sent,`im.message.persist` 是否消费成功。 |
|
||||
| 消息未落库 | 查 `im.message.persist` 是否消费成功,以及 `im_kafka_consume_log.last_reason/last_error`。 |
|
||||
| 附件未绑定 | 查消息是否带附件、`im.file.lifecycle` 是否消费成功。 |
|
||||
| 内部 API 401 | 查 secret、签名文本、RequestURI、timestamp、nonce 是否一致。 |
|
||||
| 配置没有刷新 | 查是否发送 `im.config.event` 或 `im.cache.invalidate`,IM 是否清理了对应 namespace。 |
|
||||
@@ -1023,7 +1014,7 @@ GET /v1/internal/admin-query/tenants/default/kafka/dead-letters/{dead_id}
|
||||
| 文件生命周期 | 上传完成、附件绑定、群文件登记/删除、OSS 元数据和引用表。 |
|
||||
| 工作台事件 | 会议、日报/月报、总经理信箱、红点/系统通知投递。 |
|
||||
| 死信闭环 | 死信查询、忽略、解决、重放,以及重放后二次成功/失败。 |
|
||||
| Outbox | Kafka 停止/恢复时 `im_kafka_outbox`、Admin outbox 的可靠投递。 |
|
||||
| Kafka 生产失败 | Kafka 停止/恢复时,确认 IM 不写 `im_kafka_outbox`,接口/WS 返回发送失败,恢复后可重新发送。 |
|
||||
| Admin 页面 | 操作单状态从 pending/running 到 succeeded/failed/dead 的页面联调。 |
|
||||
|
||||
### 10.2 2026-08-27 完整后端链路联调
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Remove the deprecated Kafka DB outbox table from a tenant database.
|
||||
-- Run once per tenant business database after deploying the direct Kafka publish path.
|
||||
|
||||
DROP TABLE IF EXISTS im_kafka_outbox;
|
||||
@@ -29,29 +29,6 @@ CREATE TABLE IF NOT EXISTS im_kafka_consume_log (
|
||||
INDEX idx_im_kafka_consume_log_topic(source_topic, id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS im_kafka_outbox (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
event_id VARCHAR(96) NOT NULL,
|
||||
topic VARCHAR(128) NOT NULL,
|
||||
event_type VARCHAR(96) NOT NULL,
|
||||
biz_type VARCHAR(64) NOT NULL DEFAULT '',
|
||||
biz_id VARCHAR(128) NOT NULL DEFAULT '',
|
||||
key_value VARCHAR(256) NOT NULL DEFAULT '',
|
||||
envelope JSON NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'pending',
|
||||
attempts INT NOT NULL DEFAULT 0,
|
||||
next_retry_at DATETIME(3),
|
||||
sent_at DATETIME(3),
|
||||
last_reason VARCHAR(64) NOT NULL DEFAULT '',
|
||||
last_error VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_kafka_outbox_event(event_id),
|
||||
INDEX idx_im_kafka_outbox_pending(status, next_retry_at, id),
|
||||
INDEX idx_im_kafka_outbox_biz(biz_type, biz_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS im_kafka_dead_letters (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
dead_id VARCHAR(96) NOT NULL,
|
||||
|
||||
Reference in New Issue
Block a user