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

@@ -481,7 +481,7 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
// 从 Redis 获取下一个 seq
if s.conversationCache != nil {
seq, err := s.conversationCache.GetNextSeq(ctx, conversationID)
seq, err := s.conversationCache.GetNextSeqWithGroup(ctx, conversationID, conv.Type == model.ConversationTypeGroup)
if err != nil {
return nil, fmt.Errorf("failed to get next seq for conversation %s: %w", conversationID, err)
}
@@ -1042,7 +1042,7 @@ func (s *chatServiceImpl) IsUserOnline(userID string) bool {
// 适用于群聊等由调用方自行负责推送的场景
func (s *chatServiceImpl) SaveMessage(ctx context.Context, senderID string, conversationID string, segments model.MessageSegments, replyToID *string) (*model.Message, error) {
// 验证会话是否存在
_, err := s.getConversation(ctx, conversationID)
conv, err := s.getConversation(ctx, conversationID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("会话不存在,请重新创建会话")
@@ -1075,7 +1075,7 @@ func (s *chatServiceImpl) SaveMessage(ctx context.Context, senderID string, conv
// 从 Redis 获取下一个 seqSaveMessage 方法)
if s.conversationCache != nil {
seq, err := s.conversationCache.GetNextSeq(ctx, conversationID)
seq, err := s.conversationCache.GetNextSeqWithGroup(ctx, conversationID, conv.Type == model.ConversationTypeGroup)
if err != nil {
return nil, fmt.Errorf("failed to get next seq for conversation %s: %w", conversationID, err)
}

View File

@@ -473,7 +473,7 @@ func (s *groupService) broadcastMemberJoinNotice(groupID string, targetUserID st
}
// 从 Redis 获取下一个 seq
if s.conversationCache != nil {
seq, seqErr := s.conversationCache.GetNextSeq(context.Background(), conv.ID)
seq, seqErr := s.conversationCache.GetNextSeqWithGroup(context.Background(), conv.ID, true)
if seqErr != nil {
zap.L().Warn("get next seq failed, skipping system message",
zap.String("convID", conv.ID),
@@ -1398,7 +1398,7 @@ func (s *groupService) MuteMember(userID string, groupID string, targetUserID st
// 从 Redis 获取下一个 seq
if s.conversationCache != nil {
seq, seqErr := s.conversationCache.GetNextSeq(context.Background(), conv.ID)
seq, seqErr := s.conversationCache.GetNextSeqWithGroup(context.Background(), conv.ID, true)
if seqErr != nil {
zap.L().Warn("get next seq failed, skipping system message",
zap.String("convID", conv.ID),

View File

@@ -90,7 +90,7 @@ func (s *MessageService) SendMessage(ctx context.Context, senderID, receiverID s
// 从 Redis 获取下一个 seq
if s.conversationCache != nil {
seq, seqErr := s.conversationCache.GetNextSeq(context.Background(), conv.ID)
seq, seqErr := s.conversationCache.GetNextSeqWithGroup(context.Background(), conv.ID, false)
if seqErr != nil {
return nil, fmt.Errorf("failed to get next seq for conversation %s: %w", conv.ID, seqErr)
}