新 Uber 司机端是如何克服网络延迟问题

Carbon: Optimistic Mode article feature image
Carbon: Optimistic Mode article feature image

本文是 Uber 的客户端工程师团队讲述了如何开发最新版本司机端系列文章中的第三篇,该系列代号 Carbon ,是咱们共享出行业务的核心。包括其它功能在内,Uber 司机端使得超过 300 万名司机能够查看费用、里程以及收益状况。2017 年咱们结合司机的反馈开始对司机端进行从新设计,并在 2018 年 9 月份启动了该项目。git

城市建筑和无线数据技术的竞争意味着在城市中存在一些手机没有信号的黑色区域。这种黑色区域景区更为常见,致使网络质量和阻塞程度频繁的变化。这些问题尤为影响着那些接送乘客的司机们。github

能够举一个合适的例子来讲明这种问题。假设一个司机到达了很是拥挤的班加罗尔机场终点。乘客想支付现金,司机须要在应用里面操做完成订单来查看最终的金额。把车停在路边,司机端却没法联网。乘客匆忙赶飞机,不能联网就意味着司机就不能结束行程并查看最终的金额。司机可能会继续开下去,增长了额外的时间,也可能增长了行程花费,给司机和乘客都带来了不便。后端

为了处理这种网络覆盖漏洞和预防这类事件的发生,咱们提出了 —— 乐观模式。新版本的司机端能够离线操做,这样司机就能够在没有网络的状况下用最后一次服务端的预估数据来结束行程。乐观模式下司机端能够任何网络下正常工做,极大的提升了司机和乘客的体验。服务器

乐观模式组件

咱们以前的司机端版本中支持一些离线能力来收集失败的请求,一旦网络恢复就会上传到服务器进行整理。虽然这种功能有助于预防一些显示错误,可是不能智能的更新应用状态,不能将多个功能堆积在一块儿,也不能夸会话持久化状态。咱们为新版本的司机端开发了下面这个组件来处理这些问题。网络

乐观请求

司机端的任何组件均可以经过提交一个乐观请求来开始流转。一个乐观请求可以序列化储存到磁盘,对于一个普通的网络请求来讲占用的内存很是小,而且每个乐观请求都对应一个乐观转换。app

乐观转换

乐观模式的核心是转换,换句话说,操做转换一个对象从当前状态到乐观状态,也就是,从服务返回的预期结果。转换还可以堆积,一个对象能够通过屡次转换。举一个例子来理解下转换:想象一个类Counter有一个属性count。咱们能够实现一个转换来增长count属性的值。框架

Carbon: Optimistic Mode article figure 1

图一:在这个简单的例子中,Counter对象每通过一次增长转换,count属性值就会增长一。ide

根据业务需求转换既能够是简单的也能够是复杂的。每个乐观请求都关联一个转换,转换会根据乐观请求返回一个最终的_乐观状态_。当数据从服务端返回时用户是无感知的,这种方式提供了一种平滑的过渡方案。优化

当客户端提交一个乐观请求时,关联在请求上的转换就会立马生效,应用进入乐观状态,从而完成请求。乐观状态会一直被保持直到收到服务端的真实状态,而后同步应用和服务端。ui

Carbon: Optimistic Mode article figure 2a

图 2-1: 普通的计数请求失败

Carbon: Optimistic Mode article figure 2b

图 2-2: 在无网络的状况下乐观模式使用转换及时更新数据状态,未来有网络的状况下和服务端进行同步。

乐观流

咱们整个应用都在使用 RX streams 传递数据。应用的每一个功能都会随着已发布数据流的状态改变做出响应。这种机制使咱们可以使用相同的流轻松地将乐观变换应用于对象的最新状态。为了得到乐观状态,咱们结合了数据最后的状态和可用的转换。在将数据发布回流并由功能使用以前,数据已经应用了每一个转换。随后业务只需简单的根据数据的乐观状态做出响应。

