前言

Mars 是微信官方的终端基础组件,是一个使用 C++ 编写的业务无关、跨平台的基础组件。目前在微信 Android、iOS、Windows、Mac、WP 等多个平台中使用。Mars 主要包括以下几个独立的部分:

  • COMM:基础库,包括socket、线程、消息队列、协程等基础工具;

  • XLOG:通用日志模块,充分考虑移动终端的特点,提供高性能、高可用、安全性、容错性的日志功能;(详情点击:高性能日志模块xlog)

  • SDT:网络诊断模块;

  • STN:信令传输网络模块,负责终端与服务器的小数据信令通道。包含了微信终端在移动网络上的大量优化经验与成果,经历了微信海量用户的考验。

Mars 系列开始,将为大家介绍 STN(信令传输网络模块)。由于 STN 的复杂性,该模块将被分解为多个篇章进行介绍。本文主要介绍微信中关于 socket 连接及 IP&Port 选择的思考与设计。

你需要知道的TCP连接

TCP 协议应该是目前使用的最广泛的传输层协议,它提供了可靠的端到端的传输,为应用的设计节省了大量的工作。TCP 建立连接的”三次握手”与连接终止的“四次挥手”也广为人知。在这简单的 connect 调用中,还能做怎样的思考与设计呢?

  1. int connect(int sockfd, const struct *addr, socklen_t addrlen)

连接的超时重传

超时与重传是 TCP 协议最核心的部分,在不稳定的移动网络中,超时重传的设计尤为重要。在连接建立的过程中,由于网络本身的不可靠特性,不可避免的需要重传的机制来保障可靠服务。在《TCP/IP详解 卷1》的描述中,在大多数 BSD 实现中,若主动 connect 方没有收到 SYN 的回应,会在第6秒发送第2个 SYN 进行重试,第3个 SYN 则是与第2个间隔24秒。在第75秒还没有收到回应,则 connect 调用返回 ETIMEOUT。

这就意味着,在不能立刻确认失败(例如 unreachable 等)的情况下,需要75秒的时间,才能获得结果。如果真相并不是用户的网络不可用,而是某台服务器故障、繁忙、网络不稳定等因素,那75秒的时间只能尝试1个 IP&Port 资源,对于大多数移动应用而言,是不可接受的。我们需要更积极的超时重传机制!!!

然而,我们并不能修改 TCP 的协议栈,我们只能在应用层进行干预,设计应用层的超时机制。说干就干,这个时候你是否已经在构思新的、应用层的连接超时重传机制了呢?应用层的超时重传,典型做法就是提前结束 connect 的阻塞调用,使用新的 IP&Port 资源进行 connect 重试。但是,我们应该选择怎样的连接超时值呢?4秒?10秒?20秒?30秒?不同的应用场景会有不同的选择。我们来看一下常见的几种场景:

  • 连不同 or 网络不可用等

  • 服务器繁忙 or 中间路由故障等

  • 基站繁忙 or 连接信号弱 or 丢包率高等

在第一种场景中,连接超时设置不会带来什么区别。在第二种场景中,部分服务器资源或路由不可用,我们希望连接超时能稍微短一些,使得我们能尽快的发现故障,并且通过更换 IP&Port 的方式获得可用资源或路由路径。而第三种场景则是在移动网络中经常遇到的弱网络的场景。在这种场景中,我们更换 IP&Port 资源也是无效的,因此希望连接超时能相对长一些,进行更多的TCP层的重传。(当然,也不是超时越长越好,后面的分析可以看到很多等待时长是效果低微的)

不同的场景对连接超时有不同的需求,然而,我们在程序中并没有很好的方法来区分这些场景。在进行连接超时这个阈值的选择前,我们先来看看,当前主流的 android、iOS 操作系统的连接设计。android 的 TCP 层连接超时重传如下图所示(测试机型为 nexus5,android 4.4)。超时间隔依次为(1,2,4,8,16),第5次重试后32秒返回 ETIMEOUT,总用时63秒。超时设置符合 Linux 的常规设置。

但在不同的机型中,偶尔会出现差异性。如下图 android 抓包(三星 android 4.4)。

iOS 的 connect 超时重传如下图所示。超时间隔依次为(1,1,1,1,1,2,4,8,16,32),总共是67s。

经过 tcpdump 的调研分析后,我们发现:

  1. 在 iOS 系统中对 connect 的超时重传进行了一定的修改,在 connect 初期使用更积极的策略,以适应移动网络的不稳定特征。而在 android 系统中,connect 超时重传则使用了较为“懒惰”、适用于有线网络的超时重传间隔;

  2. 不管什么平台,连接总超时时长都需要1分钟左右,这个时长在大多数移动应用中,都是不符合用户体验要求的;

  3. 连接的初始阶段,TCP 超时重传会更积极一些,越到后面,重传间隔越大。

