关于Redigo和并发的一些问题

时间:2016-05-12 06:29:35

标签: go redigo

我已经阅读了整个Redigo文档,可以在这里找到。 https://godoc.org/github.com/garyburd/redigo/redis#pkg-variables

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

  

Connections不支持对write方法的并发调用   (Send,Flush)或并发调用read方法(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混合使用?

1 个答案:

答案 0 :(得分:3)

您可以拥有一个并发编写器和一个并发阅读器。由于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的调用。