redis性能调优一则

来源:互联网 发布:全国68所网络教育学校 编辑:程序博客网 时间:2024/05/16 10:49

redis作为提升web服务端数据交互能力的重要利器,其本身也有开销,为了让redis变得更快,有必要对和redis交互的地方进行性能优化。

今天说一下golang中比较著名的一个redis库—-redigo。它的conn.Do()、Send()、Flush()、Receive()的合理使用是很有必要的。

先上一个我本地测试的例子:

func main(){    _=InitRedis(10,"127.0.0.1","6379","requirepass",false) //初始化redis,这里就不细写了    GetRedisKey()    GetRedisKey2()}func GetRedisKey()  {    now:=time.Now()    conn := redisPool.Get()    defer conn.Close()    for i:=0;i<1000;i++{    //做1000次get        key :=  "1125"+"test"+strconv.Itoa(i)        //_, err := conn.Do("set", key,"testValue") 这个是之前set        _, err := redis.String(conn.Do("get", key)) //执行get,并获取结果        if err!=nil {            fmt.Println(err)        }        //fmt.Println(result)    }    finish1:=time.Since(now) //计时    fmt.Println(finish1)}func GetRedisKey2()  {    now:=time.Now()    conn := redisPool.Get()    defer conn.Close()    var count int    for i:=0;i<1000;i++{    //做1000次get        key :=  "1125"+"test2_"+strconv.Itoa(i)        //err := conn.Send("set", key,"testValue")之前set        err := conn.Send("get",key)  //注意这里是send,不是Do了        if err!=nil {            fmt.Println(err)        }        count++    }    err := conn.Flush()   //发送指令    if err != nil {        fmt.Println(err)    }    for i:=0 ; i<count; i++ {        _, err := redis.String(conn.Receive()) //获取get结果        if err != nil {            fmt.Println(err)        }    }    finish2:=time.Since(now)  //计时    fmt.Println(finish2)}

实验结果:

80.0561ms   //GetRedisKey()运行耗时4.0033ms    //GetRedisKey2()运行耗时

结果很明显,同样是做了1000次查询,第二个方法比第一个方法快了20倍。这是为什么呢?接下来说明其中原理。

这个时候要看一看redigo的源码了,先看这个conn的结构:

type conn struct {    // Shared    mu      sync.Mutex    pending int    err     error    conn    net.Conn    // Read    readTimeout time.Duration    br          *bufio.Reader    // Write    writeTimeout time.Duration    bw           *bufio.Writer    // Scratch space for formatting argument length.    // '*' or '$', length, "\r\n"    lenScratch [32]byte    // Scratch space for formatting integers and floats.    numScratch [40]byte}

这个是redis连接的结构,我们发现有两个成员,分别是*bufio.Reader和*bufio.Writer。然后再来看这个Do():

func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {    c.mu.Lock()    pending := c.pending    c.pending = 0    c.mu.Unlock()    if cmd == "" && pending == 0 {        return nil, nil    }    if c.writeTimeout != 0 {        c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))    }    if cmd != "" {        if err := c.writeCommand(cmd, args); err != nil {  //将指令写入到一个地方去            return nil, c.fatal(err)        }    }    if err := c.bw.Flush(); err != nil {   //将缓冲取出,放到io.Writer中,看到这句代码,我们就应该知道,上面那条c.writeCommand()应该是把指令放到缓冲里了。        return nil, c.fatal(err)    }    if c.readTimeout != 0 {        c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))    }    if cmd == "" {        reply := make([]interface{}, pending)        for i := range reply {            r, e := c.readReply()              if e != nil {                return nil, c.fatal(e)            }            reply[i] = r        }        return reply, nil    }    var err error    var reply interface{}    for i := 0; i <= pending; i++ {        var e error        if reply, e = c.readReply(); e != nil {   //读redis server返回的数据            return nil, c.fatal(e)        }        if e, ok := reply.(Error); ok && err == nil {            err = e        }    }    return reply, err}

为了一探究竟,看了c.writeCommand()的实现:

func (c *conn) writeCommand(cmd string, args []interface{}) error {    c.writeLen('*', 1+len(args))    if err := c.writeString(cmd); err != nil {  //再进一步看这个方法,见下面那段        return err    }    for _, arg := range args {        if err := c.writeArg(arg, true); err != nil {            return err        }    }    return nil}
func (c *conn) writeString(s string) error {    c.writeLen('$', len(s))    c.bw.WriteString(s)     //果然调用了bufio的WriteString()方法,把指令都写到了缓冲中    _, err := c.bw.WriteString("\r\n")    return err}

到这里,我们知道,Do()这个方法基本上是包办了Send(),Flush(),Receive(),那为什么第二个测试函数会比Do()快这么多呢?原因就是,我for循环执行了多次Send(),目的就是把多条要执行的指令写到缓冲中。

func (c *conn) Send(cmd string, args ...interface{}) error {    c.mu.Lock()    c.pending += 1    c.mu.Unlock()    if c.writeTimeout != 0 {        c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))    }    if err := c.writeCommand(cmd, args); err != nil {        return c.fatal(err)    }    return nil}

这是Send()的源码,其实和Do()一开始做的事情一样,都是c.writeCommand(cmd, args)。

区别就在于,我多次执行Send(),是多条指令写到缓冲中,而不是像Do()那样,不断的执行send,flush,recv。写到缓冲之后,我再统一Flush(),把指令全写到网络io中。因为redis server支持pipelining,我再从io中一个一个Receive出来即可。这样看,1000条指令,我只进行了一次网络传输。而用Do,则执行了1000次网络传输,这差距就显而易见了。

原创粉丝点击