文章转自本人公众号:机械猿,本人之前在四川某汽轮机从事结构强度设计,目前在阿里巴巴淘宝事业部担任高级开发工程师,有机械工程同行想转行IT,或者有想入职BAT的可以找我内推~
从一个图像传输项目说起
建连成功率,是网络特别是无线网络关注的重要指标。对于客户端demo,无论哪个平台做网络请求调用socket接口都能完成基本的数据收发流程。但是不做优化的话,成功率基本在95%左右。系统调用接口基本是在有线网络时代10Mbps的网速比较适用,要提升成功率,必须有良好的底层设计。
答主交付过淘宝ARbuy相关的业务,相信有过相关项目经验的大致知道技术流程。技术核心就是物体的识别,如果识别模型放在APP侧,剩下的就是控制报文的交互,提升小包信令成功率基本各个大厂,微信淘宝支付宝美团等都有自己的套路,大体思想类似,有空再展开。如果图片放到服务端识别,那么数据包的上传就需要特别设计了,并不是调用一下网络接口就行了。
几种基本思路
无线网络相对有线网络最大的特点就是高时延低带宽,网络状态无法预估。我们先把网络请求流程拆解来看:DNS查询、TCP建连、TLS握手、数据发送、ack回应。有关DNS优化与TLS握手0rtt有空再展开,内容太多,这里讲讲数据发送重传和回应超时设计。一般最有直观感受的就是迅雷下载大文件的断点续传设计,答主之前也实现过FTP/TFTP协议,重点就是续传记录包的设计实现。各大客户端的数据续传分为下面几种:
1、 续传前查询
发送大数据流失败后,先查询服务端接收点位置,得到回包后从下一序列号开始续传。
这里需要有一个信令请求,一般采用RPC请求。
2、 上传时回应确认,断连后重连时直接续传,代表:著名的微信
这个协议代码实现比较复杂,包括ACK回应包的时间间隔、收到多少数据流进行回应等,需要根据网络状况进行调整。大家可以参考TCP协议中的nagle算法与捎带ACK特性,基本有所参考。
3、 断连重新建连后客户端直接重传,服务端再通知客户端还缺哪些,客户端再快速重传。代表:淘宝、天猫
协议设计很完善,但是动手敲过代码调试过就知道里面有哪些坑,大家可以思考一下。
业务选择
断点续传的基本方法就是上面几种,选一个把超时设计好,就能做出一个比较好的方案了。
断点续传之所以麻烦,是因为客户端发送数据包之后,无法得知服务端接收到了多少。上面的几种解决方案从不同的思路解决了这个问题。
下面分析一下三种设计方案。第一种多了一个rtt,弱网环境下不是合适的选择。初看第三种方案设计更好,但是内核中TCP的实现是有缓冲区的,可以通过getsockopt查看,默认为16k。对于上传16k左右的文件,客户端是可以全部扔到缓冲区的,这个时候断连的话方案三就降级为先发送空包,等待服务端回包告诉客户端接收到哪里了。对的,就是方案一,凭空多了一个rtt。并且如果超时设计不好的话,总体效果还比不上方案一。
再看方案二,无线网络最大的特点就是抖动大,通过服务端及时不断的给客户端回应确认,设计合适的等超时,可以及时识别TCP是否成了假连接(NAT表失效等情况)。当然对于大型文件如迅雷下载,方案三是合适的。对于社交场景如微信等IM,对即时性要求较高的适合使用方案二。
TCP基础特性
先看一下TCP的三次握手:
那么,为什么server不需要对报文3进行确认呢?换句话说,client怎么知道ack报文被对端收到了呢?这里有个约定:TCP仅对有效数据(包括SYN报文和FIN报文)进行确认,不对ack报文进行确认。也就是报文3丢失了,对双方后续通信不影响,这个不属于有效信息,所以TCP中会有捎带ACK的特性,也就是ACK会跟有效应用层数据一起发给对端,如果一段时间内没有数据交互,那么200ms超时后会回一个单独的ACK报文。那么报文2丢失了呢?服务端在发送SYNACK报文之后会启动定时器,超时未收到报文3会进行重传。这里有一个SYN报文洪水攻击,各家有各家的防范措施,前阵子淘宝还因为遭遇黑产上西溪派出所报案。有的服务端使用syncookie防范非法客户端,但是对性能有一定影响,具体得结合业务优化。服务端后台针对ping、ICMP、DDOS攻击的防范有空再展开。
这里大家可以考虑这样一个场景:A向B约定下午两点一起去教室自习,A把信息发送给B(信息1),B回应自身已经收到信息(信息2),A向B回应已经收到回应确认(信息3)。我们看一下,A怎么确认信息3已经发送给B了呢?不需要A确认,因为如果B没有收到信息3的话,会继续重传信息2。B怎么告诉A已经收到了信息3呢?同样不需要确认,后面有数据发送直接发送就行,这个例子里就是A和B直接去教室自习就行,信息2确认收到后这个时候两边的信息状态已经同步了。
慢启动与拥塞避免
发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口,另外考虑到接受方的接收能力,发送窗口可能小于拥塞窗口。
慢启动值得就是一条TCP链接刚建立时不要一下发送大量数据导致网络拥塞激增,而是由小到大根据反馈逐渐增大拥塞窗口。下面是连接刚建立时客户端的发包行为,先不考虑接收端延迟ACK的情况:
可以看到拥塞窗口呈乘数增长。
为防止cwnd增长过大引起网络拥塞,还需设置一个门限ssthresh变量。当cwnd>ssthresh时,需采用拥塞避免算法。拥塞避免算法就是接收方每收到一个ack,就把cwnd加1。
快速重传
当发送端接收到三次重复确认时,就可判断对端尚未收到确认字段后续的报文,可以进行快速重传而不必等rto定时器超时。
快速重传之后就是快速恢复,发送方会将ssthresh门限减半,考虑到发送方收到重复确认,说明此时网络拥塞有所缓解,因此可以将拥塞窗口cwnd设置为ssthresh+3。为什么加3,是根据“数据包守恒”原则,同一时刻网络管道中的数据包数量是恒定的,收到三个重复ack,说明有三个数据包离开了网络管道。
发送端流量随时间变化如下:
Linux内核中使用的是cubic算法,基本原理与reno类似(reno本身也在演进),有一个对现实网络感知迟钝的原因就是ssthresh门限值难以预估。Linux4.9已经支持bbr,目前淘宝网络客户端已经基于bbr进行协议栈优化,这块有空写写。
最后厚着脸皮推广一下自己的公众号:机械猿,有机械工程同行想转行IT,或者有想入职BAT的可以找我内推~