fix(cache): enhance sequence generation atomicity and concurrency safety
All checks were successful
Build Backend / build (push) Successful in 2m20s
Build Backend / build-docker (push) Successful in 2m12s

Implement Lua scripts for atomic sequence incrementing and cold-start initialization in Redis to prevent race conditions. Improve the sequence buffer manager with CAS-like logic to prevent interval leakage during concurrent allocations. Update the message repository to use `SELECT ... FOR UPDATE` and conditional updates for `last_seq` to ensure database consistency during high concurrency.

- Add `nextSeqLua` and `initSeqLua` for atomic Redis operations
- Implement thread-safe buffer management in `seq_buffer.go`
- Add row-level locking in `message_repo.go`
- Update service layers to support group-aware sequence retrieval
This commit is contained in:
2026-05-25 14:08:06 +08:00
parent 3fc8b38184
commit 2748c80095
6 changed files with 109 additions and 26 deletions

View File

@@ -414,14 +414,21 @@ func (r *messageRepository) UpdateConversationLastSeq(conversationID string, seq
}).Error
}
// GetNextSeq 获取会话的下一个seq值
// GetNextSeq 获取会话的下一个seq值(使用 SELECT ... FOR UPDATE 保证原子性)
func (r *messageRepository) GetNextSeq(conversationID string) (int64, error) {
var conv model.Conversation
err := r.db.Select("last_seq").Where("id = ?", conversationID).First(&conv).Error
if err != nil {
return 0, err
}
return conv.LastSeq + 1, nil
var nextSeq int64
err := r.db.Transaction(func(tx *gorm.DB) error {
var conv model.Conversation
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", conversationID).First(&conv).Error; err != nil {
return err
}
nextSeq = conv.LastSeq + 1
// 立即写回,防止并发读取同一个 last_seq 值
return tx.Model(&model.Conversation{}).
Where("id = ?", conversationID).
Update("last_seq", nextSeq).Error
})
return nextSeq, err
}
// CreateMessageWithSeq 创建消息并更新seq事务操作
@@ -433,13 +440,20 @@ func (r *messageRepository) CreateMessageWithSeq(msg *model.Message) error {
return err
}
// 更新会话last_seq
if err := tx.Model(&model.Conversation{}).
Where("id = ?", msg.ConversationID).
// 更新会话last_seq仅当新 seq 更大时才更新,防止并发写入导致 last_seq 回退
result := tx.Model(&model.Conversation{}).
Where("id = ? AND last_seq < ?", msg.ConversationID, msg.Seq).
Updates(map[string]any{
"last_seq": msg.Seq,
"last_msg_time": gorm.Expr("CURRENT_TIMESTAMP"),
}).Error; err != nil {
})
if result.Error != nil {
return result.Error
}
// 当 last_seq >= msg.Seq 时(并发写入已更新),仍需刷新 last_msg_time
if err := tx.Model(&model.Conversation{}).
Where("id = ? AND last_seq >= ?", msg.ConversationID, msg.Seq).
Update("last_msg_time", gorm.Expr("CURRENT_TIMESTAMP")).Error; err != nil {
return err
}