【问题标题】:Some questions about Redigo and concurrency关于 Redigo 和并发的一些问题
【发布时间】:2016-09-07 19:33:03
【问题描述】:

我已通读完整的 Redigo 文档,可在此处找到。 https://godoc.org/github.com/garyburd/redigo/redis#pkg-variables

这里的文档明确指出连接不支持并发调用 Send()、Flush() 或 Receive() 方法。

连接不支持并发调用写入方法 (发送、刷新)或并发调用读取方法(接收)。 连接确实允许并发读取器和写入器。

然后它指出,由于 Do 方法可以是 Send()、Flush() 和 Receive() 的组合,所以我们不能同时使用 Do()(与)其他方法。

因为 Do 方法结合了 Send、Flush 和 接收,Do方法不能与其他同时调用 方法。

这是否意味着我们可以使用存储在全局变量中的单个连接同时单独使用 Do(),只要我们不将它与其他方法混合使用?

例如这样:

var (

    // Redis Conn.
    redisConn redis.Conn

    // Redis PubSubConn wraps a Conn with convenience methods for subscribers.
    redisPsc redis.PubSubConn
)

func redisInit() {

    c, err := redis.Dial(config.RedisProtocol, config.RedisAddress)
    if err != nil {
        log.Fatal(err)
    }
    c.Do("AUTH", config.RedisPass)
    redisConn = c

    c, err = redis.Dial(config.RedisProtocol, config.RedisAddress)
    if err != nil {
        log.Fatal(err)
    }
    c.Do("AUTH", config.RedisPass)
    redisPsc = redis.PubSubConn{c}

    for {
        switch v := redisPsc.Receive().(type) {
        case redis.Message:
            // fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
            socketHub.broadcast <- v.Data
        case redis.Subscription:
            // fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
        case error:
            log.Println(v)
        }
    }

}

然后在一些 go 例程中调用 Do() 方法,如下所示:

if _, err = redisConn.Do("PUBLISH", fmt.Sprintf("user:%d", fromId), message); err != nil {
    log.Println(err)
}
if _, err = redisConn.Do("PUBLISH", fmt.Sprintf("user:%d", toId), message); err != nil {
    log.Println(err)
}

然后文档说,为了对 Redis 进行完全并发访问,我们需要创建一个池并从池中获取连接,并在完成后释放它们。

这是否意味着只要我从池中获得连接,我就可以随意使用 Send()、Flush() 和 Receive()?所以换句话说,每次我需要在 go 例程中做某事时,我都必须从池中获取新连接,而不是重用全局连接?这是否意味着只要我从池中获得新连接,我就可以将 Do() 方法与例如 Send() 一起使用?

总结一下:

1) 我可以同时使用 Do() 方法,只要我不将它与 Send、Flush 和 Receive 方法一起使用吗?

2) 只要​​我从池中获得新连接并在完成后释放它,我就可以随意使用所有内容吗?

3) 如果 (1) 为真,这会影响性能吗?在我提供的示例中,仅使用 Do() 方法同时使用全局连接,而不是与 Send、Flush 和 Receive 混合使用会更好吗?

【问题讨论】:

    标签: go redigo


    【解决方案1】:

    您可以有一个并发写入器和一个并发读取器。因为Do 结合了读取和写入操作,您可以对Do 进行一次当前调用。换句话说,您不能同时调用Do。您不能将连接存储在全局变量中并调用Do,而不使用互斥锁保护连接或使用其他机制确保Do 的并发调用者不超过一个。

    池支持并发访问。池Get 方法返回的连接遵循上述并发规则。要获得对数据库的完全并发访问,应用程序应该在单个 goroutine 中执行以下操作:Get 来自池的连接;在连接上执行 Redis 命令; Close 将底层资源返回池的连接。

    redisConn redis.Conn 替换为池。在应用启动时初始化池:

     var redisPool *redis.Pool
    
     ...
    
    redisPool = &redis.Pool{
        MaxIdle: 3,  // adjust to your needs
        IdleTimeout: 240 * time.Second,  // adjust to your needs
        Dial: func () (redis.Conn, error) {
            c, err := redis.Dial(config.RedisProtocol, config.RedisAddress)
            if err != nil {
                return nil, err
            }
            if _, err := c.Do("AUTH", config.RedisPass); err != nil {
                c.Close()
                return nil, err
            }
            return c, err
        },
    }
    

    使用池发布到频道:

     c := redisPool.Get()
     if _, err = c.Do("PUBLISH", fmt.Sprintf("user:%d", fromId), message); err != nil {
        log.Println(err)
     }
     if _, err = c.Do("PUBLISH", fmt.Sprintf("user:%d", toId), message); err != nil {
        log.Println(err)
     }
     c.Close()
    

    不要初始化redisInit() 中的池。不能保证redisInit() 会在应用程序中的其他代码使用池之前执行。

    同时添加对SubscribePSubscribe 的调用。

    【讨论】:

    • 我像“go redisInit()”一样同时运行 redisInit(),我有两个连接。一个 redisConn redis.Conn 来做发布命令一个 redisPsc redis.PubSubConn 来订阅和取消订阅。
    • 我目前没有使用游泳池。但如果我理解正确,我不能使用 Do() 来发布内容,因为当我发布内容时,我也会同时收到相同的命令?
    • 如果应用程序从单个 goroutine 发布,那么问题中的代码可以按原样使用。 PUBLISH 命令的工作方式与其他常规 Redis 命令类似:应用程序发送命令,服务器发送响应。 Do 发送命令并接收响应。如果应用程序从多个 goroutine 发布,则使用互斥体保护 redisConn 或使用我的回答中描述的池。
    • 该应用程序使用“websocket readpump”发布,因此每次 websocket 消息到达时都会发生。
    • 你应该使用一个池。 pool documentation 描述了如何使用带有 HTTP 请求处理程序的池(您的 websocket 阅读器开始作为)。
    猜你喜欢
    • 1970-01-01
    • 2011-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多