一次网络IO优化的讨论

看项目的tcp通信模块时跟同事的偶然讨论,意外拉出来好几样东西,体验非常棒,记录下来~( ̄0  ̄)y

周梦飞 12:08:27我们的tcp网络直接用的conn,之前在看别人项目里见到有用bufio.Reader优化的

张明 12:13:45io.readatleast 不是也是使用一个分配好的缓冲区吗。
周梦飞 12:14:35不是的,用的传进去的那个
周梦飞 12:15:28这里的r.Read是原始的conn.Read
周梦飞 12:15:59bufio.NewReader(conn)可以用bufio包上一层
周梦飞 12:16:31代替conn传进io.readatleast
张明 12:17:14能看到源吗,这多包了一层,这个读和次数就减少了吗,什么原理。
周梦飞 12:17:33稍等,我翻下那篇文章
周梦飞 12:19:55[https://zhuanlan.zhihu.com/p/21369473](https://zhuanlan.zhihu.com/p/21369473)



func ReadFull(r Reader, buf []byte) (n int, err error) {

return ReadAtLeast(r, buf, len(buf))

}
周梦飞 12:20:06他用的io.ReadFull
周梦飞 12:20:11跟我们实际是一样的
周梦飞 12:22:24作者解释的说,bufio帮我们内置一个buf,io发生时数据先写进那个buf里了,我们去取时,先取buf里的,要是不够才会调底层io.Read,以此减少io调用次数
张明 12:25:49我明白这个意思, 换句话说,就应该像C++的收包过程一样,一性有多少字节,全部收下来,再去解析数据,用逻辑去分包。而不是用读操作去分包。

周梦飞 12:26:45嗯,是这意思,有io就缓存,逻辑去取缓存的数据张明 12:27:56是的,我一开始是打算这样做的,当时还有内存池的一些东西也没有想完善,就用了这种最简 的做法。张明 12:28:51是的,现在一个包的内容,还要分三次读。周梦飞 12:28:56代码用法也很简单,用bufio包装conn,换下就OK了张明 12:29:03理论上是多个包的内容,尽可能一次读。周梦飞 12:29:11周梦飞 12:29:40下午改着跑跑试下张明 12:29:43C++的处理粘包也有点麻烦,这个bufio这么好用吗,张明 12:29:49我有时间也看看。周梦飞 12:30:19看例子,业务层的代码差别不大张明 12:30:41还有一个优化也准备后面做,就是还要加一个内存池,不让GC回收。周梦飞 12:31:02这个叫达达的很厉害,一直做go服务器的,出了蛮多干货张明 12:34:12你看一看,顺便了解三个问题,1, bufio内部是怎么分配的内存,2, 分配多大的内存,3,这个内存是分配一次,还是多次。周梦飞 12:40:22OK张明 12:47:49想了一下,bufio也不行,肯定也不高效。周梦飞 12:48:36什么原因呢?张明 12:48:38最高效的做法,还是学C++的处理服务器包的方式。周梦飞 12:49:31c++也是recvBuf不断接收io数据,逻辑操作的总是recvBuf张明 12:50:14如果bufio每次都新建buf,那用都不能用,现在假设bufio接受用的一个固定大小的buf.周梦飞 12:50:52不会每次都新建,不然就数据就被丢了,肯定是一条连接一个buf的张明 12:51:07我们每read一次,这个buf的前一部分字节被我们读出来的,他内部是不是要进行数据的移动。周梦飞 12:51:20看看底层怎么控制buf的增长、缩短~周梦飞 12:51:41这个要看buf的设计了周梦飞 12:51:54数组试的,肯定会移动周梦飞 12:52:06circle式的就不用张明 12:52:18是的,我说的就是这个移动的次数会比我们自己实现移动次数多。周梦飞 12:52:26还有游标式的,只有内存不够才会移动张明 12:53:06这个我估计我们自己用一个固定的缓冲区来做,比这个会高效一点。周梦飞 12:53:45嗯,是有顾虑,看下bufio内存的缓冲咋写的,要是不好,可以参照逻辑写个类似的张明 12:54:03我们用一个固定的缓冲区来接收,解析出一个包,移动一次。张明 12:54:30包就定为4k。张明 12:54:48不用变动,我们目前所有TCP包,都很小.周梦飞 12:55:23好的张明 13:02:06但其实这样的优化不是很大,@周梦飞你研究过C++服务器,知道C++服务器里每一个连接都有一个接收缓冲区, 其GO底层就己经这个做了。张明 13:04:43这个优化我们需要做,不如C++那边的优化那么大。C++如果没有缓冲区就直接读的网络上的数据,像这样一个包读三次,服务器就完了。张明 13:05:31在GO语言里,我们一个包在我们这层逻辑上读了三次,在GO底层,其实一次性就读完了所有的数据。周梦飞 13:07:12不一定吧,如果数据还没到,io.Read会阻塞当前线程等数据,数据到了重新唤起张明 13:07:49数据没有到,不应该阻塞吗。张明 13:08:45GO在底层有一个读网络数据的逻辑,有大概8kb的缓存/周梦飞 13:13:59异步的网络架构不用,这个另一个问题了,写法差别也挺大



这个8k是缓存了从系统tcp层取来的数据……那io.Read平常的消耗应该不会很重

如果conn本身的io.Read不是取系统层的数据,那确实没多大必要再包一层buf
张明 13:15:05嗯,你可以查一些资料确认一下,我也是之前不记得在哪看的。张明 13:16:09不过8k缓存了TCP层的数据,但每次取也有锁,代价不大的情况下,包一层buf效率也有好处周梦飞 13:17:38待会看下bufio的实现……之前有瞅见过别人跟io.Read(还是Write?)解释调用路径的,找找看周梦飞 13:19:35IO调用的开销是什么呢?这得从Go的runtime实现分析起,假设我们这里用到的是一个TCP连接,从TCPConn.Read()为入口,我们可以定位到fd_unix.go这个文件中的netFD.Read()方法。



这个方法中有一个循环调用syscall.Read()pd.WaitRead()的过程,这个过程有两个主要开销。



首先是syscall.Read()的开销,这个系统调用会在应用程序缓冲区和系统的Socket缓冲区之间复制数据。



其次是pd.WaitRead(),因为Go的核心是CSP模型,要让一个线程上可以跑多个Goroutine,其中的关键就是让需要等待IO的Goroutine让出执行线程,当IO事件到达的时候再重新唤醒Go
routine,这样来回切换是有一定开销的。



而我们的这个分包协议的包头很小,有极大的概率是包头和一部分包体甚至是整个包已经在Socket缓冲区等待我们读取,这种情况就很适合使用bufio.Reader来优化性能。

周梦飞 13:19:55就是这段,他跟过系统调用路径,说明消耗点张明 14:31:05go在底层,使用的也是iocp, iocp接收数据,怎么会应用层调read的时候,才去复制数据。周梦飞 14:34:44如果是用的iocp,那这个syscall.Read很可能只是投递了一次RecvIO,要等完成端口的回调,这个过程中就有系统socket的拷贝了……只是猜测张明 14:38:40嗯,不管怎么实现,我们上面需要做的优化,肯定都要做。周梦飞 14:43:01看了下bufio.Read的实现,内部用的游标式buf,不会频繁移动内存



读完后游标会归零,复写之前的数据区,缓冲默认是4096
周梦飞 14:47:22这个buf貌似不会自动增长,满了后调panic("bufio: tried to fill full buffer")

有个接口NewReaderSize(2)可以指定buf的长度
张明 14:48:17如果不是circle的,就不可能不移动数据吧周梦飞 14:49:57游标式的,如果设计成写满就挂,那也不用移动……c++里的游标到尾巴了,如果前面用掉的区域够,就移动内存块到buf前面,如果还不够就会resize了张明 14:51:43这个还是不太好,最好的还是一个固定内存,一次性读完数据(小于4K), 解析所有完整的包,就把剩余的数据(不完整的包),往最前面移动的一次张明 14:52:25这个内存不存在resize的问题, IO也会减少。周梦飞 15:03:56模仿c++的做法,也可以,readRoutine是个单独的线程,阻塞了没啥周梦飞 15:16:53发现bufio实际已经是这样的了,整个文件里调底层Read的只有两个地方

一个是外界传入的[]byte超过4096时候,直接调底层Read,就是你讲的一次读完了

另一个是内部缓存已被读完,调了fill(),里面会用剩余的缓冲去读“b.rd.Read(b.buf[b.w:])”也是足够大的



效果上就是固定大内存去一次性尽可能读全部数据
周梦飞 15:50:38加上bufio了,再改了下doWrite的写法,用select省下每次都要的判断张明 15:51:17
Good ( ̄. ̄)+
周梦飞 15:53:37试了间隔200毫秒发100byte数据,能正常收到,没报错原文链接: https://www.cnblogs.com/3workman/p/5760067.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/238507

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 下午5:51
下一篇 2023年2月13日 下午5:51

相关推荐