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

@@ -85,6 +85,8 @@ end
-- 缓冲区耗尽,扩展 LAST
local newLast = last + bufSize
redis.call('HSET', bufKey, 'LAST', newLast)
-- 推进 CURR 防止下一个调用者获得相同的 start seq
redis.call('HSET', bufKey, 'CURR', curr + 1)
-- 同步更新 msg_seq兼容旧 INCR key确保 READ 的值不回退)
local oldSeq = tonumber(redis.call('GET', seqKey))
if oldSeq and oldSeq > newLast then
@@ -178,12 +180,29 @@ func (m *SeqBufferManager) GetNextSeq(ctx context.Context, convID string, isGrou
return 0, fmt.Errorf("allocate from redis: %w", err)
}
// 3. 更新本地缓冲区
// 3. 更新本地缓冲区CAS 防止并发分配时丢失区间)
newBuf := &localSeqBuffer{curr: startSeq, last: endSeq}
m.buffers.Store(convID, newBuf)
// startSeq 已经被 Redis 分配,返回它
return startSeq, nil
for {
existing, loaded := m.buffers.LoadOrStore(convID, newBuf)
if !loaded {
// 首次插入,直接使用
return startSeq, nil
}
// 已有缓冲区,检查是否被其他 goroutine 刷新
oldBuf := existing.(*localSeqBuffer)
oldBuf.mu.Lock()
if oldBuf.curr < oldBuf.last {
// 旧缓冲区仍有余量,直接使用(丢弃本次分配,避免区间泄漏)
seq := oldBuf.curr + 1
oldBuf.curr = seq
oldBuf.mu.Unlock()
return seq, nil
}
// 旧缓冲区耗尽,替换为新分配的区间
m.buffers.Store(convID, newBuf)
oldBuf.mu.Unlock()
return startSeq, nil
}
}
// allocateFromRedis 向 Redis 预分配 seq 区间