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
}

再看下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

还是比直接写入要快,具体如何使用,看到这里应该明白了吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值