关闭redis订阅并在websocket连接关闭时结束go例程

关闭redis订阅并在websocket连接关闭时结束go例程,go,redis,Go,Redis,我正在将事件从redis订阅推送到通过websocket连接的客户端。当客户端断开websocket连接时,我无法取消订阅和退出redis go例程 受此启发,以下是我迄今为止所做的。我能够通过websocket接收订阅事件并向客户端发送消息,但是当客户端关闭websocket并且延迟关闭(完成)代码触发时,我的案例b,确定:=在完成websocket连接服务后,进行这些更改以中断读取循环: 维护为此websocket连接创建的Redis连接的一部分 完成后取消订阅所有连接 修改读取循环以在订

我正在将事件从redis订阅推送到通过websocket连接的客户端。当客户端断开websocket连接时,我无法取消订阅和退出redis go例程


受此启发,以下是我迄今为止所做的。我能够通过websocket接收订阅事件并向客户端发送消息,但是当客户端关闭websocket并且
延迟关闭(完成)
代码触发时,我的
案例b,确定:=在完成websocket连接服务后,进行这些更改以中断读取循环:

  • 维护为此websocket连接创建的Redis连接的一部分

  • 完成后取消订阅所有连接

  • 修改读取循环以在订阅计数为零时返回

代码如下:

func wsHandler(w http.ResponseWriter, r *http.Request) {
    var upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
    }

    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        HandleError(w, err)
        return
    }
    defer conn.Close()

    // Keep slice of all connections. Unsubscribe all connections on exit.
    var pscs []redis.PubSubConn
    defer func() {
        for _, psc := range rcs {
           psc.Unsubscribe() // unsubscribe with no args unsubs all channels
        }
    }()

    for {
        var req WSRequest
        err := conn.ReadJSON(&req)
        if err != nil {
            HandleWSError(conn, err)
            return
        }

        rc := redisPool.Get()
        psc := redis.PubSubConn{Conn: rc}
        pscs = append(pscs, psc)

        if err := psc.PSubscribe(req.chanName); err != nil {
            HandleWSError(conn, err)
            return
        }

        go func(req *WSRequest, conn *websocket.Conn) {
            defer rc.Close()
            for {
                switch v := psc.Receive().(type) {
                case redis.PMessage:
                    err := handler(conn, req, v)
                    if err != nil {
                        HandleWSError(conn, err)
                    }

                case redis.Subscription:
                     log.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
                     if v.Count == 0 {
                         return
                     }

                case error:
                     log.Printf("error in redis subscription; err:\n%v\n", v)
                     HandleWSError(conn, v)

                default:
                    // do nothing...
                    log.Printf("unknown redis subscription event type; %s\n", reflect.TypeOf(v))
                }
            }
        }(&req, conn)
    }
}

问题和答案中的代码为每个websocket客户端拨打多个Redis连接。一种更典型且可扩展的方法是跨多个客户端共享单个Redis pubsub连接。鉴于高级描述,典型的方法可能适合您的应用程序,但鉴于问题中的代码,我仍然不确定您在尝试做什么。

为什么应用程序会获得新连接并订阅收到的每条消息?可能我误解了redis.PubSubcon
的工作原理。每个客户端都希望订阅一个唯一的频道。我简化了我的实际代码,每个websocket客户端都会传递一个频道名称字符串。请给出应用程序正在执行的操作的高级描述。描述发送到websocket客户端和从websocket客户端发送的消息,以及这些消息与pubsub消息和频道名称的关系。每当某个表发生更改时,my postgres db中的触发器都会发送通知。go中的侦听器接收该表更改事件并通过redis发布它。通道名称的结构使感兴趣的客户端可以稍后对其进行模式匹配。客户机通过websocket连接到服务器,并发送一个负载,描述他们感兴趣的表更改事件的类型。他们订阅的频道是基于此有效负载生成的。基于有效负载,客户端可以订阅无限多个可能的频道名称。谢谢!只要阅读代码,它肯定会完成取消订阅。我试试看。“一种更典型且可扩展的方法是跨多个客户端共享一个Redis pubsub连接。”是否可以跨多个websocket连接共享一个pubsub连接,但随后让每个websocket订阅一个唯一的通道并仅从该通道接收消息?我不希望websocket从它不感兴趣的频道接收消息。您需要编写几行代码来解复用pubsub消息以共享单个redis连接。正确方向上的一小步是每个客户端使用一个Redis连接。这可能是一个简单的更改,但我不确定,因为我不明白您要做什么(高层描述和代码之间有很大的差距)。如果客户端只能订阅一个redis频道,那么更改很简单。“您需要编写几行代码来解复用pubsub消息以共享一个redis连接。”想象一下,如果我每秒有10k websocket连接和1k表更改事件。然后我需要每秒进行10毫米的消息解复用。我可以在没有redis的情况下对每个websocket客户端的消息进行多路分解,只需使用postgres notify/listen即可。我选择添加redis是为了将多路复用的负担从我的服务器转移到redis上。将数据发送到Redis并返回不会减轻前端服务器的工作量,因为与发送或接收Redis消息的成本相比,对事件进行解组(基本上是一个地图查找)的成本很小。
func wsHandler(w http.ResponseWriter, r *http.Request) {
    var upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
    }

    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        HandleError(w, err)
        return
    }
    defer conn.Close()

    // Keep slice of all connections. Unsubscribe all connections on exit.
    var pscs []redis.PubSubConn
    defer func() {
        for _, psc := range rcs {
           psc.Unsubscribe() // unsubscribe with no args unsubs all channels
        }
    }()

    for {
        var req WSRequest
        err := conn.ReadJSON(&req)
        if err != nil {
            HandleWSError(conn, err)
            return
        }

        rc := redisPool.Get()
        psc := redis.PubSubConn{Conn: rc}
        pscs = append(pscs, psc)

        if err := psc.PSubscribe(req.chanName); err != nil {
            HandleWSError(conn, err)
            return
        }

        go func(req *WSRequest, conn *websocket.Conn) {
            defer rc.Close()
            for {
                switch v := psc.Receive().(type) {
                case redis.PMessage:
                    err := handler(conn, req, v)
                    if err != nil {
                        HandleWSError(conn, err)
                    }

                case redis.Subscription:
                     log.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
                     if v.Count == 0 {
                         return
                     }

                case error:
                     log.Printf("error in redis subscription; err:\n%v\n", v)
                     HandleWSError(conn, v)

                default:
                    // do nothing...
                    log.Printf("unknown redis subscription event type; %s\n", reflect.TypeOf(v))
                }
            }
        }(&req, conn)
    }
}