即时通讯开发移动端弱网络优化方法总结

本篇的目的,就是希望以通俗易懂的语言,帮助移动端IM开发者更好地针对性优化移动网络的各种特性,使得开发出的功能给用户带来更好的使用体验。

本文乃全网同类文章中,唯一内容最全、“粪”量最重者,请做好心理准备耐心读下去,不要辜负作者已打上石膏的双手和用废的键盘。

优化方法一:“快链路”

我们需要有一条(相对)快速、(相对)顺畅、(相对)稳定的网络通道承载业务数据的传输,这条路的最好是传输快、不拥堵、带宽大、收费少。生活中做个类比,我们计划驱车从深圳到广州,如果想当然走广深高速十之八九要杯具,首先这个高速略显破败更像省道,路况不佳不敢提速;其次这条路上的车时常如过江之鲫,如果身材不好操控不便,根本就快不起来;最后双向六车道虽然勉强可以接受,但收费居然比广深沿江高速双向八车道还贵;正确的选路方案目前看是走沿江高速,虽然可能要多跑一段里程,但是通行更畅快。

① 控制传输包大小

控制传输包的大小在1400字节以下。暂时不讲为什么这样建议,先举个例子来类比一下,比如一辆大卡车满载肥猪正在高速上赶路,猪笼高高层叠好不壮观,这时前方突然出现一个隧道限高标识,司机发现卡车超限了,这下咋整。方案一,停车调头重新找路,而且十之八九找不到,最后只能哪来回哪;方案二,把其中一群猪卸下来放本地找人代养,到达目的地卸完货回来再取,你别说,这个机制在TCP/IP协议栈中也有,学名“IP分片”,后面会专门介绍。这个故事侧面证实美国计算机科学家也曾经蹲在高速路边观察生猪超载运输的过程,并饱受启发。且慢,每次遇到问题,想到一些方案后我们都应该再扪心自问:“还有没有更好的办法呢?”。当然有,参照最近流行的说法,找个台风眼,把猪都赶过去,飞一会就到了,此情此景想想也是醉了。

回归正题,概括的说,我们设定1400这个阈值,目的是减少往复,提高效能。因为TCP/IP网络中也有类似高速限高的规定,如果在超限时想要继续顺畅传输,要么做IP分片要么把应用数据拆分为多个数据报文(意指因为应用层客户端或服务器向对端发送的请求或响应数据太大时,TCP/IP协议栈控制机制自动将其拆分为若干独立数据报文发送的情况,后面为简化讨论,都以IP分片这个分支为代表,相关过程分析和结论归纳对二者均适用)。而一旦一个数据报文发生了IP分片,便会在数据链路层引入多次的传输和确认,加上报文的拆分和拼接开销,令得整个数据包的发送时延大大增加,并且,IP分片机制中,任何一个分片出现丢失时还会带来整个IP数据报文从最初的发起端重传的消耗。

我们可以得出如下结论,TCP/IP数据报文大小超过物理网络层的限制时,会引发IP分片,从而增加时空开销。

因此,设定合理的MSS至关重要,对于以太网MSS值建议是1400字节。什么,你的数学是体育老师教的吗?前面说以太网最大的传输数据大小是1500字节,IP数据报文包头是20字节,TCP报文包头是20字节,算出来MSS怎么也得是1460字节呀。如果回答是因为很多路由设备比如CISCO路由器把MSS设定为1400字节,大伙肯定不干,回忆一下IP和TCP的数据报包头都各有40字节的可选项,MTU中还需要为这些可选项留出空间,也就压缩了MSS的空间。要是再追问为啥这个值不是1380字节,那就有点过分了。

扫描二维码关注公众号,回复: 14427432 查看本文章

知识加油站:什么是MSS?

TCP MSS(TCP Maximum Segment Size,TCP最大报文段长度,后面均简称MSS)表示TCP/IP协议栈一次可以传往另一端的最大TCP数据长度,注意这个长度是指TCP报文中的有效“数据”(即应用层发出的业务数据)部分,它不包括TCP报文包头部分,我们可以把它理解为卡车能装运生猪的最大数量或重量。它是TCP选项中最经常出现,也是最早出现的选项,占4字节空间。

