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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user