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:
@@ -61,6 +61,7 @@ type GroupService interface {
|
||||
TransferOwner(userID string, groupID string, newOwnerID string) error
|
||||
GetUserGroups(userID string, page, pageSize int) ([]model.Group, int64, error)
|
||||
GetMemberCount(groupID string) (int, error)
|
||||
GetMemberCountBatch(ctx context.Context, groupIDs []string) (map[string]int64, error)
|
||||
|
||||
// 成员管理
|
||||
InviteMembers(userID string, groupID string, memberIDs []string) error
|
||||
@@ -106,25 +107,36 @@ type GroupMembersResult struct {
|
||||
|
||||
// groupService 群组服务实现
|
||||
type groupService struct {
|
||||
groupRepo repository.GroupRepository
|
||||
userRepo repository.UserRepository
|
||||
messageRepo repository.MessageRepository
|
||||
requestRepo repository.GroupJoinRequestRepository
|
||||
notifyRepo repository.SystemNotificationRepository
|
||||
wsHub *ws.Hub
|
||||
cache cache.Cache
|
||||
groupRepo repository.GroupRepository
|
||||
userRepo repository.UserRepository
|
||||
messageRepo repository.MessageRepository
|
||||
requestRepo repository.GroupJoinRequestRepository
|
||||
notifyRepo repository.SystemNotificationRepository
|
||||
wsHub *ws.Hub
|
||||
cache cache.Cache
|
||||
conversationCache *cache.ConversationCache
|
||||
}
|
||||
|
||||
// NewGroupService 创建群组服务
|
||||
func NewGroupService(groupRepo repository.GroupRepository, userRepo repository.UserRepository, messageRepo repository.MessageRepository, requestRepo repository.GroupJoinRequestRepository, notifyRepo repository.SystemNotificationRepository, wsHub *ws.Hub, cacheBackend cache.Cache) GroupService {
|
||||
convRepoAdapter := cache.NewConversationRepositoryAdapter(messageRepo)
|
||||
msgRepoAdapter := cache.NewMessageRepositoryAdapter(messageRepo)
|
||||
conversationCache := cache.NewConversationCache(
|
||||
cacheBackend,
|
||||
convRepoAdapter,
|
||||
msgRepoAdapter,
|
||||
cache.DefaultConversationCacheSettings(),
|
||||
)
|
||||
|
||||
return &groupService{
|
||||
groupRepo: groupRepo,
|
||||
userRepo: userRepo,
|
||||
messageRepo: messageRepo,
|
||||
requestRepo: requestRepo,
|
||||
notifyRepo: notifyRepo,
|
||||
wsHub: wsHub,
|
||||
cache: cacheBackend,
|
||||
groupRepo: groupRepo,
|
||||
userRepo: userRepo,
|
||||
messageRepo: messageRepo,
|
||||
requestRepo: requestRepo,
|
||||
notifyRepo: notifyRepo,
|
||||
wsHub: wsHub,
|
||||
cache: cacheBackend,
|
||||
conversationCache: conversationCache,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +311,11 @@ func (s *groupService) GetMemberCount(groupID string) (int, error) {
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
// GetMemberCountBatch 批量获取多个群的成员数量
|
||||
func (s *groupService) GetMemberCountBatch(ctx context.Context, groupIDs []string) (map[string]int64, error) {
|
||||
return s.groupRepo.GetMemberCountBatch(ctx, groupIDs)
|
||||
}
|
||||
|
||||
// UpdateGroup 更新群组信息
|
||||
func (s *groupService) UpdateGroup(userID string, groupID string, updates map[string]any) error {
|
||||
// 检查群组是否存在
|
||||
@@ -453,6 +470,18 @@ func (s *groupService) broadcastMemberJoinNotice(groupID string, targetUserID st
|
||||
Status: model.MessageStatusNormal,
|
||||
Category: model.CategoryNotification,
|
||||
}
|
||||
// 从 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
|
||||
}
|
||||
}
|
||||
if err := s.messageRepo.CreateMessageWithSeq(msg); err != nil {
|
||||
zap.L().Warn("保存入群提示消息失败",
|
||||
zap.String("component", "broadcastMemberJoinNotice"),
|
||||
@@ -1366,7 +1395,19 @@ func (s *groupService) MuteMember(userID string, groupID string, targetUserID st
|
||||
Category: model.CategoryNotification,
|
||||
}
|
||||
|
||||
// 保存消息并获取 seq
|
||||
// 从 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
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.messageRepo.CreateMessageWithSeq(msg); err != nil {
|
||||
zap.L().Warn("保存禁言消息失败",
|
||||
zap.String("component", "MuteMember"),
|
||||
|
||||
Reference in New Issue
Block a user