因此,在实际的连接超时设置上,我们根据不同的系统特征,结合应用能接受的“用户体验”范围,可以设置不同的连接超时间隔。例如在 iOS 系统中,由于采用了较为积极的超时间隔,我们可以将 connect 调用的超时设置为10s。在10s内,iOS 会自动进行6次的重发。在 android 系统中,系统会在第7秒发起第3次重发,之后需要在第15秒才会重发。在不同的用户体验要求下,应用可以将 connect 的调用超时设置为不同的值。例如也可以设置为10s(意味着给第3次重发3s的等待时间),从而避免无效的等待时长。同时通过更换 IP&Port 后,重新调用 connect 操作的方式,来获得更积极的重发策略,更快的查找到可用的 IP&Port 组合。

连接的终止

“四次挥手”的连接终止协议已经口熟能详。过程如下图所示。需要关注的是,图中主动关闭的一方会进入 TIME_WAIT 状态,在此状态中通常将停留2倍的 MSL 时长。MSL 时长在不同的操作系统中有不同的设置,通常在30秒到60秒。TIME_WAIT 的数量太多会导致耗尽主动关闭方的 socket 端口和句柄,导致无法再发起新的连接,进而严重影响主动关闭方的并发性能。虽然在实际的使用中,可以通过 tcp_tw_recycle,tcp_tw_reuse,tcp_max_tw_buckets 等方式缓解该问题,但也会带来一些副作用。最好的解决方案是在协议的设计上,尽量的由终端来发起关闭的操作,避免服务器的大量 TIME_WAIT 状态。例如,使用长连接避免频繁的关闭;在短连接的协议设计上,务必加上终止标记(例如 http 头部加上 content-length )使得可以由终端来发起关闭的操作。

串行连接 VS 并发连接 VS 复合连接

在上述的连接超时策略中,我们选择10秒的连接超时。这就意味着我们需要10秒的时间来确认一个 IP&Port 组合的 connect 超时。当我们有多个 IP&Port 资源时,遍历的效率偏低。那我们是否能设置 connect 的超时为更短呢?例如4秒。我们知道移动互联网具有不稳定的特征,超时时间设置过短,会导致在弱网络的情况下,connect 总是失败,导致不可用。串行连接的策略在超时选择上,由于需要兼顾高性能与高可用的设计目标,使得该策略是一个相对“慢”的连接策略。

与此相应,我们会想到并发连接的策略。并发连接,同时发起对N个 IP&Port 的连接调用,可以让我们第一时间发现可用的连接,并且还顺带发现了 connect 最快的 IP&Port 配置。并发连接可以一举解决了“高性能”、“高可用”的设计目标,看起来很完美。然而,这个时候,服务端的同学“跳”起来了。在并发连接的策略下,服务器需要提供的连接能力是串行连接的N倍,对服务器连接资源是极大的浪费。同时,并发连接是否会引起连接资源的竞争,从而影响网络正常用户的常规体验,也是个未知的因素。

让我们来回顾串行连接与并行连接的优缺点。

串行连接

  • 资源占用少

  • 无服务器负载问题

  • 超时选择困难

  • 最慢可用

并行连接

  • 网络资源竞争

  • 服务器负载高

  • 最快可用

那么,有没有一种策略,能同时满足高性能、高可用、低负载的目标呢?在微信的连接设计中,我们使用了”复合连接“的策略。如下图所示。

初始阶段,应用发起对 IP1 &Port1 的 connect 调用。在第4秒的时候,如果第一个 connect 还没有返回,则发起对 IP2 &Port2 的 connect 调用。以此类推,直至发起了5组 IP&Port 的 connect 调用。 
对比串行连接与并行连接,复合连接有以下特点:

  • 常规情况下,服务器负载与串行连接策略相同,实现了低负载的目标;

  • 异常情况下,每4s发起新(IP,Port)组合的 connect 调用,使得应用可以快速的查找可用 IP&Port,实现高性能的目标;

  • 在超时时间的选择上,复合方式的“并发”已经实现了高性能、低负载的目标,因此在超时时间的选择上可以相对宽松,以保障高可用为重。

综合对比,复合连接能够维持低资源消耗的情况下,能同时实现低负载、高性能、高可用的目标。

微信 IP&Port 排序算法的演进

