面试官又双叒叕“突袭”:如何优化一个网络请求
面试官:ok,看来是有备而来,那么我们今天聊聊网络优化咋做吧。
小萧:我大意了,没有闪。老头子,你不讲武德,我奉劝你耗子尾汁。
如何优化一个网络请求呢?
相信大家在面试的时候可能会被问到这个问题。今天我其实就是讲述下我知道的一些简单的优化方式,可以帮助大家在面试的过程中得到点基础分数。
我们先从最简单,大家比较容易了解到的讲起。
DNS 优化
DNS 则是典型的应用层的协议了,至于说为什么第二层能查第三层的 IP,因为 DNS 是 Domain Name System 缩写,所以你认为是服务是协 1653 议都可以。
一个 Http 请求在建立 Tcp 连接的过程中,肯定会产生一次 DNS,那么我们是不是可以通过内存缓存的方式,通过一个HashMap
持有这个Host
的IP
,当下次发起 Tcp 连接的时候,我们就可以用直接用内存中的这个 Ip,而不需要再去走一遍 Dns 服务了。
这个时候你肯定会问我,卧槽,你这个不是搞我吗,这可怎么改呀?
如果你的网络层用的是OkHttp
的话,Okhttp
在封装的时候就已经考虑到这个部分了,其内部提供了Dns
的接口,可以让外部在构造 Client 的时候传入。
class HttpDns : Dns {
private val cacheHost = hashMapOf<String, InetAddress>()
override fun lookup(hostname: String): MutableList<InetAddress> {
if (cacheHost.containsKey(hostname)) {
cacheHost[hostname]?.apply {
return mutableListOf(this)
}
}
return try {
InetAddress.getAllByName(hostname)?.first()?.apply {
cacheHost[hostname] = this
}
mutableListOf(*InetAddress.getAllByName(hostname))
} catch (e: NullPointerException) {
val unknownHostException =
UnknownHostException("Broken system behaviour for dns lookup of $hostname")
unknownHostException.initCause(e)
throw unknownHostException
}
}
}
这里可以稍微给大家展开下,LocalDns
是不可以被信任的,经常会有运营商会搞一些奇奇怪怪的 Dns 拦截,导致大家收到的请求是运营商所缓存的(目的是为了省流量),所以阿里腾讯等都有自己对外输出的HttpDns
的服务。这个服务可以帮助大家找到真实准确的 Host 的 Ip,就是这个服务是收钱的。
CacheControl
Http 请求在 1.1 阶段就引入了CacheControl
了,通过CacheControl
可以让后端直接控制请求内容的缓存策略。所以还有比缓存更简单粗暴的网络优化方式吗?
在 http 中,控制缓存开关的字段有两个:
Pragma
?和?Cache-Control
。
通过图片简单的介绍下一些缓存参数。
如果说一句不负责任的话,这个只要后端大佬开启 CacheControl 就好了呀,原生网络库本来就支持的。当然后端大佬一般都不是特别愿意,其实各位安卓也可以通过添加OkHttp
拦截器的方式给网络请求添加一个统一的CacheControl
,当然如果你有定制化的需求肯定还是要自己开发的,我这里只负责科普下这个面试可以回答的地方,细节大家可以参考下这个仓库。
HTTP 协议规格说明定义 ETag 为“被请求变量的实体值”。另一种说法是,ETag 是一个可以与 Web 资源关联的记号(token)。典型的 Web 资源可以一个 Web 页,但也可能是 JSON 或 XML 文档。服务器单独负责判断记号是什么及其含义,并在 HTTP 响应头中将其传送到客户端,以下是服务器端返回的格式:ETag:"50b1c1d4f775c61:df3"客户端的查询更新格式是这样的:If-None-Match : W / "50b1c1d4f775c61:df3"如果 ETag 没改变,则返回状态 304 然后不返回,这也和 Last-Modified 一样。测试 Etag 主要在断点下载时比较有用。
而我们只要使用了CacheControl
,就可以用到ETag
, 如果当数据内容没有发生变更的情况下,就不会传输数据,这样也可以给大家略微优化下你们的 Api 请求。
Http 1.0 - 1.1 - 1.X - 2.0
以下所有内容均来自网络 HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
当然我们还可以让后端升级接口协议版本,这个可以明显提升你请求响应性能。
长连接,HTTP 1.1 支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP1.1 中默认开启 Connection: keep-alive,一定程度上弥补了 HTTP1.0 每次请求都要创建连接的缺点。
header 压缩,如上文中所言,对前面提到过 HTTP1.x 的 header 带有大量信息,而且每次都要重复发送,HTTP2.0 使用 encoder 来减少需要传输的 header 大小,通讯双方各自 cache 一份 header fields 表,既避免了重复 header 的传输,又减小了需要传输的大小。
新的二进制格式(Binary Format),HTTP1.x 的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0 和 1 的组合。基于这种考虑 HTTP2.0 的协议解析决定采用二进制格式,实现方便且健壮。
多路复用(MultiPlexing),即连接共享,即每一个 request 都是是用作连接共享机制的。一个 request 对应一个 id,这样一个连接上可以有多个 request,每个连接的 request 可以随机的混杂在一起,接收方可以根据 request 的 id
将 request 再归属到各自不同的服务端请求里面。
HTTP2.0 的多路复用和 HTTP1.X 中的长连接复用有什么区别?
HTTP/1.* 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接;
HTTP/1.1 Pipeling 解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;
HTTP/2 多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;
好了,下面要开始真的进入牛逼的东西了,前文你肯定以为我是个大水逼,复制黏贴。
GRPC( A high-performance, open-source universal RPC framework)
不知道各位有没有听说过一个都市怪谈,字节的网络库优化有多厉害多厉害,网络底层采用的是 Webview 底层的 Chromium 的网络库,在弱网情况下对于 api 的优化啥的,巴拉巴拉.....
Cronet 是 Chromium 网络引擎对不同操作系统做的封装,实现了移动端应用层、表示层、会话层协议,支持 HTTP1/2、SPDY、QUIC、WebSocket、FTP、DNS、TLS 等协议标准。支持 Android、IOS、Chrome OS、Fuchsia,部分支持 Linux、MacOS、Windows 桌面操作系统。实现了 Brotli 数据压缩、预连接、DNS 缓存、session 复用等策略优化以及 TCP fast open 等系统优化。本文内容基于 Chromium 75 版本。
字节用的就是Chrome
的cronet网络库(顺便展开下,cronet 同时支持 ios,android,前端)。而由于 grpc 协议的问题,所以传输内容直接使用的protobuf
格式,所以其不仅仅是网络层上的优化,同时由于流能直接转化成实体类,同时也减少了可序列化的时间。
protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10 倍)、更快(20 ~ 100 倍)、更为简单。
你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。
但是正常的网络框架基本都使用了Retrofit+Okhttp
,而且大家都已经使用的很习惯了,所以我大胆的猜测,字节其实应该用OkHttp
桥接了cronet
。所以这样基本就能无缝桥接当前已有的网络库了。
由 GRRC 升级 QUIC
QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于 UDP 的低时延的互联网传输层协议。在 2016 年 11 月国际互联网工程任务组(IETF)召开了第一次 QUIC 工作组会议,受到了业界的广泛关注。这也意味着 QUIC 开始了它的标准化过程,成为新一代传输层协议
其实整个QUIC
协议(Http3.0 协议)本来就是谷歌写的,所以谷歌的Cronet
本身就支持这也是正常的。 我其实之前就特地去查过OKHttp
支持的协议内容,当前还是只停留在 2.0 阶段,主要就还是因为当前的Connection
写的太好了,而且需要把 Tcp 直接更换成 Udp,所以迟迟没有更新 3.0 协议的支持。
所以各位如果想从协议层去做对应的优化,那么可能 OkHttp 带给大家的应该还是无尽的等待了。
还能干吗?
评论