Android 网络性能优化(2)DNS优化

DNS劫持又叫域名劫持,是指攻击者通过某种手段篡改了某个域名的解析结果,使得指向该域名的IP变成了另一个IP。

这里不过多讲解,因为内容很多,而且和本章没啥关联。

PS:你在看这篇文章时,左下角、右下角如果出现了广告,这很有可能能使DNS被劫持了,当然了,这也可能就是官方自己的广告= =。

2.4.2 DNS 服务器故障

每年都会经常出现 一些DNS服务器的故障,如果你恰好遇上,体验就很不好。这是因为DNS寻址是我们访问服务器的必要过程,DNS服务器故障直接导致寻不到址,或者寻址速度降低。

2.4.3 DNS 调度不准确

说不好一些DNS服务器里面的算法写的有点水,调度性能低下,这样会也会直接导致我们寻址速度变慢。

2.5 小结


总的来说,DNS寻址有下面几个缺点:

  • 不稳定

遭遇DNS劫持或DNS服务器宕机

  • 不准确

某些小运营商没有 DNS 服务器,直接调用其他运营商的 DNS 服务器,最终直接跨网传输

  • 不及时

运营商可能会修改 DNS 的 TTL(Time-To-Live,DNS 缓存时间),导致 DNS 的修改,延迟生效。

除了运营商,DNS服务器本身的索引算法也可能会有调度的问题(不过概率蛮小的)

所以看到DNS其实也有蛮多缺点的,那么我们该怎么去优化呢?

首先我们是开发App的,我们面向的问题是App在进行网络请求时,更快更稳定。那么最暴力的方法就是绕过DNS,我们使用DNS的原因是我们记不住冗长的ip地址,但在App开发中,App程序本身是可以记得住这些ip地址的,那就让它去记就行了,让它直接访问ip地址,就减少了DNS的寻址过程。

ip直连 就是以此为基础的实现方案。

3. ip直连

=========================================================================

3.1 原理


客户端从服务器拉取配置文件,配置文件包含了域名和ip地址的映射,客户端在之后的网络请求中,在配置中根据域名找到ip,直接连接这个ip地址进行请求。

而最开始拉取配置文件这个动作是经过DNS的,而之后都是绕过DNS的。

ip直连有以下的优势:

  • 在之后的连接中,都会绕过DNS,这样可以提高连接速率

  • 可以降低DNS劫持的风险

  • 一般都能拿到多个ip地址,所以可以进跑马比较,就快连接,这样可以提高连接质量

3.2 一个Http的直连demo


举一个最简单的直连的例子。假如我们需要直连百度,那我们先在cmd中,直接获取到百度的ip:

ping www.baidu.com

正在 Ping www.a.shifen.com [14.215.177.39] 具有 32 字节的数据:

来自 14.215.177.39 的回复: 字节=32 时间=6ms TTL=50

来自 14.215.177.39 的回复: 字节=32 时间=6ms TTL=50

来自 14.215.177.39 的回复: 字节=32 时间=6ms TTL=50

来自 14.215.177.39 的回复: 字节=32 时间=6ms TTL=50

14.215.177.39 的 Ping 统计信息:

数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),

往返行程的估计时间(以毫秒为单位):

最短 = 6ms,最长 = 6ms,平均 = 6ms

上面就已经拿到了百度的ip。接着在我们App中运用:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

// 直接加载这个页面

webview.loadUrl(“http://14.215.177.39/”)

}

}

这就是最简单的ip直连,绕过了DNS的域名寻址。 但是实际上,我们也不会这样简单的用,有三点:

  1. ip地址是会变的。比如人家服务器重启一下,就变了

  2. 一个服务器下面是允许有多个子服务器的,所以这些子服务器对外的ip是一样的。在访问时,如果不带上Host,可能会找不到想要的服务器。

  3. 现在基本都是使用 Https来访问了,而Https需要验证验证书,所以步骤会更加复杂。

3.3 Https的ip直连


Https应用到ip直连上,需要面临一个问题:证书的校验

在Https建立连接中,有这么一步,客户端需要拿着操作系统的根证书列表去验证的域名的,而一般来说,这个动作都是由运营商DNS来做的。而ip直连没有了DNS,代表着我们需要手动去验证。

举个例子:当你打开Chrome,使用ip直连的方式访问某个网站,并且使用Https的形式,即: https://xxx.xxx.xx/,你会看到下面这个页面:

在这里插入图片描述

你需要在界面输入 "thisisunsafe"来绕过证书验证访问该界面。

3.3.1 使用HttpsURLConnection实现ip直连

为了解决上述问题,我们需要手动验证,所幸的是Android SDK提供api供我们去验证,看了下别的语言的情况,好像Dart就没有。

Thread {

val url = “https://140.205.160.59/”

val hostName = “baidu.com”

val connection = URL(url).openConnection() as HttpsURLConnection

// 1

connection.hostnameVerifier = HostnameVerifier { host, session ->

HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)

}

connection.connect()

}

来看看上面代码。

注释1:

SDK提供了 HttpsURLConnection.getDefaultHostnameVerifier().verify() 的接口来验证,我们将我们要访问的域名代入,走低层校验逻辑。这部分我没阅读过源代码,我的猜测就是使用操作系统的根证书来验证。

3.3.2 解决多域名ip直连问题(SNI技术)

在3.2节中,ip直连还面临了一个问题,即一个服务器下面是允许有多个子服务器的,所以这些子服务器对外的ip是一样的。在访问时,如果不带上Host,可能会找不到想要的服务器。

但是Https中存在一个问题:即TLS握手的建立是在Http请求之前的,这说明就算你在请求行中带上Host也没用,因为顺序先后的问题,服务器根本不知道你要访问的是哪个域名。

SNI(Server Name Indication)技术则解决了这个问题:

SNI通过让客户端发送虚拟域名的名称作为TLS协商的一部分来解决此问题。这使服务器能够提前选择正确的虚拟域名,并向浏览器提供包含正确名称的证书。

而这个动作一般由运营商DNS来做的,所以绕过DNS,就需要自己手动连接。而SDK也提供了这些Api给我们。

下面是高于Api24的做法:

// 拿到SSLSocket

val ssl = SSLSocketFactory.getDefault().createSocket() as SSLSocket

// 使用 SSLParameters来设置SNI技术

val sslParameters = ssl.sslParameters

// 设置SNI,该Api要高于24

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

sslParameters.serverNames = listOf(SNIServerName(…))

}

低于24的代码,则需要继承 SSLSocketFactory,重写 createScoekt来返回一个实现SNI的SSLSocket,大致代码如下:

/**

  • 创建一个SSLSocket,就是建立握手前协商通道,这个时候利用SNI,为服务器传入一个虚拟域名

  • 这样服务器根据可以根据虚拟域名,返回该域名的证书

*/

override fun createSocket(s: Socket?, host: String?, port: Int, autoClose: Boolean): Socket {

// 拿到设置的Host

var peerHost = conn.getRequestProperty(“Host”)

// 获取Socket连接的ip地址

val address = s?.inetAddress

if(peerHost == null){

// 如果没有,则拿默认的

peerHost = host

}

if(autoClose){

// 原来的Socket可以不需要了

s?.close()

}

// 获取一个 SSLCertificateSocketFactory

val sslSocketFactory = SSLCertificateSocketFactory.getDefault(0) as SSLCertificateSocketFactory

// 建立一个SSLSocket, 但是这时还没有带证书验证

val ssl = sslSocketFactory.createSocket(address, port) as SSLSocket

// 支持 TLS 1.1/1.2 版本

ssl.enabledProtocols = ssl.enabledProtocols

// 为该域名设置SNI

sslSocketFactory.setHostname(ssl, peerHost)

val session = ssl.session

//获取低层默认证书验证器

val hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier()

if(!hostnameVerifier.verify(peerHost, session)){

// 如果验证失败,抛出错误

throw SSLPeerUnverifiedException(“Cannot verify hostname: $peerHost”)

}

return ssl

}

然后为 HttpsConnectionURL设置这个Factory:

connection.sslSocketFactory = SNISocketFactory(connection)

这大概就是ip直连的思路,当然了,在实际实战时,还是会有一些坑的,但是Android网络这方面的坑都被前人踩完了,所以大胆使用,不懂的可以直接百度、google就行了。

4. HttpDNS

============================================================================

4.1 原理


传统的DNS基本上都是基于UDP协议的,加上咱运营商参差不齐的管理Local DNS服务器,导致我们发出域名解析的那一瞬间,一切就变得不可控了。

而优化DNS的本质是让域名解析的过程变得可控,ip直连的本质做法是绕过DNS,直接不走DNS解析了,从根本上上解决问题。

而 Https另辟蹊径:既然udp不可靠,那么就换成可靠的tcp,既然运营商Local DNS容易被劫持,那就不用运营商的,用自己的服务器

