该篇博客记录了观看WWDC Session714《Optimizing your app for today’s internet》的内容以及一些理解。
全球移动数据
一、全球有大约40亿人在使用互联网
- 这个数字超过了世界人口的一般
- 在全球范围内,使用人数的增长率正在减缓
二、互联网整体还是发展的
- 机器到机器、物联网、只能家具都在发展
- 中国、印度互联网也在发展
- 智能手机、移动数据也在发展
关注不太理想的网络情况
如果一个应用是在快速的LTE网络环境下构建的,那么它可能在2G移动网络环境下会表现的很糟糕。
但是如果一个应用是在2G移动网络环境下构建的,那么它在快速的LTE网络环境下也会表现的很好。
所以我们应该多关注网络情况较差的情况,对于这种情况,有以下几点建议:
- 2G移动网络在全球范围内依旧是很普遍的
- Network Link Conditioner(NLC)是一个很好的工具,它可以模拟接近于真实世界的网络状况
- 使用工具(例如:Wireshark和tcptrace)来帮助分析问题
- 应该从一开始就构建不同网络环境,如果在最后再去构建不同网络环境,那么此时已经晚了
IPv6的可用性
IPv6是十分重要的,因为它比IPv4有更好的性能。
如果要让App有更好的性能,我们不仅需要确保App的性能,还要确保App连接的服务器支持IPv6。
IPv6的使用在全世界一直呈现增长的趋势:
下面是一组Apple统计的关于印度的网络数据:
由图中数据可以看出,在使用IPv6的情况下,75%的TCP连接可以在150ms内建立;而使用IPv4的情况下,建立连接可能耗时325ms以上,由此可见IPv6对于性能的提升。
Explicit Congestion Notification(ECN)
我们可以使用Explicit Congestion Notification(ECN)来提高性能,ECN是通过减少数据包丢失和重传技术来提高性能的。
默认情况下,macOS和iOS已经启用了这个功能,所以我们不需要在应用上做额外的事情来启用该功能。我们唯一需要做的就是确保我们的后台支持ECN
以下是对于Alexa前百万服务支持ECN占比的调查数据:
Multipath TCP(MPTCP)
我们在使用TCP时,如果遇到了网络切换,可能会导致连接的断开。
MPTCP可以根据每个数据包来进行路由决策,进而实现实时切换连接,解决该问题。
关于MPTCP,有以下几个要点:
- 更快的连接故障切换
- 与代理一起使用
- 从2013年开始在端到端的服务器上使用
- 依赖于移动运营商,全球78%的运营商支持MPTCP,只有22%的运营商不支持MPTCP
TCP Fast Open(TFO)
- 可以避免TCP三次握手连接建立时间
- 需要确认运营商提供的服务器支持TCP Fast Open
Quick UDP Internet Connections (QUIC)
一、QUIC是一种新的传输协议
- 可能接替TCP
- 运行在无法辨识的资料包层面
- 提供可靠的、噪音可控的数据流
二、标准化的IETF项目正在进行中
三、苹果的工程师参与了该项目的开发
四、QUIC一旦准备好,Apple就会提供支持它的API
DNS
DNS的性能
现在的DNS有如下特点和缺陷:
一、许多网站使用较短生命周期的DNS记录
- DNS生命周期低于60s
- 这么做支持快速故障处理
二、现在的数据中心很少有down掉的情况
- 很少使用快速故障处理
- IP地址通常不会更改
- 由于DNS记录生命周期很短且IP地址通常不会更改,可能造成无故的250ms的延迟
- 一些ISPs缓存DNS的时间要比60s长
DNS优化
优化的思路为:如果要求DNS记录,Apple会立刻返回记录,无论该记录是否过期;如果记录已经过期,那么Apple会异步的进行新的DNS记录的查询;如果查询结果与过期结果一致,那么就提升了效率,如果查询结果与预期结果不一致,那么就异步通知程序新的地址。
关于DNS的优化,以下是几个重要的点:
一、CloudKit目前已经使用该优化
二、可以通过在DNSServiceQueryRecord
或者DNSServiceGetAddrInfo
中设置kDNSServiceFlagsAllowExpiredAnswers
来启用优化
三、该优化需要配合Happy Eyeballs(RFC 6555)算法进行
- 如果拥有记录,那么就尝试去连接
- 如果DNS报告了新的地址,那么也尝试连接新地址
Avoid SCNetworkReachability
我们经常使用 SCNetworkReachability 来预测网络,一般步骤为:
- 通过 SCNetworkReachability 来检查网络
- 网络检查通过,尝试去连接
- 在此过程中,网络可能会发生变化,导致连接失败,就会再次进行第一步
上述问题主要是由于设计不合理,在上述设计中,我们做的是预测网络是否可达,但是预测网络是一件不靠谱的事情。
在iOS11中,为URLSession.config提供了waitsForConnectivity属性,该属性表明只有系统在确认网络通畅的情况下才会进行请求,进而避免了请求失败再次开启请求的问题。
Transport Layer Security (TLS) 1.3
TLS1.2已经发布10年了,现在TLS1.3已经准备好了,下面是TLS1.3的一些要点:
- 提高了安全性
- 缩短了建立连接的时间
- IESG(互联网工程指导小组)在2018年3月21日批准了最终草案
- 今年夏天将作为IETF RFC被RFC编辑队列发布
- 最终版本已经在iOS 12中测试过了
- 现在在iOS或macOS中默认关闭TLS1.3,可以使用
sudo defaults write /Library/Preferences/com.apple.networkd tcp_connect_enable_tls13 1
提前打开TLS1.3
Certificate Transparency
我们可能会遇到证书颁发机构处于恶意或无能,向实体颁发了不该颁发的证书,这样会引起安全问题。
解决这一个问题的方法是一个叫做证书透明日志的东西,该物具有以下特点:
- 证书透明日志是对已经颁发证书的公共验证日志
- 任何人都可以访问该日志
- 客户端可以检查该证书是否被记录
如果一个流氓证书颁发机构颁发虚假证书,如果它要记录日志,那么就会被发现它在颁发虚假证书;如果它对该证书不进行记录,那么客户端不会通过验证。
它的工作流程如下:
- 该策略从2018年末开始:所有公开的受信任的CA机构颁发的新的TLS证书必须能够被Certificate Transparency验证
- 现有的已经颁发的证书不受影响
- 客户端不受影响
Bonjour Conformance Test
Bonjour Conformance Test可以验证你的硬件设备是否正确实现Bonjour。
Bonjour Conformance Test验证被要求使用的场景
- 硬件产品上使用了Bonjour名称和logo
- 使用Windows应用程序绑定Windows安装包的Bonjour
- AirPrint、AirPlay、CarPlay以及HomeKit设备
Bonjour Conformance Test的好处
- 帮助提高产品的可靠性
- 提高用户体验
API Choices
现在我们的应用中,可能使用URLSession来访问网络,也可能使用第三方框架访问网络,第三方框架可能是通过BSD Socket来访问网络,同样的,我们的应用也可以直接使用BSD Socket来访问网络。如下:
Apple开始开源URLSession底层的Network.framework框架,并且强烈建议我们在程序中使用URLSession或直接使用Network.framework,同时也建议第三方框架提供商也改用Networ.framework,避免使用BSD Sockets。如下:
URLSession
URLSession是一个高级基础网络API,它支持HTTP/2、HTTP/1.1;支持过程中和过程外的转移;支持处理cookies、缓存、身份验证和代理;提供URLSessionStreamTask用于TCP连接
URLSession是所有Apple平台上推荐的API
接下里从以下四个方面讨论下URLSession的细节:
- 延迟(Latency)
- 吞吐量(Throughput)
- 响应能力(Responsiveness)
- 系统资源(System resources)
延迟(Latency)
减少延迟
HTTP 1.1
如果我们现在需要向服务器请求三份资源,在使用HTTP 1.1的情况下,整体流程如下:
在这个过程中,我们耗费许多资源去重复打开新连接,如果我们改用单个连接会怎么样?
单个连接
使用单个连接的过程如下:
在这个过程中,我们可以看到,我们必须等到前一个请求得到响应之后才能开启第二个请求,这就叫做HTTP线头阻塞(HTTP head-of-line blocking)
HTTP 2
我们改用HTTP 2来实现该次需求,过程如下:
在这个过程中,HTTP/2减少了每个请求之间的间隔,同时减少了等待响应的空闲时间。
HTTP/2具有以下优点:
- 在HTTP层没有HTTP线头阻塞
- 更好的带宽利用率
- 无需客户端更改(前提是使用URLSession)
- 减少服务器端开销
HTTP/2 Connection Coalescing
从今年开始URLSession引入了HTTP/2 Connection Coalescing来减少连接。
URLSession采用了新的连接策略:当 1.IP地址匹配或者2.连接由同一个TLS证书覆盖的主机时,URLSession将只开启一个连接。
新的策略在iOS 12和macOS Mojave中启用。
URLSession原有的策略如下:
URLSession新的策略如下:
新策略的优点
- 连接重用
- 对HTTP/2特别重要
- 减少创建URLSession对象昂贵的消耗
吞吐量(Throughput)
最大化吞吐量
原理:减少请求大小
HTTP cookies
- 指定好域和路径
- 使用较小cookies
- 删除不想要的cookies或者为cookies设置过期时间
- 适当的在服务器上保存一些状态
HTTP/2报头压缩
URLSession支持两种压缩方式
Gzip
- 支持HTTP和HTTPS压缩
Brotli
- iOS 11和macOS High Sierra之后推出
- 仅支持HTTPS压缩
- 针对文本和HTML做了优化
- 对短数据压缩率最好
响应能力(Responsiveness)
提升响应能力
QoS(Quality of Service)
首先我们看一下QoS类别,由高到低分别为:
- 用户交互的(User-Interactive)
- 用户创建的(User-Initiated)
- 默认的(Default)
- 实用程序的(Utility)
- 后台的(Background)
URLSession对于QoS的结合
- URLSession对于QoS是有感知的
- 当调用
task.resunme()
时,会获取Dispatch queue的QoS - Delegate的消息遵循QoS
Network Service Type
在今年,会新增一种responsiveData级别的类型:
- 大多数情况使用default和background
- 当使用Cisco Fast Lane时,会自动标记为responsiveData
URLSession Adaptable Connectivity
- 之前,我们只要有请求就会立刻进行连接
- 允许每一个URLSession使用
waitsForConnectivity
- 如果资源不是需要资源,那么在回调
func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask)
中调用task.cancel()
系统资源(System resources)
更好地利用系统资源
Use Background Sessions
- 要利用系统智能的开启与暂停任务
- 如果任务没有完成,即使App没有运行,那么传输也在执行
使用后台Session的代码如下:
// Optionally set isDiscretionary for non-urgent downloads
let configuration = URLSessionConfiguration.background(withIdentifier: "com.apple.example")
configuration.isDiscretionary = true
Cache Wisely
- 缓存应该是暂时的
- 不要缓存唯一的内容
- 考虑在以下方法中决定是否对某个内容进行缓存
// Implement delegate method to decide when to cache content
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
willCacheResponse proposedResponse: CachedURLResponse,
completionHandler: @escaping (CachedURLResponse?) -> Void) {
// If you don't want to cache
completionHandler(nil)
}
- 如果可能,服务端通过
Cache-Control: no-store
来决定是否缓存
总结
以下总结今天提到的优化方式以及对优化入手方面的效果: