双 11,不仅是广大商家和消费者们的饕餮盛宴,同时也是各家电商公司的技术研发力量的实力体现。2016 年苏宁易购双 11 首小时订单量增 287%,移动端占比 86%。为了给用户带来更好的体验,苏宁易购移动端团队一直以来追求 “极速、稳定、安全” 的目标,围绕这一共同目标,我们以小团队作战、敏捷的开发模式,开展了一系列工作,其中主要包括:
(1)客户端架构解耦:为了保障 App 性能的稳定,适应业务的快熟发展及模块化,我们完成了客户端架构的改造,实现了系统横向和纵向解耦、动态化、组件化等。
(2)客户端性能优化:为了追求更好的响应性能,同时兼顾移动端安全性,我们进行了图片渲染、静态资源上的全方面加速,并且对网络链路进行了深度的优化。
(3)监控体系:一切以数据为依据,实现了移动 App 监控和数据分析系统。
1. 客户端架构的改造
苏宁易购客户端从 11 年开始做起,随着移动技术的发展,架构也发生了很大的变化。
老架构的问题主要是代码耦合问题:
(1)横向代码耦合——业务代码之间没有明显的模块边界,哪里需要用到其它的功能就直接包含一下其它模块业务的头文件,生成所需功能的对象来直接使用,模块之间调用呈网状结构。
(2)纵向代码耦合——由于没有合理的进行纵向分层,很多基础功能代码里包含了大量的业务功能,业务代码与基础功能代码没有严格的逻辑界线。
图 1. 网状引用结构:简单的逻辑变的复杂且混乱
1.1 客户端分层模型,解决纵向耦合
首先,我们对客户端进行了分层处理。客户端在纵向上划分成三层:业务层、服务层、基础层。在横向上,我们引入了功能模块的概念,将客户端抽象为一个个的模块所组建成的一个整体。分层思想如图:
图 2. 新架构下客户端分层模型
1. 基础层:对基础代码做了很多整理,把基础代码里所有的业务代码都删除掉。另外我们自己重新封装了数据库、UI、网络、自定义控件、常用的第三方库、常用的自定义宏等内容,它们构成了我们的基础层。
2. 服务层:在基础层成型以后,我们把一些通用的功能进行了封装,例如:埋点、url 管理、网络状态检测、缓存管理等。它们使用基础层来搭建自己的功能,为业务层提供服务。
3. 业务层:对所有的业务功能进行模块划分,大致划分为首页、购物车、嗨购、会员、搜索等模块,所有模块都使用统一的真实目录结构,将模块从逻辑上和物理上都归于一个目录下,不同的模块之间从物理上进行目录隔离。
1.2 中介型引用结构,解决横向耦合
解除横向耦合的原理就是将之前直接的网状引用结构改变为间接的中介型引用结构。加入一个模块管理器,所有的模块把需要提供给其它模块调用的功能,都预先在管理器中进行注册。在使用时,使用者向管理器进行申请,即可以得到对应的功能对象。在这种结构下,模块的插拔对其它功能影响非常小,耦合也非常小,如图。
图 3. 中介型引用结构:调用逻辑清晰
在具体的实现方式上,目前常用的解耦方式大致有 protocol 方式和 url 方式,我们两种都有用到:在对服务层的引用上,我们更多使用 protocol 方式;在对某些业务模块的调用上,我们更多使用 url 方式。
protocol 解耦:protocol 方式是 id 的一种灵活使用,体现了面向接口编程的思想,模块要导出的功能不是一个具体的头文件,而是用 protocol 来表示功能的接口的.h 文件。使用者在使用时得到的是一个 id,但可以调用具体的协议方法来完成自己需要的功能。另外,由于得到的是 id,所以功能模块也实现了接口与实现的分离,例如根据不同的情况,被调用者可以通过 id 返回不同的实现对象,而使用者无需做任何变动。目前我们在埋点、定位、分享等服务层上使用的是这种方式的调用。
动态化和 url 解耦:随着移动端架构的发展,出现了动态化的概念,即是把客户端的部分功能在原生和 H5、Weex、React Native 等实现方式之间进行灵活的切换。这样客户端可以进行动态的发布、降级、切换实现等功能而不用重新发版本。易购客户端里我们主要采用了 url 的方式来支持这种动态化。我们把需要动态化的原生页面按业务线 / 模块 / 页面名的形式定义它的 url,在页面的类对象生成后注册一个 block 到 url 跳转管理器里,这个 block 会在使用者调用页面 url 时被执行,生成一个页面对象进行返回和展示,这样就得到了 url 化的原生页面跳转。同理,H5、Weex 页面在客户端也需要一个原生页面作为容器来加载它们,因此我们把这两个容器做为单独的模块,并且使用来进行跳转。如图:
图 4. url 解耦模型
我们在 url 跳转管理器上做了一些操作,它可以下载服务端的配置列表,根据配置列表来改变当前的跳转方向,跳到不同的容器上来进行展示,由此我们也实现了通过服务端的配置来达到客户端实现切换的目的。
2. H5 容器的性能优化
在 H5 容器层面,我们也进行了多项的优化来提升页面的加载速度,并且减少页面的下载数据量。
2.1 静态资源预加载
我们把前端页面进行了动静分离,把一些页面会使用到的静态资源预先下载到客户端。把容器的资源请求进行拦截,将本地已经下好的文件内容返回给它进行展示,这样就省去了即时网络通信的时间,提升了页面的加载性能。
2.2 WebP 图片格式的使用
H5 容器的图片我们全部使用 WebP 格式。我们把容器对图片的请求进行了拦截,用 native 代码重新发起一个 WebP 图片的请求,在请求成功后 native 把 WebP 图片转换为 png 的数据返回给 H5 容器进行展示,使用 WebP 后我们页面需要下载的图片数据量减少了一半以上。
2.3 容器渲染性能优化
为了提升容器的渲染性能,并且解决 H5 页面长列表内存不断增大等问题,我们引入了 Weex。Weex 通过将 js 转化为 native 代码渲染,有效的提升了页面的渲染性能,Weex 提供的长列表实现机制通过 native 的单元格重用也很好的解决了长列表问题。我们把 H5 容器和 Weex 容器进行了动态化处理,通过 url 映射可以任意的在两者之间进行转换。
3. 网络链路的优化
移动网络环境与有线网络存在着很大的差异与区别。因此,移动网络对我们的性能策略优化提出了新的、独特的要求。在我们未作优化前, 2G/3G 网络下一个简单的资源(下载用时在 0.1s)请求在网络传输中可能都会耗费很长的时间(1.345s)。
图 5. 下载用时只占端到端时间很少的一部分
另一方面,随着近年来苏宁易购移动端的不断发展和引流,用户数持续递增,网络劫持的问题也逐渐严重起来,比如:页面加载白屏,APP 下端弹出小广告,甚至无法提交订单。保证移动网络下,用户隐私和数据传输安全性也成为了我们这些年来的重点工作之一。
为了解决性能与安全两方面的问题与瓶颈,我们采用了以下技术方案:
3.1 使用 HTTP/2——链路复用,加速传输
HTTP/2 采用二进制帧格式而非文本格式进行传输,突破了请求并发数的限制,能够实现完全的多路复用,使页面的传输效率和建连复用效益最大化。我们在移动端部分促销页面使用了 HTTP/2 传输,在 3G/4G/Wifi 网络下,其效果是令人欣喜的。
图 6. HTTP/2 加速效果与 HTTP/1.1 对比
3.2 HttpDNS 寻址——DNS 解析安全、精确、快速
HttpDNS 是使用 HTTP 协议向 DNS 服务器的 80 端口进行请求,代替传统的 DNS 协议向 DNS 服务器的 53 端口进行请求, 绕开了运营商的 Local DNS,从而避免了使用运营商 Local DNS 造成的劫持和跨网问题。
在实现上,我们会维护一张域名列表,将域名解析值预取到客户端本地的 DNSCache 中,预取优先调用 HttpDNS 接口,如果获取不到数据,则直接从 localDNS 取数据,并设置一个独立的线程作为定时器,根据 TTL 过期时间来检查 domain 是否需要更新。
HttpDNS 为我们带来了安全和性能上诸多的收益。
3.3 HTTPS 代理——保证传输链路的绝对安全
HTTPS 能够给用户带来更安全的网络体验、更好的隐私保护。然而,HTTPS 增加了 TLS 握手环节,再加上应用数据传输需要经过对称加密,对性能提出了更大的挑战。作为一个好的架构,一定要均衡安全和性能两方面,如果让天秤向任何一方倾斜过多,都会影响最终的用户体验。
因此,结合服务端,我们做了很多优化工作:
(1)基于链路的优化
建立连接的延迟体现在每个 SSL 连接上,因此尽早完成 SSL 握手是优化工作的重点。对于普通的图片资源和文档请求,我们在 CDN 上完成 SSL 卸载;对于涉及用户信息的受限资源和脚本,我们在内网防火墙上完成 SSL 卸载。
(2)基于 SSL 协议的优化
在 SSL 协议优化方面,我们在服务端支持 ALPN 协议,使用适合 Forward Secerecy 的加密算法,开启了 False Start,客户端在第二次 SSL 握手的同时就可以发送应用数据,减少了一次 RTT 时间。
另外,我们启用了 Session ID 和 Session Ticket 的会话恢复技术,对同一个用户,在多个连接间共享协商后的安全密钥。并将 Session 信息缓存到 Redis 中,通过一台中心服务器统一管理,解决了集群情况下 Session ID 无法共享的问题。
(3)基于证书和加密套件的优化
在证书链优化方面,由于 TCP 初始拥塞窗口的存在,如果证书太长可能会产生额外的往返开销,移动端采用的证书链都是“站点证书 - 中间证书 - 根证书”三级的,同时服务端只发送站点证书和中间证书,根证书部署在客户端,将证书链控制在 3KB 以内。为了避免不必要的证书过期校验,我们在服务端开启了 OCSP Stapling。
在加密套件的选择上,优先选择 ECDHE-RSA-AES128-GCM-SHA256,综合安全、性能和开销,是最优的选择。
当然,我们还实现了 H5 内容压缩与图片的智能适配,针对 HTML、JS、CSS 等格式进行无损压缩;根据终端网络制式智能调整图片分发尺寸,提升终端展现性能。
经过我们以上的改造,从 10 大城市 3 大运营商上采集的数据分析,客户端访问速度提速明显, Android 大概提速一倍,IOS 也有 60% 左右的提高;错误率也明显减少了 80%。
图 7. 安卓的加速效果对比
图 8. IOS 的加速效果对比
4. 移动 App 性能监控系统
监控是系统的眼睛,尤其是针对线上电商系统。监控能让我们知道系统的运行状态,在我们进行优化时,监控能告诉我们优化的效果,在系统出问题时,也能让我们知道系统发生了什么。可以说,一切都是建立在完善监控的监控体系下的,因此,最后咱们花点时间来聊一聊易购移动端的监控手段。
移动端监控体系既不能只看客户端,也不能只看服务端,必须将客户端、网络,服务端相打通,形成完整端到端的调用链,才能让问题无处遁形。
图 8. 苏宁易购移动端端到端监控体系
我们开发了专门用于 App 监控和数据统计分析系统。能够从五个维度:移动应用崩溃、移动应用错误、移动应用请求响应时间、移动应用交互性能、运营商网络响应时间来帮助我们分析问题。
图 9. 苏宁易购移动端平均响应时间监控
5. 结语
路漫漫其修远兮,吾将上下而求索。经过了这些年来的努力,客户端的架构升级让易购 App 更加稳定的运转起来,移动端加速各项技术的应用让用户体验更加的极速和安全,再通过监控系统的全方位 24 小时实时监控和保障,真正做到了我们的良苦用心,用户的畅快体验。然而,追求极致的道路是没有终点的,并且前行的过程中将永远有挑战,易购移动端团队将继续努力下去!
本文由苏宁易购移动端技术团队撰写。苏宁云商最早成立的完善技术团队之一。面向苏宁易购数亿用户群体,目前我们拥有一整套完备移动研发能力,在移动框架、性能优化、自动化测试、移动 app 监控及前端容器框架等各个方向已拥有近百位移动技术专家。苏宁易购双 11 首小时订单量增 287%,移动端占比 86%;我们致力于整合苏宁线上线下渠道,立足于移动开发技术,扩展移动端优秀产品,为用户提供极致移动体验。苏宁易购移动端技术团队将会在移动端动态化,容器化,服务化等多方面深耕细作,并将会和广大的同行朋友们一起,努力把移动技术越做越好。
评论