HttpDNS顾名思义,使用Http,去发出域名解析的请求,而请求的服务器是HttpDNS服务器集群,是云服务器,效率更优于运营商DNS,它利用算法优势查询最优域名,我们不需要了解其内部实现,只需要知道,它比传统Local DNS要强大就可以了。如下图所示:

在这里插入图片描述

而HttpDNS服务器集群,可以自己开发, 也可以使用现成的,比如腾讯云、阿里云、微信都有他们自己研发的HttpDNS,收费都不高,接入方便。

4.2 使用HttpDNS的优势


请看下表:

| 功能 | HTTPDNS | 传统DNS |

| — | — | — |

| 防劫持 | Http基于Tcp,具有一定防劫持功能,传输可靠 | 大部分基于Udp,容易被黑客篡改,防劫持能力垃圾的一批 |

| 调度算法 | 根据来源ip,就近、就快的接入业务节点 | 黑匣子,每个运营商都有自己的接入策略,调度策略参差不齐 |

| 解析延迟 | 对热点域名解析、缓存DNS解析结果、解析结果懒更新策略等方式实现0ms更新 | 无差别多重服务器查询,解析时长200~2000ms不等 |

| 快速生效 | 避免Local DNS不遵循权威TTL,解析结果长时间无法更新的问题 | 会自行修改DNS 的 TTL(Time-To-Live,DNS 缓存时间),导致 DNS 的修改,延迟生效。 |

| 业务成功率优化 | 有效降低无线场景下解析失败的比率 | 未知 |

4.3 OkHttp接入HttpDNS


OkHttp组件允许我们接入HttpDNS,所以实现起来不是很难。 OkHttp默认是使用Android SDK的服务 InetAddress来解析域名,所以我们需要替换这个就行了。

步骤如下:

  1. 在App init的时候根据域名去HttpDNS拉取ip地址,将这些ip地址存储在本地DnsCache列表中

  2. 实现 DNS接口,重写lookup(),当OkHttp使用网络请求时,会走这个方法解析域名,那么让它去DnsCache列表中取就行了,如果没有的话,就走系统默认的DNS解析

  3. 调用OkHttp的 .dns()方法,使用第二步中的对象,替换OkHttp的域名解析实现。

我们需要自己实现DNS接口,并实现 lookup方法:

class HttpDNS : Dns {

// DNS cache

val dnsCache: MutableMap<String, MutableList>? = null

/**

  • 寻址方法, 必须重写

*/

override fun lookup(hostname: String): MutableList {

val ip = getIpByHost(hostname)

// 如果读不到ip,则使用系统默认的 InetAdaaress 来解析地址

return ip ?: Dns.SYSTEM.lookup(hostname)

}

/**

  • 查找缓存

*/

private fun getIpByHost(hostname: String): MutableList? {

return dnsCache?.get(hostname)

}

/**

  • 获取dns方法,一般在打开App的时候根据域名去获取ip列表,放在缓存里面

*/

private fun refresh(hostName: String): MutableList? {

val ipAddress: MutableList? = null

// 做网络请求, 这里是HttpDNS的服务器url, 拿着HostName去解析

val reqUrl: String = HTTPDNS_SERVER_URL;

val request = Request.Builder().url(reqUrl)

.addHeader(“domain”, hostName) // 在请求中代入hostname

.build()

OkHttpClient().newCall(request).enqueue(object : okhttp3.Callback {

override fun onFailure(call: Call, e: IOException) {

Log.w(“Rikka”, “解析失败”)

}

override fun onResponse(call: Call, response: Response) {

Log.d(“Rikka”, “解析成功”)

// 反正这里就是把解析的数据丢进cache中

dnsCache?.put(hostName, call.xxxxx)

}

})

return ipAddress

}

}

最后在 OkHttp中加入这个dns:

okHttpClient = new OkHttpClient().newBuilder()

.dns(new HttpDNS()) // 加入这个DNS

.build();

使用HttpDNS + OkHttp的话,通用性更强,因为OkHttp组件是可以加拦截器的,在SNI、Https的场景下都适用,而且规避了证书校验、域名检查环节。最重要的是使用非常的简单。

5. 域名收敛

=========================================================================

在讲解域名收敛之前,需要先了解域名发散

5.1 域名发散


PC时代,为了突破突破浏览器对于同一域名并发请求数的限制,使用域名发散为同一个服务申请多个域名,从而可以一定程度上提高并发量。简单的来说就是:

http 静态资源采用多个子域名

为什么浏览器会有并发限制呢?主要有两个原因:

  • 服务器本身承载能力差,所以在高并发的场景下,一些服务器都会崩,所以为了克服,只能由浏览器进行限制了。

  • 防止 DDOS 攻击。最基本的 DoS 攻击就是利用合理的服务请求来占用过多的服务资源,从而使合法用户无法得到服务的响应。如果最大并发量不受限制,那么DoS攻击就可以使用大量的请求,直至服务器崩溃

5.2 域名收敛


emmm显然,在上面介绍了DNS和域名发散后,我们显然发现:如果同时请求多个域名,那么DNS的解析速度肯定严重影响着整个请求过程。

移动端时代,响应速度是App赖以生存的根本,如果因为并发请求导致DNS并发,会产生 速度、开销的严重消耗。

所以解决域名发散带来的问题的方案有是: 域名收敛

移动端的网络请求模式和PC浏览器不同,移动端一般一个页面请求1~3个接口,所以很少场景达到一个端的高并发,所以可以接口收拢,比如一个页面只用一个接口。减少DNS寻址

5.3 SPDY / Http2


自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

初级工程师拿到需求会直接开始做,然后做着做着发现有问题了,要么技术实现不了,要么逻辑有问题。

而高级工程师拿到需求会考虑很多,技术的可行性?对现有业务有没有帮助?对现有技术架构的影响?扩展性如何?等等…之后才会再进行设计编码阶段。

而现在随着跨平台开发,混合式开发,前端开发之类的热门,Android开发者需要学习和掌握的技术也在不断的增加。

通过和一些行业里的朋友交流讨论,以及参考现在大厂面试的要求。我们花了差不多一个月时间整理出了这份Android高级工程师需要掌握的所有知识体系。你可以看下掌握了多少。

混合式开发,微信小程序。都是得学会并且熟练的

这些是Android相关技术的内核,还有Java进阶

高级进阶必备的一些技术。像移动开发架构项目实战等

Android前沿技术;包括了组件化,热升级和热修复,以及各种架构跟框架的详细技术体系

以上即是我们整理的Android高级工程师需要掌握的技术体系了。可能很多朋友觉得很多技术自己都会了,只是一些新的技术不清楚而已。应该没什么太大的问题。

而这恰恰是问题所在!为什么别人高级工程师能年限突破30万,而你只有十几万呢?

就因为你只需补充你自己认为需要的,但并不知道企业需要的。这个就特别容易造成差距。因为你的技术体系并不系统,是零碎的,散乱的。那么你凭什么突破30万年薪呢?

我这些话比较直接,可能会戳到一些人的玻璃心,但是我知道肯定会对一些人起到点醒的效果的。而但凡只要有人因为我的这份高级系统大纲以及这些话找到了方向,并且付出行动去提升自我,为了成功变得更加努力。那么我做的这些就都有了意义。

喜欢的话请帮忙转发点赞一下能让更多有需要的人看到吧。谢谢!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

练的

[外链图片转存中…(img-EvxhfSA0-1712194494190)]

这些是Android相关技术的内核,还有Java进阶

[外链图片转存中…(img-KPjrRe2l-1712194494191)]

高级进阶必备的一些技术。像移动开发架构项目实战等

[外链图片转存中…(img-Ro3e59oe-1712194494191)]

Android前沿技术;包括了组件化,热升级和热修复,以及各种架构跟框架的详细技术体系

[外链图片转存中…(img-1wfQAQBi-1712194494192)]

以上即是我们整理的Android高级工程师需要掌握的技术体系了。可能很多朋友觉得很多技术自己都会了,只是一些新的技术不清楚而已。应该没什么太大的问题。

而这恰恰是问题所在!为什么别人高级工程师能年限突破30万,而你只有十几万呢?

就因为你只需补充你自己认为需要的,但并不知道企业需要的。这个就特别容易造成差距。因为你的技术体系并不系统,是零碎的,散乱的。那么你凭什么突破30万年薪呢?

我这些话比较直接,可能会戳到一些人的玻璃心,但是我知道肯定会对一些人起到点醒的效果的。而但凡只要有人因为我的这份高级系统大纲以及这些话找到了方向,并且付出行动去提升自我,为了成功变得更加努力。那么我做的这些就都有了意义。

喜欢的话请帮忙转发点赞一下能让更多有需要的人看到吧。谢谢!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 27
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值