诚信为本,市场在变,诚信永远不变...
  咨询电话:400-123-4567

公司新闻

客户端网络优化(一) - 原理篇

网络优化是客户端技术方向中公认的一个深度领域,对于 App 性能和用户体验至关重要。本文除了 、连接和带宽方面的优化技术外,会结合着优化的一些实践,以及在成本和收益的衡量,会有区别于市面上其他的分享,希望对大家有所帮助。

为什么优化

肯定有同学会有疑问,网络请求不就是通过网络框架 SDK 去服务端请求数据吗,尤其在这个性能过剩的年代,时间都不会差多少,我们还有没有必要再去抠细节做优化了,废话不多说咱们直接看数据,来证明优化的价值

  • 发现网站加载时间每延长 1 秒 用户便会流失 10%

  • 发现页面加载时间超过 353% 的用户将停止访问

  • 发现加载时间每延长 1 秒一年就会减少 16 亿美元的营收

如何优化

想知道如何优化,首先我们需要先确定优化方向:

  • 提高成功率
  • 减少请求耗时
  • 减少网络带宽

接下来我们了解下 网络连接流程,如下图:

connection.png

从上图我们能清晰的看出 连接经历了以下几个流程:

  • : 1 个
  • 需要经历 ,, 三次握手1.5个,不过 和 合并了: 1 个
  • 需要经过握手和密钥交换: 2 个
  • 数据传输

综上所述,一个 连接需要 4 个 才到数据传输阶段,如果我们能减少建连的 次数,或者降低数据传输量,会对网络的稳定和速度带来很大的提升。

  • DNS & HttpDNS

DNS(Domain Name System,域名系统), 用于用户在网络请求时,根据域名查出 IP 地址,使用户更方便的访问互联网。传统DNS面临DNS缓存、解析转发、攻击等问题,具体的DNS流程如下图所示:

local_dns.png

优先通过 协议与自建 服务器交互,如果有问题再降级到运营商的 方案,既有效防止域名劫持,提高域名解析效率又保证了稳定可靠, 的原理如下图所示:

httpdns.png

HttpDNS优势

  • 稳定性:绕过运营商 ,避免域名劫持,解决了由于 缓存导致的变更域名后无法即时生效的问题
  • 精准调度:避免 调度不准确问题,自己做解析,返回最优服务端 IP 地址
  • 缩短解析延迟:通过域名预解析、缓存 解析结果等方式实现缩短域名解析延迟的效果

综述

先来看看主要的两点收益

  • 防止劫持,不过由于客户端大都已经全栈 了, 时代的中间人攻击已经基本没有了,但是还是有收益的。
  • 解析延迟带来的速度提升,目前全栈 后,大都已经是长连接,数据统计单域名下通过 占比 5%+, 解析平均耗时 80ms 左右,如果平摊到全量的网络请求 会带来 1% 左右的提升

上面的收益没有提到精准调度,是因为我们的 APP 流量主要在国内,国内节点相对丰富,服务整体质量也较高,即使出现调度不准确的情况,差值也不会太大,但如果在国外情况可能会差很多。

长连接

长连接指在一个连接上可以连续发送多个数据包,做到连接复用 我们知道从 开始就支持了长连接,但是 中引入了多路复用的技术。 大家可以通过 该链接 直观感受下 比 到底快了多少。

http1_2.png

这是 Akamai 公司建立的一个官方的演示,用以说明 相比于之前的 在性能上的大幅度提升。同时请求 379 张图片,从加载时间 的对比可以看出在速度上的优势。

的多路复用技术代替了原来的序列和阻塞机制。 中,如果想并发多个请求,必须使用多个 TCP 链接,且浏览器为了控制资源,还会对单个域名有连接数量限制,有了二进制分帧之后,不再依赖 TCP 链接去实现多流并行了,所有请求都是通过一个 TCP 连接并发送完成,利用请求优先级解决关键请求阻塞问题,使得重要的请求优先得到响应。这样更容易实现全速传输,减少 TCP 慢启动时间,提高传输的速度。传输流程如下图:

multiplexing.png

说了这么多优点,那多路复用有缺点么,主要是受限TCP的限制,一般来说同一域名下只需要使用一个 TCP 连接。但当连接中出现频繁丢包情况,就会有队头阻塞问题,所有的包都会等待丢包重传成功后传输,这样的表现情况反倒不如 了,还可以通过多个 TCP 连接并行传输数据。

域名合并

随着开发规模逐渐扩大,各业务团队出于独立性和稳定性的考虑,纷纷申请了自己的接口域名,域名会越来越多。我们知道 属于应用层协议,在传输层使用 协议,在网络层使用IP协议,所以 的长连接本质上是 长连接,是对一个域名( IP )来说的,如果域名过多面临下面几个问题:

  • 长连接的复用效率降低
  • 每个域名都需要经过 服务来解析服务器 IP。
  • 请求需要跟不同服务器同时保持长连接,增加了客户端和服务端的资源消耗。