在建立连接的调用中,除了超时时间的设置外,IP&Port是连接的最重要参数。IP&Port 的排序、选择对于 connect 的性能也是有着重大的影响。本节主要讨论在已知 IP 列表、Port 列表的情况下,如何排序、组合的问题,而不讨论如何获得就近接入等问题。

IP&Port 的组成

在微信中,IP有多种来源类型。优先级从上而下分别为:

  • WXDNS IP

  • DNS IP

  • Auth IP

  • Hardcode IP

WXDNS IP 是通过微信自建的 DNS 服务获得的IP列表,自建 DNS 对防劫持、有效期控制等有重要作用。DNS IP 则是通过常规的 DNS 解析获得的 IP 列表。Auth IP 是微信动态下发的保底IP列表。而Hardcode IP 则是最终的保底IP列表。总体而言,分为常规IP列表、保底IP列表两个类别。WXDNS IP、DNS IP 为常规列表,Auth IP,Hardcode IP 为保底列表。同时,在组成实际使用的 IP&Port 列表时,由于 WXDNS 与 DNS 的功能近似,因此通常只出现其中一种类型的IP列表。Auth IP 与Hardcode IP 的功能近似,也是同时只能出现两者中的一种类型。 
在 Port 的选择上,微信服务在常规情况下提供2个端口,预防端口被封锁的情况。特别情况下,可以通过配置下发进行端口更新。

IP&Port排序算法(一):随机组合排序算法

每个TCP连接都是以 IP&Port 的组合为唯一标识。在 IP&Port 的选择上,我们初步归纳为2个目标:

  • 高可用:尽快的找到可用的 IP&Port 资源

  • 高性能:优先使用质量好的 IP&Port

  • 负载均衡:IP的排序算法不带任何偏向因子,避免造成人为的负载不均衡

在微信早期的排序选择上,我们使用了一种随机组合的排序算法。即将 WXDNS or DNS IP 列表与 Port 列表进行组合,组合后的结果进行随机排序。在随机排序的结果列表中,使用下述步骤进行排序:

  1. 选取IP1+Port1;

  2. 选取IP2+Port2,尽量使得IP1与IP2不相等,Port1与Port2不相等;

  3. 选取IP3+Port3,尽量使得IP3与IP1、IP2都不相等,Port3与Port1、Port2都不相等;

  4. 以此类推,形成常规列表。

同理,使用 Auth IP or Hardcode IP 列表与 Port 列表的组合,我们按照相同算法生成另外一份保底列表,并将保底列表排序在常规列表的后面,从而组成完整的 IP&Port 列表。随机组合排序的算法有着以下的特点:

  • 高性能:每一次尝试都尽量使用完全不同的资源,使得能最快的发现可用资源;

  • 初始随机,从而避免列表顺序的固化;

  • 保底列表在最后,形成最后的保护屏障;

  • 在不同的网络下,维护着不同的资源列表。

在使用中,如果发现 IP&Port 访问失败,则在列表中 ban 掉该资源。这里有个小优化,即当 IP1&Port1 的上一次访问成功时,需要连续失败2次才 ban 该资源。目的是为了减小偶然的网络抖动造成的影响。

随机组合排序算法的设计初衷,是为了以最快的速度尝试不同的资源组合,从而快速寻找到可用的资源。然而,在微信的实际使用中,却发现这种算法存在着诸多的问题。例如:

  • 网络不可用或网络较大波动情况下,列表被ban的速度较快;

  • Auth IP or Hardcode IP 列表太容易被访问到:随着常规资源陆续被ban,保底资源总是会被访问到,造成对保底资源的访问量大。保底资源是为了微信服务这不符合保底资源的设计初衷。

  • 当引入复合连接策略后,IP资源不足。这是因为 ban 的策略简单粗暴的丢弃失败的 IP,导致 IP 资源越来越少;

  • 每次缓存超时或列表轮空后,对于新列表没有经验信息可用

在随机组合排序算法的基础上,为了解决遇到的新问题,微信使用了新的“以史为鉴”的算法。

IP&Port 排序算法(二):以史为鉴

