feat(core): optimize performance and reliability through batching and redis-backed unread counts
This commit introduces several significant architectural improvements to enhance system performance, scalability, and reliability:
- **Performance Optimization (N+1 Problem Resolution)**: Implemented batching mechanisms across `MessageHandler`, `ChatService`, `GroupService`, `MessageService`, and `UserService`. This replaces multiple individual database/cache queries with single batch operations for fetching unread counts, last messages, participants, and user information.
- **Enhanced Unread Count Management**: Migrated unread count tracking to a Redis Hash-based approach (`unread#️⃣{userID}`). This allows for atomic increments/decrements and efficient retrieval of both individual conversation unread counts and total unread counts for a user.
- **Improved Message Sequencing**: Integrated Redis-based sequence (`seq`) pre-allocation for messages to ensure strict ordering and reduce database contention during high-concurrency message creation.
- **Push Service Reliability**: Refactored the push notification system to include persistent `PushRecord` tracking. Added a recovery mechanism (`recoverPendingPushes`) to reload pending notifications from the database upon service startup, ensuring better delivery guarantees.
- **WebSocket Reliability**: Updated the WebSocket hub to move away from in-memory history replay in favor of a client-driven synchronization model (`sync_required` event and `ack` handling), reducing memory overhead and improving connection stability.
- **Cache Layer Enhancements**: Added `HIncrBy` and `IncrBySeq` to the `Cache` interface and its implementations (`RedisCache`, `LayeredCache`) to support the new unread and sequence management logic.
This commit is contained in:
@@ -86,6 +86,19 @@ func (s *MessageService) SendMessage(ctx context.Context, senderID, receiverID s
|
||||
Status: model.MessageStatusNormal,
|
||||
}
|
||||
|
||||
// 从 Redis 获取下一个 seq
|
||||
if s.conversationCache != nil {
|
||||
seq, seqErr := s.conversationCache.GetNextSeq(context.Background(), conv.ID)
|
||||
if seqErr != nil {
|
||||
zap.L().Warn("redis get next seq failed, falling back to DB",
|
||||
zap.String("convID", conv.ID),
|
||||
zap.Error(seqErr),
|
||||
)
|
||||
} else {
|
||||
msg.Seq = seq
|
||||
}
|
||||
}
|
||||
|
||||
// 使用事务创建消息并更新seq
|
||||
err = s.messageRepo.CreateMessageWithSeq(msg)
|
||||
if err != nil {
|
||||
@@ -284,6 +297,16 @@ func (s *MessageService) GetConversationParticipants(conversationID string) ([]*
|
||||
return s.messageRepo.GetConversationParticipants(conversationID)
|
||||
}
|
||||
|
||||
// GetParticipantsBatch 批量获取多个会话的参与者列表
|
||||
func (s *MessageService) GetParticipantsBatch(ctx context.Context, convIDs []string) (map[string][]*model.ConversationParticipant, error) {
|
||||
return s.messageRepo.GetParticipantsBatch(ctx, convIDs)
|
||||
}
|
||||
|
||||
// GetMyParticipantsBatch 批量获取用户在多个会话中的参与者信息
|
||||
func (s *MessageService) GetMyParticipantsBatch(ctx context.Context, convIDs []string, userID string) (map[string]*model.ConversationParticipant, error) {
|
||||
return s.messageRepo.GetMyParticipantsBatch(ctx, convIDs, userID)
|
||||
}
|
||||
|
||||
// ParseConversationID 辅助函数:直接返回字符串ID(已经是string类型)
|
||||
func ParseConversationID(idStr string) (string, error) {
|
||||
return idStr, nil
|
||||
|
||||
Reference in New Issue
Block a user