具体方案如下图所示

domain_merge.png

TLS-v1.2 会话恢复

会话恢复主要是通过减少 TLS 密钥协商交换的过程,在第二次建连时提高连接效率 -> 。具体是如何实现的呢?包含两种方式,一种是 ,一种是 。下面讲解了 的原理:

img

  • Session ID

    类似我们熟知的 的概念。 由服务器端支持,协议中的标准字段,因此基本所有服务器都支持,客户端只缓存会话 ID,服务器端保存会话 ID 以及协商的通信信息,占用服务器资源较多。

  • Session Ticket

    存在一些弊端,比如分布式系统中的 同步问题,如果不能同步则大大限制了会话复用效率,但 Session Ticket 没问题。 更像我们熟知的 的概念, 用只有服务端知道的安全密钥加密过的会话信息,保存在客户端上。客户端在 时带上了 ,服务器如果能成功解密就可以完成快速握手。

不管是 还是 都存在时效性问题,不是永久有效的。

TLS-v1.3

首先需要明确的是,同等情况下, 比 少一个 时间。并且废除了一些不安全的算法,提升了安全性。首次握手,完成 密钥协商需要 2 个 时间,只需要 1 个 时间。会话恢复 需要 1 个 时间, 则因为在第一个包中携带数据(early data),只需要 0 个 ,有点类似 层的 。

  • 首次握手流程

    img

  • 会话恢复-0RTT

    中更改了会话恢复机制,废除了原有的 和 的方式,使用 的机制,并支持了 0RTT模式下的恢复模式(实现 的条件比较苛刻,目前不少浏览器虽然支持 协议,但是还不支持发送 ,所以它们也没法启用 模式的会话恢复)。当 client 和 server 共享一个 (从外部获得或通过一个以前的握手获得)时, 允许 client 在第一个发送出去的消息中携带数据("")。Client 使用这个 PSK 生成 并用它加密 。Server 收到这个 之后,用 扩展中的 导出 并用它解密 。 会话恢复模式如下:

    img

HTTP/3

首次在2013年被提出,互联网工程任务组在 2018 年的时候将基于 协议的 重命名为 。不过目前 还没有最终定稿,最新我看已经到了第 34 版,应该很快就会发布正式版本。某些意义上说 协议实际上就是。(,快速 网络连接) 基于 ,利用 的速度与效率。同时 也整合了 、 和 的优点,并加以优化。用一张图可以清晰地表示他们之间的关系。

主要带来了零 建立连接、连接迁移、队头阻塞/多路复用等诸多优势,具体细节暂不介绍了。推出的原理与实践,敬请期待。

头部压缩

的 中的字段很多时候都是重复的,例如 、 等。随着请求增长,这些请求中的冗余标头字段不必要地消耗带宽,从而显著增加了延迟,因此, 头部压缩技术应时而生,使用的压缩算法为 。借用 Google 的性能专家 Ilya Grigorik 在 Velocity 2015 ? SC 会议中分享的一张图,让我们了解下头部压缩的原理:

head_compress1.png

上述图主要涉及两个点

  • 静态表中定义了61个 字段与 ,可以通过传输 进而获取 ,极大减少了报文大小。静态表中的字段和值固定,而且是只读的。详见静态表
  • 动态表接在静态表之后,结构与静态表相同,以先进先出的顺序维护的 字段列表,可随时更新。同一个连接上产生的请求和响应越多,动态字典积累得越全,头部压缩效果也就越好。所以尽量上面提到的域名合并,不仅提升连接性能也能提高带宽优化效果

简单描述一下 算法的过程:

  • 发送方和接受方共同维护一份静态表和动态表
  • 发送方根据字典表的内容,编码压缩消息头部
  • 接收方根据字典表进行解码,并且根据指令来判断是否需要更新动态表

看完了 头部压缩的介绍,那在没有头部压缩技术的 时代,当处理一些通用参数时,我们当时只能把参数压缩后放入 传输,因为 只支持 码,压缩导致乱码,如果放入 还得 或者 编码,不仅增大了体积还要浪费解码的性能。

数据表明通用参数通过 的 传输,由于长连通道大概在 90%+ 复用比例,压缩率可以达到惊人的 90%,同样比压缩后放在 中减少约 50% 的 size,如果遇到一些无规则的文本数据, 压缩率也会随着变低,这时候提升将会更明显。说了这么多,最后让我们通过抓包看下, 头部压缩的效果吧:

head_compress2.png

Json vs Protobuffer