由于复合连接的引入,在每次复合连接的尝试中,微信可以伪“并发”的对N个 IP&Port 进行 connect(微信中目前N=5)。简单的ban丢弃的策略会使得 IP 资源越来越少。 针对这个特点,我们对IP&Port算法进行了以下修改:

  • 初始资源列表分为两类列表:常规列表,保底列表,分别使用方案(一)随机组合排序算法生成初始顺序;

  • 对每次复合连接使用的列表,规定5个资源的组成是4个常规资源+1个保底资源,并且保底资源在最后(完全无法获取常规资源的情况除外)。这种资源组成方式一方面解决了“保底资源太容易被访问到”的问题,一方面也保障了保底资源的作用;

  • 在不同网络中,分别记录每个 IP&Port 的使用情况,并根据使用记录进行评分、排序;

  • 区分连续记录:对每个 IP&Port 的更新,10秒内的连续成功或失败,不进行使用情况的记录。这种处理方式一方面是为了避免网络不可用或网络出现较大波动时,IP资源被过快的错误标记;一方面也避免失败历史被快速的覆盖;

  • 最近的8条使用记录中,如果有超过3条失败记录,且最新一次失败记录时间为10分钟内,则本次排序ban该记录。这种处理方式的目的是避免历史分数较高的 IP&Port 在突然出现故障时很难被排序算法排除的问题;

  • 无历史的记录使用随机评分排序。

通过上述方法,我们保证了保底资源不会被轻易访问到,解决了列表被快速标记的问题,同时也保证了历史记录好的资源在出现故障时也能被快速替换。

IP&Port 排序算法(三):遗忘历史

“以史为鉴”的方案在微信中使用了一段时间,看起来运行良好。直至某一天,微信的部分服务集群出现了故障。虽然微信客户端快速的切换到可用的服务器资源,但当故障服务器恢复后,微信客户端却迟迟没有分流到已恢复服务的集群,导致部分微信服务器负载过高,而部分微信服务器却负载较低的情况。通过分析,发现“以史为鉴”的排序方案存在着一些问题:

  • 初始阶段排在前面的资源容易获得较多的成功记录,从而分数始终维持在较高的水平;

  • 出灾情况下,故障机器由于有失败记录,使得很难获得“被原谅”的机会,从而也很难更新使用历史;

  • 采用了无历史记录随机评分,破坏了原有的“相邻记录尽量不相同”的随机性设计;

因此,好的 IP&Port 排序算法,不仅应该快速的发现可用的资源,使得在出灾情况下能快速的响应,同时,也应该具备一定的“遗忘性”、“容灾性”,使得灾情恢复后能较快的发现“灾情恢复”这一事实,并且进行重排序,使得服务器资源得到更合理的使用。在综合考虑“以史为鉴”和“遗忘历史”后,新的 方案具有以下特征:

  • 内存历史、文件历史双层记录历史:反映资源使用的近期情况及历史情况;

  • 初始化状态:每次进程重启或网络切换后,从文件历史中“压缩”出内存历史作为初始状态;

  • 旁路检测:额外更新历史的渠道,更有助于挑选高性能的资源,并且帮助“灾情恢复”的资源获得使用的机会;

  • 文件历史的遗忘性:文件历史每24小时强制刷新,避免高分数的记录长期“占有”队列;

  • 无历史、有历史的混合排序。

具体实现查看 Mars 源代码中的 simple_ipport_sort。

总结

连接是信令传输的前提,一个简单的连接操作蕴含着不少的优化空间。在连接超时的选择上,我们要兼顾性能与可用性,过短的连接超时可能导致弱网络下的低可用性,但过长的连接超时又影响用户体验。在 STN 中,我们结合系统本身的 TCP 连接重传特性,进行了相应的设计考量。即使如此,串行的连接方案仍然不能满足高性能的需求。并发连接的方案获得高性能的同时,也带来了服务器负载剧增的损失。综合考虑下,STN 使用了“复合连接”的方案,获得高性能的同时,也保证通常情况下的服务器低负载。

IP&Port 是连接的最重要资源,IP&Port 的排序选择是连接过程的重要部分。在微信的实际使用中,我们依次使用了“随机组合”、“以史为鉴”、“遗忘历史”三种方案,综合的考虑了查找性能、移动互联网的不稳定性、容灾及容灾恢复等。

连接超时、连接策略及 IP&Port 排序是连接的是三个重要组成部分,相关的方案也随着微信实践在不断的发展中。相信在不同的应用场景中,我们可能会遇到更多的不同问题及需求。随着Mars的开源,也能有机会参考、吸收其他应用中的实战经验,使得网络优化持续的深入。


关注 Mars , 来 Github 给我们 star 吧

https://github.com/Tencent/mars

查看 Mars 项目源码,请点击[阅读原文]。

https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286458&idx=1&sn=320f690faa4f97f7a49a291d4de174a9&chksm=8334c3b8b4434aae904b6d590027b100283ef175938610805dd33ca53f004bd3c56040b11fa6#rd

