研究redis方法的时候看到有大佬讲说send()方法的性能数倍于Do()
不使用管道提交
rc := RedisClient.Get()
defer rc.Close()
currentTimeStart := time.Now()
for i:=0 ;i<100000;i++{
fmt.Println(i)
rc.Do("INCR","TESTKEY")
}
currentTimeEnd := time.Now()
fmt.Println(currentTimeStart)
fmt.Println(currentTimeEnd)
99999
2018-12-11 10:58:15.006840374 +0800 CST m=+3.051498224
2018-12-11 10:58:23.793490127 +0800 CST m=+11.837884394
func TestPipline() {
rc := RedisClient.Get()
defer rc.Close()
currentTimeStart := time.Now()
for i:=0 ;i<100000;i++{
fmt.Println(i)
rc.Send("INCR","TESTKEY")
}
rc.Flush()
rc.Receive()
currentTimeEnd := time.Now()
fmt.Println(currentTimeStart)
fmt.Println(currentTimeEnd)
}
99999
2018-12-11 11:03:09.416745233 +0800 CST m=+11.625497498
2018-12-11 11:03:09.962783858 +0800 CST m=+12.171518442
结论显而易见,“可以看到10w次,管道提交只要0.5s ,非管道提交长达8s ,”
打开redis验证一下结果:
send:
do:
看到问题了吗,send()没有写完,do()写完了十万次,什么原因?
先看下源码:
// Conn represents a connection to a Redis server.
type Conn interface {
// Close closes the connection.
Close() error
// Err returns a non-nil value when the connection is not usable.
Err() error
// Do sends a command to the server and returns the received reply.
Do(commandName string, args ...interface{}) (reply interface{}, err error)
// Send writes the command to the client's output buffer.
Send(commandName string, args ...interface{}) error
// Flush flushes the output buffer to the Redis server.
Flush() error
// Receive receives a single reply from the Redis server
Receive() (reply interface{}, err error)
}
区别就是send是将命令写到了缓冲区,do是直接提交给了redis server
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
}
再看下Flush方法的源码:
func (c *conn) Flush() error {
if c.writeTimeout != 0 {
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
}
if err := c.bw.Flush(); err != nil {
return c.fatal(err)
}
return nil
}
func (c *conn) Receive() (interface{}, error) {
return c.ReceiveWithTimeout(c.readTimeout)
}
Send()是写到buffer,没有其他返回值,调用的是writeCommand,writeCommand又断言类型分别调用了writestring或writeint等方法,最底层是bufio.Writer,熟悉bufio.Writer的同学是不是明白了点什么,没错这是buffer的defaultBufSize问题
原因分析
当使用buffer读写文件时,数据并没有直接被写入磁盘,而是被缓存到一个字节数据中,这个字节数组的大小是8kb,默认情况下只有当8kb被填充满了以后,数据才会被一次性写入磁盘,这样一来就大大减少了系统调用的次数(file是每一次write都会产生系统调用),当然也正是因为buffer中的每一次write只是写入到内存中(JVM自身内存中),所以当数据未写入磁盘前,如果JVM进程挂了,那么就会造成数据丢失。
手动刷盘
为了解决数据丢失的问题,buf中提供了flush()方法,用户可以自行决定合适将数据刷写到磁盘中
如果你的flush()调用的非常频繁,那就会退化为普通的file模式了。
如果你的flush()调用的又不太频繁,那么丢数据的可能性就比较高。
无论如何业务逻辑中数据写完时,一定要调用一次flush(),确保缓冲区的数据刷到磁盘上。
所以,问题的关键在于Flush,以及何时Flush,怎么用flush,那就需要在实际项目中取验证、调试、调优,研发的过程就是不断调优的过程。
将上面的代码改为:
func sendtest(){
fmt.Println("Send...")
rds := openRdb()
defer rds.Close()
currentTimeStart := time.Now()
for i:=0 ;i<100000;i++{
// fmt.Println(i)
err:=rds.Send("INCR","TESTKEY")
if err!=nil{
fmt.Println(err)
}
rds.Flush()
}
rds.Receive()
currentTimeEnd := time.Now()
fmt.Println(currentTimeStart)
fmt.Println(currentTimeEnd)
}
看结果:
Send...
2020-12-29 16:04:03.018509161 +0800 CST m=+0.062186221
2020-12-29 16:04:03.058721263 +0800 CST m=+0.102398202
还是比直接写入要快,具体如何使用,看到这里应该明白了吧!