MSS是在建立TCP链接的三次握手过程中协商的,每一方都会在SYN或SYN/ACK数据报文中通告其期望接收数据报文的MSS(MSS也只能出现在SYN或SYN/ACK数据报中),说是协商,其实也没太多回旋的余地,原因一会讲。如果协商过程中一方不接受另一方的MSS值,则TCP/IP协议栈会选择使用默认值:536字节。

那么问题来了,控制“限高”哪种方案才最强。我们尝试探讨一下。

首先,可以在我们自己IDC内将各种路由交换设备的MSS设定小于或等于1400字节,并积极参与TCP三次握手时的MSS协商过程,期望达到自动控制服务器收发数据报文大小不超过路径最小MTU从而避免IP分片。这个方案的问题是如果路由路径上其它设备不积极参与协商活动,而它的MTU(或MSS设置值)又比较low,那就白干了。这就好比国家制定了一个高速沿途隧道限高公示通告标准,但是某些地方政府就是不告诉你,没辙。

其次,可以在业务服务中控制应用数据请求/响应的大小在1400字节以下(注:也无法根本避免前述方案中间路由MTU/MSS low的问题),在应用层数据写入时就避免往返数据包大小超过协商确定的MSS。但是,归根到底,在出发前就把数据拆分为多个数据报文,同IP分片机制本质是相同的,交互响应开销增加是必然的。考虑到人在江湖,安全第一,本方案从源头上控制,显得更实际一些。

当然,最靠谱的还是做简法,控制传输数据的欲望,用曼妙的身姿腾挪有致,相关的内容放到轻往复章节探讨。即时通讯聊天软件app开发可以加蔚可云的v:weikeyun24咨询

对应到前面的快乐运猪案例,就是要么在生猪装车之前咱们按照这条路上的最低限高来装车(问题是怎么能知道整个路上的最低限高是多少),要么按照国家标准规定允许的最小限高来装车,到这里,肥猪们终于可以愉快的上路了,风和日丽,通行无阻,嗯,真的吗?

② 放大TCP拥塞窗口

把TCP拥塞窗口(cwnd)初始值设为10,这也是目前Linux Kernel中TCP/IP协议栈的缺省值。放大TCP拥塞窗口是一项有理有据的重要优化措施,对移动网络尤其重要,我们同样从一些基本理论开始逐步深入理解它。

TCP是个传输控制协议,体现控制的两个关键机制分别是基于滑动窗口的端到端之间的流量控制和基于RTT/RTO测算的端到网络之间的拥塞控制。

流量控制目标是为了避免数据发送太快对端应用层处理不过来造成SOCKET缓存溢出,就像一次发了N车肥猪,买家那边来不及处理,然后临时囤货的猪圈又已客满,只好拒收/抛弃,相关概念和细节我们不展开了,有兴趣可以研读《TCP/IP详解 卷一:协议》。

拥塞控制目标是在拥塞发生时能及时发现并通过减少数据报文进入网络的速率和数量,达到防止网络拥塞的目的,这种机制可以确保网络大部分时间是可用的。拥塞控制的前提在于能发现有网络拥塞的迹象,TCP/IP协议栈的算法是通过分组丢失来判断网络上某处可能有拥塞情况发生,评判的具体指标为分组发送超时和收到对端对某个分组的重复ACK。在有线网络时代,丢包发生确实能比较确定的表明网络中某个交换设备故障或因为网络端口流量过大,路由设备转发处理不及时造成本地缓存溢出而丢弃数据报文,但在移动网络中,丢包的情况就变得非常复杂,其它因素影响和干扰造成丢包的概率远远大于中间路由交换设备的故障或过载。比如短时间的信号干扰、进入一个信号屏蔽的区域、从空闲基站切换到繁忙基站或者移动网络类型切换等等。网络中增加了这么多不确定的影响因素,这在TCP拥塞控制算法最初设计时,是无法预见的,同时,我们也确信未来会有更完善的解决方案。

拥塞控制是TCP/IP协议栈最经典的和最复杂的设计之一,互联网自我牺牲的利他精神表露无遗,设计者认为,在拥塞发生时,我们应该减少数据报文进入网络的速率和数量,主动让出道路,令网络能尽快调整恢复至正常水平。

