You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中实现RabbitMQ消息转WebSocket客户端且避免协程阻塞

解决RabbitMQ消息转发到WebSocket客户端的goroutine通信问题

核心思路

用线程安全的连接管理器统一管理所有活跃的WebSocket连接,RabbitMQ消费者通过用户ID从管理器中获取对应连接并转发消息,避免直接传递连接引用导致的阻塞问题。

具体实现步骤

1. 实现线程安全的连接管理器

创建一个ConnectionManager结构体,用sync.RWMutex保护内部连接映射,提供注册、注销、根据用户ID获取连接的方法:

package service

import (
    "sync"
)

// ConnectionManager 管理所有活跃的WebSocket连接
type ConnectionManager struct {
    mu      sync.RWMutex
    clients map[string]*ClientService // key: 用户ID
}

func NewConnectionManager() *ConnectionManager {
    return &ConnectionManager{
        clients: make(map[string]*ClientService),
    }
}

// Register 注册用户的WebSocket连接
func (cm *ConnectionManager) Register(userID string, client *ClientService) {
    cm.mu.Lock()
    defer cm.mu.Unlock()
    cm.clients[userID] = client
}

// Unregister 注销用户的WebSocket连接
func (cm *ConnectionManager) Unregister(userID string) {
    cm.mu.Lock()
    defer cm.mu.Unlock()
    delete(cm.clients, userID)
}

// GetClient 根据用户ID获取对应的ClientService
func (cm *ConnectionManager) GetClient(userID string) (*ClientService, bool) {
    cm.mu.RLock()
    defer cm.mu.RUnlock()
    client, exists := cm.clients[userID]
    return client, exists
}

2. 修改主函数集成连接管理器

初始化连接管理器,在WebSocket连接验证通过后注册连接,断开时自动注销:

func main() {
    loadEnvVariables()

    wg := new(sync.WaitGroup)
    wg.Add(2)

    // 初始化连接管理器
    connManager := service.NewConnectionManager()

    go func() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            conn, err := upgrades.Upgrade(w, r, nil)
            if err != nil {
                w.WriteHeader(http.StatusInternalServerError)
                _, err := w.Write([]byte("failed upgrading connection"))
                if err != nil {
                    return
                }
                return
            }

            defer conn.Close()

            clientService := service.NewClientService(conn)
            done := make(chan struct{})
            clientID := uuid.New().String()
            var userID string

            if _, exists := r.Header["Authorization"]; exists {
                jwt := getJWT(r)
                grpc, err := grpcclient.GetGrpcClient(os.Getenv("AS"))
                if err != nil {
                    log.Fatalf("did not connect: %s", err)
                }
                defer grpc.Close()

                sessionClient := as.NewSessionServiceClient(grpc)
                authService := service.NewAuthService(sessionClient)
                res, err := authService.ValidateSession(jwt)
                if err != nil {
                    clientService.Send(fmt.Sprintf("Internal server error"))
                    return // 验证失败直接返回
                }

                userID = res.User.Id
                clientService.Send(fmt.Sprintf("Server: Welcome! Your ID is %s", clientID))
                clientService.SaveConnection(userID, clientID)

                // 注册连接到管理器
                connManager.Register(userID, clientService)
                // 连接断开时自动注销
                defer connManager.Unregister(userID)

                go clientService.WritePump(userID, done)
                clientService.ReadPump(userID, done)
                return // 验证成功跳过错误提示
            }
            clientService.Send(fmt.Sprintf("Authentication error"))
        })

        if err := http.ListenAndServe(":3071", nil); err != nil {
            panic(err)
        }
        wg.Done()
    }()

    go func() {
        rabbitService := service.NewRabbitService(&service.RabbitConfig{
            Schema:         model.SCHEMA,
            Username:       os.Getenv("RMQ_USERNAME"),
            Password:       os.Getenv("RMQ_PASSWORD"),
            Host:           os.Getenv("RMQ_HOST"),
            Port:           os.Getenv("RMQ_PORT"),
            VHost:          os.Getenv("RMQ_VHOST"),
            ConnectionName: model.CONNECTION_NAME,
            ExchangeName:   model.EXCHANGE_NAME,
            ExchangeType:   model.EXCHANGE_TOPIC,
        })
        if err := rabbitService.Connect(); err != nil {
            log.Fatalln("unable to connect to rabbit", err)
        }
        // 将连接管理器传给TestHandler
        testHandler := service.NewTestHandler(&service.TestHandlerConfig{
            RabbitService: rabbitService,
            ExchangeConfig: &service.ExchangeConfig{
                RoutingKey:   model.TEST_HANDLER_EVENT,
                QueueName:    model.QUE_NAME,
                ConsumerName: model.TEST_HANDLER_NAME,
            },
            ConnManager: connManager,
        })
        testHandler.Declare()
        testHandler.Handle()
        wg.Done()
    }()

    wg.Wait()
}

3. 修改TestHandler转发消息

更新TestHandler定义,加入连接管理器字段,消费RabbitMQ消息时根据用户ID获取连接并发送:

package service

import (
    "encoding/json"
    "log"
)

type TestHandlerConfig struct {
    RabbitService *RabbitService
    ExchangeConfig *ExchangeConfig
    ConnManager *ConnectionManager // 新增连接管理器字段
}

type TestHandler struct {
    rabbitService *RabbitService
    exchangeCfg *ExchangeConfig
    connManager *ConnectionManager
}

func NewTestHandler(cfg *TestHandlerConfig) *TestHandler {
    return &TestHandler{
        rabbitService: cfg.RabbitService,
        exchangeCfg: cfg.ExchangeConfig,
        connManager: cfg.ConnManager,
    }
}

// Handle 消费RabbitMQ消息并转发到WebSocket
func (th *TestHandler) Handle() {
    err := th.rabbitService.Consume(th.exchangeCfg.QueueName, th.exchangeCfg.ConsumerName, func(msg []byte) error {
        // 解析消息获取目标用户ID(根据实际消息格式调整)
        var msgData struct {
            UserID string `json:"user_id"`
            Content string `json:"content"`
        }
        if err := json.Unmarshal(msg, &msgData); err != nil {
            log.Printf("failed to parse message: %v", err)
            return nil
        }

        // 获取用户的WebSocket连接
        client, exists := th.connManager.GetClient(msgData.UserID)
        if !exists {
            log.Printf("no active connection for user %s", msgData.UserID)
            return nil
        }

        // 发送消息到客户端
        if err := client.Send(msgData.Content); err != nil {
            log.Printf("failed to send message to user %s: %v", msgData.UserID, err)
            th.connManager.Unregister(msgData.UserID)
        }
        return nil
    })

    if err != nil {
        log.Fatalln("failed to consume messages:", err)
    }
}

关键说明

  • 线程安全:用sync.RWMutex保护连接映射,支持多goroutine并发读写,避免数据竞争。
  • 连接生命周期管理:连接建立时注册,断开时自动注销,确保管理器只保留活跃连接。
  • 组件解耦:RabbitMQ消费者与WebSocket处理器通过连接管理器交互,无需直接传递连接引用,避免阻塞新连接处理。

内容的提问来源于stack exchange,提问作者Nikola Bozic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 22:40:03