feat(ws): implement WebSocket cluster mode with Redis Pub/Sub
Introduce a new WebSocket messaging architecture that supports both standalone and cluster modes. This allows for horizontal scaling of WebSocket servers by using Redis Pub/Sub to synchronize messages across multiple instances. Key changes: - Added `ws.MessagePublisher` interface to abstract message distribution. - Implemented `ws.Bus` to handle cluster-mode messaging via Redis. - Added `ws.OnlineTracker` to manage user online status across the cluster. - Refactored multiple services (Chat, Group, Push, Call, etc.) to use the new `MessagePublisher` instead of a concrete `ws.Hub`. - Added WebSocket configuration options (mode, instance ID, channel, TTL, heartbeat) to `config.yaml` and `config.go`. - Updated dependency injection with Wire to support the new publisher and Redis client. - Improved logging by replacing standard `log` with `zap` in several service components.
This commit is contained in:
@@ -63,9 +63,9 @@ func ProvideMessageHandler(
|
||||
messageService *service.MessageService,
|
||||
userService service.UserService,
|
||||
groupService service.GroupService,
|
||||
wsHub *ws.Hub,
|
||||
publisher ws.MessagePublisher,
|
||||
) *handler.MessageHandler {
|
||||
return handler.NewMessageHandler(chatService, messageService, userService, groupService, wsHub)
|
||||
return handler.NewMessageHandler(chatService, messageService, userService, groupService, publisher)
|
||||
}
|
||||
|
||||
// ProvideSystemMessageHandler 提供系统消息处理器
|
||||
@@ -93,14 +93,14 @@ func ProvideScheduleHandler(
|
||||
|
||||
// ProvideWSHandler 提供WebSocket处理器
|
||||
func ProvideWSHandler(
|
||||
wsHub *ws.Hub,
|
||||
publisher ws.MessagePublisher,
|
||||
chatService service.ChatService,
|
||||
groupService service.GroupService,
|
||||
jwtService *service.JWTService,
|
||||
callService service.CallService,
|
||||
userRepo repository.UserRepository,
|
||||
) *handler.WSHandler {
|
||||
return handler.NewWSHandler(wsHub, chatService, groupService, jwtService, callService, userRepo)
|
||||
return handler.NewWSHandler(publisher, chatService, groupService, jwtService, callService, userRepo)
|
||||
}
|
||||
|
||||
// ProvideAdminVerificationHandler 提供管理端身份认证处理器
|
||||
|
||||
@@ -46,8 +46,8 @@ var InfrastructureSet = wire.NewSet(
|
||||
ProvideBuiltinHooks,
|
||||
ProvideModerationHooks,
|
||||
|
||||
// WebSocket Hub
|
||||
ProvideWSHub,
|
||||
// WebSocket MessagePublisher
|
||||
ProvideWSMessagePublisher,
|
||||
|
||||
// 外部服务客户端
|
||||
ProvideOpenAIClient,
|
||||
@@ -163,9 +163,32 @@ func ProvideModerationHooks(
|
||||
return moderationHooks
|
||||
}
|
||||
|
||||
// ProvideWSHub 提供 WebSocket Hub
|
||||
func ProvideWSHub() *ws.Hub {
|
||||
return ws.NewHub()
|
||||
// ProvideWSMessagePublisher 提供 WebSocket 消息发布器
|
||||
// standalone 模式返回 *Hub,cluster 模式返回 *Bus(组合 Hub + Redis Pub/Sub)
|
||||
func ProvideWSMessagePublisher(cfg *config.Config, redisClient *redis.Client) ws.MessagePublisher {
|
||||
hub := ws.NewHub()
|
||||
|
||||
if cfg.WebSocket.Mode == "cluster" && redisClient != nil {
|
||||
bus := ws.NewBus(hub, redisClient.GetClient(), &ws.WSClusterConfig{
|
||||
InstanceID: cfg.WebSocket.Cluster.InstanceID,
|
||||
MsgChannel: cfg.WebSocket.Cluster.MsgChannel,
|
||||
OnlineTTL: cfg.WebSocket.Cluster.OnlineTTL,
|
||||
HeartbeatInterval: cfg.WebSocket.Cluster.HeartbeatInterval,
|
||||
})
|
||||
if err := bus.Start(); err != nil {
|
||||
zap.L().Error("Failed to start WebSocket Bus, falling back to standalone mode",
|
||||
zap.Error(err),
|
||||
)
|
||||
return hub
|
||||
}
|
||||
zap.L().Info("WebSocket running in cluster mode",
|
||||
zap.String("instance_id", bus.InstanceID()),
|
||||
)
|
||||
return bus
|
||||
}
|
||||
|
||||
zap.L().Info("WebSocket running in standalone mode")
|
||||
return hub
|
||||
}
|
||||
|
||||
// ProvideOpenAIClient 提供 OpenAI 客户端
|
||||
|
||||
@@ -105,10 +105,10 @@ func ProvidePushService(
|
||||
pushRepo repository.PushRecordRepository,
|
||||
deviceTokenRepo repository.DeviceTokenRepository,
|
||||
messageRepo repository.MessageRepository,
|
||||
wsHub *ws.Hub,
|
||||
publisher ws.MessagePublisher,
|
||||
jpushClient *jpush.Client,
|
||||
) service.PushService {
|
||||
return service.NewPushService(pushRepo, deviceTokenRepo, messageRepo, wsHub, jpushClient)
|
||||
return service.NewPushService(pushRepo, deviceTokenRepo, messageRepo, publisher, jpushClient)
|
||||
}
|
||||
|
||||
// ProvideSystemMessageService 提供系统消息服务
|
||||
@@ -205,12 +205,12 @@ func ProvideChannelService(channelRepo repository.ChannelRepository, cacheBacken
|
||||
func ProvideChatService(
|
||||
messageRepo repository.MessageRepository,
|
||||
userRepo repository.UserRepository,
|
||||
wsHub *ws.Hub,
|
||||
publisher ws.MessagePublisher,
|
||||
cacheBackend cache.Cache,
|
||||
uploadService *service.UploadService,
|
||||
pushService service.PushService,
|
||||
) service.ChatService {
|
||||
return service.NewChatService(messageRepo, userRepo, nil, wsHub, cacheBackend, uploadService, pushService)
|
||||
return service.NewChatService(messageRepo, userRepo, nil, publisher, cacheBackend, uploadService, pushService)
|
||||
}
|
||||
|
||||
// ProvideScheduleService 提供日程服务
|
||||
@@ -236,10 +236,10 @@ func ProvideGroupService(
|
||||
messageRepo repository.MessageRepository,
|
||||
requestRepo repository.GroupJoinRequestRepository,
|
||||
notifyRepo repository.SystemNotificationRepository,
|
||||
wsHub *ws.Hub,
|
||||
publisher ws.MessagePublisher,
|
||||
cacheBackend cache.Cache,
|
||||
) service.GroupService {
|
||||
return service.NewGroupService(groupRepo, userRepo, messageRepo, requestRepo, notifyRepo, wsHub, cacheBackend)
|
||||
return service.NewGroupService(groupRepo, userRepo, messageRepo, requestRepo, notifyRepo, publisher, cacheBackend)
|
||||
}
|
||||
|
||||
// ProvideUploadService 提供上传服务
|
||||
@@ -382,13 +382,13 @@ func ProvideHotRankWorker(cfg *config.Config, postRepo repository.PostRepository
|
||||
|
||||
func ProvideQRCodeLoginService(
|
||||
redisClient *redis.Client,
|
||||
wsHub *ws.Hub,
|
||||
publisher ws.MessagePublisher,
|
||||
jwtService *service.JWTService,
|
||||
userService service.UserService,
|
||||
activityService service.UserActivityService,
|
||||
logService *service.LogService,
|
||||
) *service.QRCodeLoginService {
|
||||
return service.NewQRCodeLoginService(redisClient, wsHub, jwtService, userService, activityService, logService)
|
||||
return service.NewQRCodeLoginService(redisClient, publisher, jwtService, userService, activityService, logService)
|
||||
}
|
||||
|
||||
// ProvideMaterialService 提供学习资料服务
|
||||
@@ -407,11 +407,12 @@ func parseDuration(s string) (time.Duration, error) {
|
||||
// ProvideCallService 提供通话服务
|
||||
func ProvideCallService(
|
||||
callRepo repository.CallRepository,
|
||||
wsHub *ws.Hub,
|
||||
publisher ws.MessagePublisher,
|
||||
cfg *config.Config,
|
||||
db *gorm.DB,
|
||||
redisClient *redis.Client,
|
||||
) service.CallService {
|
||||
return service.NewCallService(callRepo, wsHub, cfg, db)
|
||||
return service.NewCallService(callRepo, publisher, cfg, db, redisClient)
|
||||
}
|
||||
|
||||
// ProvideReportService 提供举报服务
|
||||
|
||||
Reference in New Issue
Block a user