③ 调大SOCKET读写缓冲区

把SOCKET的读缓冲区(亦可称为发送缓冲区)和写缓冲区(亦可称为接收缓冲区)大小设置为64KB。在Linux平台上,可以通过 setsockopt 函数设置SO_RCVBUF和SO_SNDBUF选项来分别调整SOCKET读缓冲区和写缓冲区的大小。

这两个缓冲区跟我们的TCP/IP协议栈到底有怎么样的关联呢。我们回忆一下TCP数据报格式及首部中的各字段里面有个16位窗口大小(见下图),还有我们前面提到的流量控制机制和滑动窗口的概念,大幕徐徐拉开,主角纷纷粉墨登场。在正式详细介绍之前,按照传统,我们还是先站在猪场老板的角度看一下,读缓冲区就好比买家用来囤货的临时猪圈,如果货到了买家使用部门来不及处理,就先在这里临时囤着,写缓冲区就好比养猪场根据订单装好车准备发货,如果买家说我现在可以收货便可速度发出,有点明白了吧。

④ 调大RTO(Retransmission TimeOut)初始值

将RTO(Retransmission TimeOut)初始值设为3s。

TCP为每一个报文段都设定了一个定时器,称为重传定时器(RTO),当RTO超时且该报文段还没有收到接收端的ACK确认,此时TCP就会对该报文段进行重传。当TCP链路发生超时时,意味着很可能某个报文段在网络路由路径的某处丢失了,也因此判断此时网络出现拥塞的可能性变得很大,TCP会积极反应,马上启动拥塞控制机制。

RTO初始值设为3s,这也是目前Linux Kernel版本中TCP/IP协议栈的缺省值,在链路传输过程中,TCP协议栈会根据RTT动态重新计算RTO,以适应当前网络的状况。有很多的网络调优方案建议把这个值尽量调小,但是,我们开篇介绍移动网络的特点之一是高时延,这也意味着在一个RTT比较大的网络上传输数据时,如果RTO初始值过小,很可能发生不必要的重传,并且还会因为这个事件引起TCP协议栈的过激反应,大炮一响,拥塞控制闪亮登场。

猪场老板的态度是什么样的呢:曾经有一份按时发货的合同摆在我的面前,我没有去注意,等到重新发了货才追悔莫及,尘世间最痛苦的事莫过于此,如果上天能给我一个再来一次的机会,我希望对甲方说耐心点,如果非要给这个耐心加一个期限的话,我希望是一万年。

⑤ 禁用TCP快速回收

TCP快速回收是一种链接资源快速回收和重用的机制,当TCP链接进入到TIME_WAIT状态时,通常需要等待2MSL的时长,但是一旦启用TCP快速回收,则只需等待一个重传时间(RTO)后就能够快速的释放这个链接,以被重新使用。

Linux Kernel的TCP/IP协议栈提供了一组控制参数用于配置TCP端口的快速回收重用,当把它们的值设置为1时表示启用该选项:

1)  net.ipv4.tcp_tw_reuse = 1

2) net.ipv4.tcp_tw_recycle = 1

3)  net.ipv4.tcp_timestamps = 1(tcp_tw_recycle启用时必须同时启用本项,反之则不然,timestamps用于RTT计算,在TCP报文头部的可选项中传输,包括两个参数,分别为发送方发送TCP报文时的时间戳和接收方收到TCP报文响应时的时间戳。Linux系统和移动设备上的Android、iOS都缺省开启了此选项,建议不要随意关闭)

以上参数中tw是TIME_WAIT的缩写,TIME_WAIT与TCP层的链接关闭状态机相关。

⑥ HTTP协议:打开SOCKET的TCP_NODELAY选项

TCP/IP协议栈为了提升传输效率,避免大量小的数据报文在网络中流窜造成拥塞,设计了一套相互协同的机制,那就是Nagle's Algorithm和TCP Delayed Acknoledgement。

