Redigo和并发的一些问题

Some questions about Redigo and concurrency

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

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

Connections do not support concurrent calls to the write methods (Send, Flush) or concurrent calls to the read method (Receive). Connections do allow a concurrent reader and writer.

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

Because the Do method combines the functionality of Send, Flush and Receive, the Do method cannot be called concurrently with the other methods.

这是否意味着我们可以使用存储在全局变量中的单个连接单独并发使用 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 混淆是否更好?

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

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

用游泳池替换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() 会在应用程序中的其他代码使用池之前执行。

同时添加对 Subscribe or PSubscribe 的调用。