周末被反应国内的服务福建省出现无法调用的情况,心中大概知道什么问题。这个问题经常出现,尤其是在福建,江苏,河南等省份。
下面是简单的解决方案,分别为:HTTPS(SSL),CDN,ipv6,组建集群,客户端指定DNS
HTTPS
大部分国内的服务加上证书可以解决很多问题,没错这是真实情况…
CDN
最优的解决方案,目前可以查询到上面三个省份均有节点的服务商:
- 阿里云
- 腾讯云
- 七牛云
其它服务商暂时没有查询,具体可以咨询官网客服。如果是前后端分离的项目,推荐购买接口加速的CDN就可以了。至此这个方案基本可以解决所有国内地区的访问难题!
ipv6
现在国内云厂商是提供免费ipv6的,且绝大部分用户实际上设备和网络状况均支持ipv6,如果可以使用ipv6进行访问也可以解决很大程度的DNS污染问题。
后端集群
在访问不到的地区购置服务器,组建集群,亦可解决问题,但是依然需要考虑被重复封禁的问题以及客户端因为地理位置解析不精准或者缓存等原因,并未选择最优线路的方案;亦或者选择的最优路线根本就是访问不到的路线。所以这个方案被排除在最后!(这里只是以解决访问问题为例,而非其他原因)
客户端方案:指定可访问的DNS
部分运营商会存在白名单/黑名单的情况,当然我们的备案服务和国内IP暂时可以排除黑名单的问题。但是不排除运营商DNS存在污染情况。如果定位不到问题,不妨让客户端多加一点逻辑。首先是检测哪个DNS服务访问最快,以Android端为例,以下用到第三方库版本分别为:OKHttp3(4.13.2),dnsJava(3.5.2),下面为具体逻辑代码:
object DnsLatencyTester {
// DNS服务器列表
private val dnsServers = listOf(
//并非越多越好,下面只是国内流行且高可用的DNS地址,另外还有360的DNS地址,具体情况请按需添加
"223.5.5.5", // 阿里公共DNS
"223.6.6.6", // 阿里公共DNS
"119.29.29.29", // 腾讯公共DNS
"119.28.28.28", // 腾讯公共DNS
"180.76.76.76", // 百度公共DNS
"114.114.114.114", // 114DNS纯净版
"114.114.115.115", // 114DNS纯净版
)
// 测试API地址,协商你们的任意可访问服务地址
private const val TEST_URL = ""
private const val TIMEOUT_MS = 5000L
/**
* 测试所有DNS服务器的延迟并返回最快的一个
*/
suspend fun findFastestDns(context: Context): String? = withContext(Dispatchers.IO) {
// 为每个DNS创建异步测试任务
val deferredResults = dnsServers.map { dns ->
async { testDnsLatency(dns) to dns }
}
// 等待所有测试完成并过滤掉超时的结果
val results = deferredResults.awaitAll()
.filter { it.first != -1L }
.sortedBy { it.first }
// 返回延迟最低的DNS
results.firstOrNull()?.second
}
/**
* 测试单个DNS服务器的延迟
* @return 延迟时间(毫秒),-1表示超时或失败
*/
private suspend fun testDnsLatency(dnsServer: String): Long = withContext(Dispatchers.IO) {
return@withContext try {
// 创建自定义DNS
val customDns = object : Dns {
override fun lookup(hostname: String): List<InetAddress> {
try {
// 使用指定DNS服务器解析域名
val addresses = InetAddress.getAllByName(hostname)
return addresses.toList()
} catch (e: UnknownHostException) {
// 如果解析失败,尝试直接使用IP连接
return listOf(InetAddress.getByName(dnsServer))
}
}
}
// 配置OkHttpClient
val client = OkHttpClient.Builder()
.dns(customDns)
.connectTimeout(TIMEOUT_MS, TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT_MS, TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT_MS, TimeUnit.MILLISECONDS)
.build()
// 创建请求
val request = Request.Builder()
.url(TEST_URL)
.head() // 使用HEAD请求减少数据传输
.build()
// 记录开始时间
val startTime = System.currentTimeMillis()
// 发送请求
client.newCall(request).execute().close()
// 计算延迟时间
System.currentTimeMillis() - startTime
} catch (e: Exception) {
// 发生异常时返回-1
-1L
}
}
}
可以在闪屏页调用(注意实际用户体验):
lifecycleScope.launch {
val fastestDns = DnsLatencyTester.findFastestDns(binding.imageViewcontent.context)
if (fastestDns != null) {
Log.i("DNS_TEST", "最快的DNS服务器: $fastestDns")
SPUtils.getInstance().put("fastestDns", fastestDns)
getInApp()
} else {
Log.e("DNS_TEST", "所有DNS服务器测试失败")
SPUtils.getInstance().put("fastestDns", "")
getInApp()
}
}
OKHttp3的使用示例:
var customDns: Dns = Dns.SYSTEM // 默认使用系统 DNS
val fastestDns = SPUtils.getInstance().getString("fastestDns")
if (fastestDns.isNotEmpty()) {
try {
customDns = CustomDns(fastestDns)
} catch (e: UnknownHostException) {
e.printStackTrace()
customDns = Dns.SYSTEM
}
}
val okHttpClientBuilder = OkHttpClient().newBuilder().apply {
connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
dns(customDns)
addInterceptor(MInterceptor())
addInterceptor(DataEncryptInterceptor())
中间存在的CustomDNS
代码如下:
class CustomDns(dnsServer: String) : Dns {
private var resolver: SimpleResolver? = null
init {
try {
// 使用自定义的DNS服务器地址
this.resolver = SimpleResolver(dnsServer)
} catch (e: UnknownHostException) {
throw IllegalArgumentException("Invalid DNS server address: $dnsServer", e)
}
}
@Throws(UnknownHostException::class)
override fun lookup(hostname: String): List<InetAddress> {
try {
// 使用dnsjava进行解析
val name = Name("$hostname.")
val record: Record = Record.newRecord(name, Type.A, DClass.IN)
val query = Message.newQuery(record)
val response = resolver!!.send(query)
val addresses: MutableList<InetAddress> = ArrayList()
if (response.rcode == Rcode.NOERROR) {
val records: Array<Record> = response.getSectionArray(Section.ANSWER)
for (r in records) {
if (r is ARecord) {
val a = r as ARecord
addresses.add(a.address)
}
}
}
if (addresses.isEmpty()) {
throw UnknownHostException("No address found for $hostname")
}
return addresses
} catch (e: Exception) {
// 捕获所有dnsjava的异常,并转换为UnknownHostException
throw UnknownHostException("DNS lookup failed for " + hostname + ": " + e.message)
}
}
}
以上就是我目前可以想到的全部解决方案,综合之后我个人建议的排序为:
CDN>DNS>集群,(ipv6以及HTTPS为默认配置😄)