依赖请求

同时也存在一些请求依赖于乐观请求的完成。例如,甚至在后端不知道行程已经开始的状况下发送一个结束行程的请求是不合理的。当咱们在等待乐观请求完成的时候,这样的依赖请求将会被放入队列一段时间。若是周期过长,咱们会结束这个请求,通知用户网络错误。

设计挑战

咱们在这个设计中遇到了一些挑战。咱们想要支持多个堆叠的乐观请求,容许在没有网络的状况下完成多个步骤。因为和服务器不一样步,咱们还须要处理错误地进入乐观状态而且必须回滚到先前状态的状况。确保咱们可靠地向司机展现最准确的状态须要进行屡次迭代,并持续优化。

兜底转换

乐观模式开启的状况下,应用程序可能会在乐观请求完成以前收到其余的网络数据。

Carbon: Optimistic Mode article figure 3

图 3: 在这个场景中,咱们在收到服务器最新的状态以后又进行了乐观转换。

咱们继续拿上面用到的计数器的例子来讲。应用程序使用增长变换把最终的值变成了 2。然而,这个值尚未和服务端同步。在这期间,收到的其余的网络响应可能仍是旧的值 1。乐观模式使用转换更新了这个旧值而且维护这个乐观状态。这就确保了应用程序不会在两种状态以前来回切换,避免给用户产生混乱的体验。

应用重启时如何存活

全部的乐观请求和最新的乐观状态一块儿被保存在磁盘里,因此它们可以在应用重启的时候得以保留。考虑这么一种状况,一些请求正在排队和服务器同步,可是用户却杀死了应用。在从新启动的时候,乐观请求和状态会从磁盘中加载。这容许用户在从新启动应用时处于相同的状态。乐观请求排队和服务器同步。

显示错误

咱们遇到的这个新功能的一个特殊问题是它如何显示错误。乐观模式的请求只应该因为后端中断而失败,而且结果应该是可预测的便于模拟。然而,实践中会出现错误。因为咱们使用乐观的流程服务用户,因此一个小错就可能带来很很差的体验。首先,应用程序的状态回滚到以前的乐观状态,不是用户所指望的状态,下个动做可能不太明显。其次,即便以前的状态可能已经无效了,咱们也须要用它来接收错误缘由来展现。为了处理这些问题,咱们在司机端里加入了一个全局处理错误信息的框架,它能够调用内部弹窗框架。

请求出错的状况老是不多见的。对于常常发生的错误,好比行程过短,咱们在手机端上实现了检查,以便更好的处理。

节省时间

对司机来讲,咱们在开始和结束行程上利用乐观模式节省了大量的时间。咱们常常能够看到在真实的网络请求完成以前行程已经开始几分钟的状况。截至 2018 年 11 月,咱们注意到平均每一个乐观操做节省大约 13.5 秒的时间。即便在新司机端的早期阶段,咱们天天累计节省司机的时间也超过了一年。

乐观模式的发展

在无网络的状况下可以正常运行的能力在 Ubers 的其余应用程序上面使用的也很是好。设计之初是为了加速开始和结束行程的速度,它还被整合到 Uber Eats 中功能中,当使用现金结算时,能够更快的结束。它还能用到相似于这种能够快速响应后续同步到服务端的业务中,好比对乘客或司机的评价,标记消息已读,和收集交付的指纹。

Uber 司机端系列文章索引

  1. 为何咱们决定重构 Uber 司机端
  2. 使用RIBs重构Uber司机端
  3. 新 Uber 司机端是如何克服网络延迟问题的
  4. Scaling Cash Payments in Uber Eats
  5. How to Ship an App Rewrite Without Risking Your Entire Business
  6. Building a Scalable and Reliable Map Interface for Drivers
  7. Engineering Uber Beacon: Matching Riders and Drivers in 24-bit RGB Colors
相关文章
相关标签/搜索