微信终端跨平台组件 Mars 系列(三)连接超时与IPPort排序相关推荐

  1. 整理:微信终端跨平台组件 mars 系列(一) - 高性能日志模块xlog

    学习网站:微信终端跨平台组件 mars 系列(一) - 高性能日志模块xlog 其中涉及知识: 垃圾回收机制 垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象. ...

  2. 微信终端跨平台组件 mars 系列(一) - 高性能日志模块xlog

    前言 mars 是微信官方的终端基础组件,是一个使用 C++ 编写的业务性无关,平台性无关的基础组件.目前已接入微信 Android.iOS.Mac.Windows.WP 等客户端.现正在筹备开源中, ...

  3. 【腾讯Bugly干货分享】微信终端跨平台组件 mars 系列(二) - 信令传输超时设计

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/9DJxipJaaBC8yC-buHgnTQ 作者简介: ...

  4. 微信终端跨平台组件 mars 系列(二) - 信令传输超时设计

    前言 mars 是微信官方使用 C++ 编写的业务性无关.平台性无关的终端基础组件,目前在微信 Android.iOS.Windows.Mac.Windows Phone 等多个平台中使用,并正在筹备 ...

  5. [转]微信终端跨平台组件 mars 系列(二) - 信令传输超时设计

    转至 http://mp.weixin.qq.com/s/PnICVDyVuMSyvpvTrdEpSQ  感谢作者,支持原创 前言 mars 是微信官方使用 C++ 编写的业务性无关.平台性无关的终端 ...

  6. 微信终端跨平台组件 Mars 在移动网络的探索和实践

    视频观看地址:http://edu.csdn.net/course/detail/4385 在 IM 方面,弱网络一直是横亘在应用开发者面前的一大问题,微信终端跨平台网络基础组件 Mars 团队基于微 ...

  7. asp.net 安装element ui_Vue组件库系列三:打造属于自己的 UI 库文档(新版本的方案)...

    上一章介绍了element-ui老版本中md文档构建过程,如果大家认真看了,就会发现在.md文档中还是有一些重复的部分,怎样使文档看着更加简洁,操作更加方便,饿了么团队下了很大的力气做了探索,现在我们 ...

  8. 组件库系列三:编写组件库文档

    文章目录 vuepress介绍 创建文档工程 配置运行指令 vuepress浏览器自动更新 下载插件和依赖 npm/yarn link docs文件夹 .vuepress文件夹 可收缩代码块 效果展示 ...

  9. Python开发微信公众号后台(系列三)

    原文链接:点击打开链接 摘要: 之前有主流的小黄鸡机器人,但是没找到官方的网页版.(小黄鸡提供付费 API )尝试使用http://www.niurenqushi.com/app/simsimi/(虽 ...

最新文章

  1. SpringBoot+flowable快速实现工作流,so easy!
  2. 035_使用Enumeration遍历Vector元素
  3. babymips(上) 寒假逆向生涯(14/100)
  4. ftl保存成html中文是乱码,解决freemarker生成静态页面时乱码问题
  5. Java查看字节码工具
  6. python接口自动化(四十一)- 发xml格式参数的post请求(超详解)
  7. 北京互联网地域歧视链
  8. 建站之星检测不到mysql_建站之星安装,建站之星安装教程 | 帮助信息-动天数据...
  9. 过麦--回忆里面的山东
  10. 超细!在浏览器输入xxxhub 回车之后发生了什么?
  11. 基于51单片机智能大棚智能花盆浇水浇花灌溉补光散热原理图Proteus仿真
  12. cobol text文件的入出力
  13. Python3基础知识
  14. 【话费充值平台】话费充值平台接口设计
  15. IntelliJ IDEA 之 配置JDK 的 4种方式
  16. RAM和ROM是什么?RAM和ROM其实都是内存
  17. 论异步编程的正确姿势:十个接口的活现在只需要一个接口就能搞定!
  18. VIM 的 commentary 插件
  19. Maven Dependencies missing jar 解决方法
  20. 中点法 matlab,中点法解常微分方程(组)

热门文章

  1. 关于 mybatis-generator自定义注释生成 使用DefaultCommentGenerator重写来完成
  2. cas sso单点登录 登录过程和登出过程原理说明
  3. 单元测试(Unit Test)学习
  4. SAP BW增量队列深入研究
  5. 根据工作年限预测工资python代码实现
  6. Gauss-Newton算法学习
  7. 【Python】判断列表中是否存在一个数
  8. 科大星云诗社动态20210118
  9. 吴恩达《Machine Learning》精炼笔记 5:神经网络
  10. 图像凸性检测函数convexityDefects在Python2.7下使用opencv3.0的问题