我们经常看到App中会包含有一个服务器列表文件,开发人员和测试人员可以随意切换到任意服务器进行开发测试工作。
这只是服务器列表文件的一种功用,是给开发和测试人员使用的,为此我们需要为App设计一个后门,由他们手动进行切换,相关内容请参见2章节。
服务器列表文件还有另一种作用,就是由App自己来决定选用哪个服务器作为MobileAPI服务器。
众所周知,App发起MobileAPI请求到接收到数据,这个过程所耗费的时间由3部分组成:从App到达服务器的时间,服务器处理的时间,从服务器到App的时间。
其中,从App到达服务器的时间,加上从服务器到App的时间,我们称为来回走路的时间。对于2G、3G、4G和WiFi用户,因为网络环境的不同,来回走路所花费的时间大相径庭。
于是我们会准备多台服务器,可能是放在全国各地,也可能是分别接入电信、移动或联通的专线。这些服务器有可能是配置相同的,也有可能是由若干高配和低配组成。我们把这些服务器的域名罗列在App的服务器列表文件中,如图9-11所示:
<Servers> <Server key="s1" type="3G" url="http://login1.company.com/"> <Server key="s2" type="3G" url="http://login2.company.com/"> <Server key="s3" type="4G" url="http://login3.company.com/"> <Server key="s4" type="4G" url="http://login4.company.com/"> <Server key="s5" type="2G" url="http://login5.company.com/"> <Server key="s6" type="2G" url="http://login6.company.com/"> <Server key="s7" type="WiFi" url="http://login7.company.com/"> <Server key="s8" type="WiFi" url="http://login8.company.com/"> </Servers>
图9-11 服务器列表的配置文件
接下来,我们会让MobileAPI提供一个接口服务A,该接口不需要任何入参,直接返回1这个结果。这样就确保了App从发起MobileAPI请求到接收到数据的时间,就是来回走路的时间。
在App第一次启动的时候,我会让App根据当前的网络情况,遍历服务器列表文件中的域名,访问这些域名下的接口服务A,计算出哪个域名的访问速度最快。同一个域名只访问一次,得不到准确的数据,一般而言,我会调用10次后取平均值,来作为参考标准。
当网络环境发生变化的时候,也要把上述这个操作执行一遍,测算出该网络环境下哪个域名的访问速度最快。为了避免频繁做这个事情,我会设置一个缓存,记录最后一次测算每种网络环境的时间,以确保1个小时之内不会测算2次。
一旦测算出当前网络环境下哪个域名的访问速度最快,那么接下来1个小时内,访问MobileAPI就会使用这个域名了。1个小时后,我们将在App后台线程再次发起测算工作,重新选择最佳的域名。
上述这种解决方案,能帮助用户选择最快的MobileAPI服务器,但是由此会导致另一种负面效果,App一厢情愿的认为网络环境好所对应的服务器访问速度也最快,于是这台服务器的CPU会迅速被占满,无法处理后续接踵而至的网络请求。所以,我们要将服务器的处理能力划分为优良中差四种级别,并在App发起测评请求(调用MobileAPI接口服务A)的时候把这个值返回给App,当达到中(CPU占用60%)这个级别时,即使网速很快,也不能采用这个域名对应的服务器。
当大多数公司还在纠结于如何能更好提高MobileAPI的性能时,已经有公司开始抛弃Http+JSON,开始走Tcp+ProtoBuf的路线了。
Tcp是长连接,ProtoBuf则是基于二进制的协议,可读性差但是体积小。
这里我不讨论Protobuf协议中的required、optional或repeated关键字,也不讨论Android和iOS大小端对齐的问题。这些都属于App和服务器能使用Protobuf进行通信的第一步。
我只说三点,一是工具,二是架构,三是性能。
1. 工具
我们需要做一个工具,能帮助开发人员把Protobuf协议自动转换为Android或iOS的实体类和相应的方法。使用该方法就可以发起一次Protobuf请求并获取到服务器返回的实体数据,这将极大的加速开发人员的工作效率。
2. 架构
传统MobileAPI返回Http+JSON,当我们改为使用TCP+Protobuf的时候,之前的JSON仍然要维护,因为我们要给自己留一条后路,一旦服务器上的TCP+Protobuf扛不住了,要立刻能切换回Http+JSON。
那么问题就来了。难道我们要为App同时维护两套MobileAPI逻辑吗?
当然不行,一种理想的设计方案如图9-12所示:
图9-12 新的MobileAPI架构设计
但是反观我们的MobileAPI代码,却不是这样的,你会发现业务逻辑和JSON绑的很紧,往往是从后台取到数据就立刻填充到JSON字段中了。我们需要重构,把取数据的业务逻辑和返回什么样的数据(JSON或Protobuf)剥离开,最好能拆分成3个项目,最差也应该是在一个项目中拆分为不同的目录。这样业务逻辑如果有变动,只需要修改一个地方,然后在JSON或Protobuf中追加字段。
生成器模式(Builder)这时候就能派上用场了。它能很好的弥合Protobuf和JSON这两种数据格式的差异性。
我们把业务逻辑、JSON生成器、Protobuf生成器框在一起后,下一步要面临的就是以Http还是TCP的协议返回给App数据了。Http协议由Header和Body两部分组成,都需要填充数据,其实我们也可以在TCP协议中定义Header和Body,把之前填充在Http的Header中的版本信息、Cookie传递过去。
策略模式(Strategy)可以用于指定使用Http协议还是TCP协议。
3. 性能。
Tcp要解决的技术难点就在于,服务器上长连接数量多会导致服务器性能压力,如果解决不了,用起来还不如Http。
于是我们采取TCP长连接和短连接混合的模式。
TCP长连接就是每个App客户端都是作为一个连接,保存在服务器的长连接池中。但是这个池子中的长连接数量是有上限的,所以我们持续清理池子中长期不使用的长连接,比如说几分钟内不使用就关闭这个连接,大不了以后再连上来。
资源是有闲的,对于日活几十万的App而言,我们要保证服务器至少能支撑这几十万个长连接。如果超过了这个池子的上限,那么我们就要使用短连接作为补充。短连接就是连接后完成一次调用就把连接关闭了。
服务器要根据当前长连接池的情况,来决定建立长连接还是短连接。如果TCP长连接和短连接都没有资源了,那就切换到Http,这其实也是一种短连接。
网络请求的场景不同,也会影响TCP长连接和短连接的选择。比如说xmpp聊天,就比较适合TCP长连接。
用户的活跃度,也可以作为选择TCP长连接还是短连接的依据。活跃用户往往会长时间使用App,频繁发起网络请求,这时候要使用长连接。对于那些偶尔打开App随便点一点看一看的用户,可以先使用短连接。等用户发起网络请求的次数超过某个筏值时,就切换到长连接。
网络环境是影响App选择TCP长连接还是短连接的又一个因素。对于WiFi环境,网络请求普遍比2G、3G和4G要好。接下来的策略有两种,
不得不说的是,WiFi不一定快过4G,甚至是3G和2G,所以上述策略有不准的情况。