1. 程式人生 > 其它 >golang redis的Do方法與send方法

golang redis的Do方法與send方法

技術標籤:golangredisgolang

研究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

還是比直接寫入要快,具體如何使用,看到這裡應該明白了吧!