golang redis的Do方法與send方法
研究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
}
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
將上面的程式碼改為:
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
還是比直接寫入要快,具體如何使用,看到這裡應該明白了吧!