是 Google 出品的一种轻量 & 高效的结构化数据存储格式,性能比 好,Size 比 要小

  • 数据格式

    因为咱们这里说的是带宽,所以咱们详细说下 size 这块的优化,相比 json 来说, 节省了字段名字和分隔符,具体格式如下:

    
    
  • 数据编码方式

    的数据格式不可避免的存在定长数据和变长数据的表示问题,编码方式用到了 。 这里主要介绍下 ,因为 主要是对于负数时的一个补充( 不太适合表示负数,有兴趣的同学可以自行查下资料 ) , 其实是一种变长的编码方式,用字节表示数字,征用了每个字节的最高位 (), 是 1 话,则表示还有后序字节,一直到 为 0 的字节为止,具体表示如下表:

    
    

    不难看出值越小的数字,使用越少的字节数表示。如对于 类型的数字,一般需要 4 个字节 表示,但是用了 小于 128 一个字节就够了,但是对于大的 数字(大于2^28)会需要 5 个 字节 来表示,但大多数情况下,数据中不会有很大的数字,所以采用 方法总是可以用更少的字节数来表示数字

  • 具体示例

    首先定义一个实体类

    
    

    的序列化结果

    
    

    Protobuffer 的序列化结果

    
    

    简单说下 的序列化逻辑吧, 的 字段取值为 1 的话,类型为 则对应的编码是:。 的类型是 ,对应的 取 0。而它的 又是 1,所以第一个字节是 ,第二个字节是数字 1 的 编码,即 。

    
    
  • 优化数据

    原始数据 Size 比 小 30%左右,压缩后 Size 差距 10%+

上面说了这么多技术的原理,接下来分享下我们的技术选型以及思考,希望能给大家带来帮助

  • 我们的应用主要在国内使用,这个功能只对首次建连有提升,目前首次建连的比例较小,也就在5%左右,所以对性能提升较小;安全问题再全栈切了 后也没有那么突出;并且 也会遇到一些技术难点,比如自建 需要维护一套聪明的服务端IP调度策略;客户端需要注意 IP 缓存 以及 的双栈探测和选取策略等,综合考虑下目前我们没有使用

  • 长连接&域名合并

    长连接和域名合并,这两个放在一起说下吧,因为他们是相辅相成的,域名合并后,不同业务线使用一个长连接通道,减少了TCP 慢启动时间,更容易实现全速传输,优势还是很明显的。

    长连接方案还是有很多的,涉及到 、、、自建长链等方式,最终我们选择了,并不是因为这个方案性能最优,而是综合来看这个方案最有优势, 就不用说了,这个方案早就淘汰了,而 虽然性能好,但是目前阶段并没有完整稳定的前后端框架,所以只适合尝鲜,是我们接下来的一个目标,自建长链虽然能结合业务定制逻辑,达到最优的性能,但是比较复杂,也正是由于特殊的定制导致没办法方便的切换官方的协议(如)

  • TLS 协议

    协议我们积极跟进了官方最新稳定版 版本的升级,客户端在 和 后已经开始默认支持 ,想在低系统版本上支持需要接入三方扩展库,由于支持的系统版本占比已经很高,后面也会越来越高,并且 协议是兼容式的升级,允许客户端同时存在和两个版本,综合考虑下我们的升级策略就是只升级服务端

    协议升级主要带来两个方面的提升,性能和安全。先来说下性能的提升, 对比版本来说,性能的提升体现在减少握手时的一次 时间,由于连接复用率很高,所以性能的提升和 一样效果很小。至于安全前面也有提到,废弃了一些相对不安全的算法,提升了安全性

  • 带宽优化

    带宽优化对于网络传输的速度和用户的流量消耗都是有帮助的,所以我们要尽量减少数据的传输,那么在框架层来说主要的策略有两种:

    一是减少相同数据的传输次数,也就是对应着 的头部压缩,原理上面也有介绍,这里就不在赘述了,对于目前长连接的通道, 中不变化的数据只传输一个标识即可,这个的效果还是很明显的,所以框架可以考虑一些通用并且不长变化的参数放在 中传输,但是此处需要注意并不是所有的参数都适合在 中,因为 中的数据只支持 编码,所以有一些敏感数据放在此处会容易被破解,如果加密后在放进来,还需要在进行 编码处理,这样可能会加大很多传输的 size,所以框架侧要进行取舍 (PS:在补充个 字段的小坑:都会转成小写,而 大小写均可,在升级 协议的时候需要注意下)

    二是减少传输 size,常规的做法如切换更省空间的数据格式 ,因为关联到数据格式的变动,需要前后端一起调整,所以改造历史业务成本会比较高有阻力,比较适合在新的业务上尝鲜,总结出一套最佳实践后,再去尝试慢慢改造旧的业务

平台注册入口