Nagle算法(Nagle's Algorithm)是以发明人John Nagle的名字来命名。John Nagle在1984年首次用这个算法来尝试解决福特汽车公司的网络拥塞问题(RFC 896),该问题的具体描述是:如果我们的应用程序一次产生1个字节的数据(典型的如telnet、XWindows等应用),而这个1个字节数据又以网络数据包的形式发送到远端服务器,那么就很容易使网络中有太多微小分组而导致过载。

因为传输1个字节有效数据的微小分组却需花费40个字节的额外开销(即IP包头20字节 + TCP包头20字节),这种有效载荷利用率极其低下的情况被统称为愚蠢窗口症候群(Silly Window Syndrome),前面我们在谈MSS时也提到过,如果为一头猪开个大卡车跑一趟,也够愚钝的。对于轻负载广域网或者局域网来说,尚可接受,但是对于重负载的广域网而言,就极有可能引起网络拥塞导致瘫痪。

现代TCP/IP 协议栈默认几乎都启用了这两个功能。

我们在移动APP的设计实现中,请求大部分都很轻(数据大小不超过MSS),为了避免上述分析的问题,建议开启SOCKET的TCP_NODELAY选项,同时,我们在编程时对写数据尤其要注意,一个有效指令做到一次完整写入(后面会讲协议合并,是多个指令一次完整写入的设计思想),这样服务器会马上有响应数据返回,顺便也就捎上ACK了。

接入调度

① 就快接入

在客户端接入服务器调度策略的演化过程中,我们最早采用了“就近接入”的策略,在距离客户端更近的地方部署服务器或使用CDN,期望通过减少RTT来提高网络交互响应性能。这个策略在国内的落地执行还需要加一个前缀:“分省分运营商”,这就给广大负责IDC建设的同学带来了巨大的精神和肉体折磨。

在持续运营的过程中,根据观察到的数据,发现并非物理距离最近的就是最快的。回忆一下前面谈到的吞吐量指标BDP,它与链路带宽和RTT成正比关系,而RTT是受物理距离、网络拥塞程度、IDC吞吐量、跨网时延等诸多因素综合影响的,单纯的就近显然不够精细了。

“就快接入”在“就近接入”策略的基础上改善提升,它利用客户端测速和报告机制,通过后台大数据分析,形成与客户端接入IP按就快原则匹配接入服务器的经验调度策略库,令客户端总能优先选择到最快的服务器接入点。

② 去DNS的IP直连

DNS不但需要1个RTT的时间消耗,而且移动网络下的DNS还存在很多其它问题:

1) 部分DNS承载全网用户40%以上的查询请求,负载重,一旦故障,影响巨大,这样的案例在PC互联网也有很多,Google一下即可感受触目惊心的效果;

2) 山寨、水货、刷ROM等移动设备的LOCAL DNS设置错误;

3) 终端DNS解析滥用,导致解析成功率低;

4)

某些运营商DNS有域名劫持问题,实际上有线ISP也存在类似问题。域名劫持对安全危害极大,产品设计时要注意服务端返回数据的安全校验(如果协议已经建立在安全通道上时则不用考虑,安全通道可以基于HTTPS或者私有安全体系)。对于劫持的判断需要客户端报告实际拉取服务数据的目标地址IP等信息;

5) DNS污染、老化、脆弱。

综上就是在前述就快接入小节中,接入调度FSM会优先使用动态服务器列表的原因。

③ 网络可达性探测

在连接建立过程中如果出现连接失败的现象,而终端系统提供的网络状态接口反馈网络可用时,我们需要做网络可达性探测(即向预埋的URL或者IP地址发起连接尝试),以区别网络异常和接入服务异常的情况,为定位问题,优化后台接入调度做数据支持。

探测数据可以异步报告到服务器,至少应该包含以下字段:

1) 探测事件ID,要求全局唯一不重复;

2) 探测发生时间;

3) 探测发生时网络类型和其它网络信息(比如WIFI时的SSID等);

4) 本地调度的接入服务器集合类型;

5) 本地调度的接入服务器IP(如使用域名接入,可忽略);

6) 探测的目标URL或IP地址

7) 本次探测的耗时。

猜你喜欢

转载自blog.csdn.net/wecloud1314/article/details/126095280