Version: 0.5.0.dev.260310

refactor: ♻️ 调整 Outbox 写入时序并移除 Kafka 首包同步投递逻辑

* 将 `outbox` 表写入逻辑后置到 LLM 请求之后,减少主链路阻塞
* 删除 Codex 生成的 Kafka 首包同步投递抽象逻辑,简化消息发送流程
* 优化 SSE 首字到达时间,整体降低约 1s 延迟
* 当前在请求 LLM 之前的流程全部为 Redis 操作,显著降低 IO 开销

docs: 📊 保留 SSE 链路性能打点逻辑

* 保留原有 SSE 全链路打点计时代码,便于后续性能排查与分析
* 当前默认注释,如需使用可手动启用进行性能调试
This commit is contained in:
Losita
2026-03-10 23:10:09 +08:00
parent 959049db42
commit 912a6d8cfe
6 changed files with 138 additions and 123 deletions

View File

@@ -11,11 +11,6 @@ import (
"gorm.io/gorm/clause"
)
// OutboxDAO 封装 outbox 表读写逻辑。
// outbox 状态机约定:
// pending -> published -> consumed成功终态
// pending/published -> pending失败重试
// pending/published -> dead不可恢复或达到最大重试
type OutboxDAO struct {
db *gorm.DB
}
@@ -24,11 +19,6 @@ func NewOutboxDAO(db *gorm.DB) *OutboxDAO {
return &OutboxDAO{db: db}
}
// CreateChatHistoryMessage 创建“聊天记录持久化”的 outbox 消息。
// 关键点:
// 1) 初始状态为 pending
// 2) NextRetryAt=now允许被“首次同步投递”或“扫描器”立即处理
// 3) payload 以 JSON 形式落表,保证消费端可重放。
func (d *OutboxDAO) CreateChatHistoryMessage(ctx context.Context, topic, messageKey string, payload model.ChatHistoryPersistPayload, maxRetry int) (int64, error) {
if maxRetry <= 0 {
maxRetry = 20
@@ -62,8 +52,6 @@ func (d *OutboxDAO) GetByID(ctx context.Context, id int64) (*model.AgentOutboxMe
return &msg, nil
}
// ListDueMessages 查询“到期可重试”的 pending 消息。
// 查询条件status=pending 且 next_retry_at<=当前时间。
func (d *OutboxDAO) ListDueMessages(ctx context.Context, limit int) ([]model.AgentOutboxMessage, error) {
if limit <= 0 {
limit = 100
@@ -81,10 +69,7 @@ func (d *OutboxDAO) ListDueMessages(ctx context.Context, limit int) ([]model.Age
return messages, nil
}
// MarkPublished 将消息标记为“已写入 Kafka”
// 注意:
// 1) 仅在非终态(非 consumed/dead下更新避免覆盖最终状态
// 2) 清理 next_retry_at避免已投递消息继续被扫描器重复拉取。
// MarkPublished 仅在消息未进入最终态时更新为 published避免覆盖 consumed/dead
func (d *OutboxDAO) MarkPublished(ctx context.Context, id int64) error {
now := time.Now()
updates := map[string]interface{}{
@@ -100,7 +85,6 @@ func (d *OutboxDAO) MarkPublished(ctx context.Context, id int64) error {
return result.Error
}
// MarkDead 将消息置为死信终态。
func (d *OutboxDAO) MarkDead(ctx context.Context, id int64, reason string) error {
now := time.Now()
lastErr := truncateError(reason)
@@ -113,11 +97,6 @@ func (d *OutboxDAO) MarkDead(ctx context.Context, id int64, reason string) error
return d.db.WithContext(ctx).Model(&model.AgentOutboxMessage{}).Where("id = ?", id).Updates(updates).Error
}
// MarkFailedForRetry 在失败时推进重试状态。
// 关键点:
// 1) 事务 + FOR UPDATE 防并发覆盖(尤其是 dispatch/consume 并发场景);
// 2) retry_count 自增;
// 3) 达到 max_retry 后转 dead否则按指数退避设置 next_retry_at。
func (d *OutboxDAO) MarkFailedForRetry(ctx context.Context, id int64, reason string) error {
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var msg model.AgentOutboxMessage
@@ -125,7 +104,6 @@ func (d *OutboxDAO) MarkFailedForRetry(ctx context.Context, id int64, reason str
if err != nil {
return err
}
// 终态直接跳过,保持幂等。
if msg.Status == model.OutboxStatusConsumed || msg.Status == model.OutboxStatusDead {
return nil
}
@@ -153,8 +131,6 @@ func (d *OutboxDAO) MarkFailedForRetry(ctx context.Context, id int64, reason str
})
}
// PersistChatHistoryAndMarkConsumed 执行“消费业务”并回写 consumed。
// 这里把“写 chat_histories”与“更新 outbox 状态”放进同一事务,保证原子性。
func (d *OutboxDAO) PersistChatHistoryAndMarkConsumed(ctx context.Context, outboxID int64, payload model.ChatHistoryPersistPayload) error {
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var outboxMsg model.AgentOutboxMessage
@@ -165,7 +141,6 @@ func (d *OutboxDAO) PersistChatHistoryAndMarkConsumed(ctx context.Context, outbo
}
return err
}
// 幂等保护:重复消费不重复落库。
if outboxMsg.Status == model.OutboxStatusConsumed {
return nil
}
@@ -197,7 +172,6 @@ func (d *OutboxDAO) PersistChatHistoryAndMarkConsumed(ctx context.Context, outbo
})
}
// calcRetryBackoff 指数退避(上限 2^5=32 秒)。
func calcRetryBackoff(retryCount int) time.Duration {
if retryCount <= 0 {
return time.Second