Uber:如何建立司機支持服務的簽到及預約系統

Uber的Greenlight Hubs(GLH)在全球擁有超過700個分支機構,為合作車主提供從賬戶和支付到車輛檢查和車主註冊等各方面的人工支持。為了給合作車主創造更好的體驗並提高客戶滿意度,Uber的客戶優先工程團隊開發的內部客戶支持系統,是一個通過GLH實現了更加簡化和快速的支持申請的解決方案。

客戶支持系統包含兩個主要功能:為我們的服務專家提供的登記隊列系統,以跟蹤合作車主進入GLH的情況; 和一個預約系統,讓合作車主可以通過Uber合作車主APP安排人工支持的預約。 這些工具自從2017年3月推出以來,已經改善了全球合作車主的的支持服務體驗。

向內部解決方案的過渡

隨著Uber的發展,我們之前的客戶支持技術在為合作車主提供最佳體驗上不能很好的擴展。通過開發我們自己的GLH客戶支持系統,我們提出了一個既適合我們的可擴展性和定製需求,又改進了現有基礎架構以支持新功能的解決方案。

開發我們自己的工具意味著我們可以:

    • 方便獲得客戶支持需要的信息:我們的登記系統可以讓客戶支持代表更加方便獲得那些解決合作車主關心的問題所需要的相關信息。這種整合有助於減少支持服務的解決時間和改善合作車主使用GLHs的體驗。

    • 合作車主交流渠道的聚合:Uber各種支持渠道(包括應用內消息,GLH自身和電話支持)的集中化意味著GLH專家擁有額外的上下文信息,在一個地方統一的解決合作車主的問題。

    • 為合作車主在GLH縮短等待時間:使用我們升級後的系統,合作車主可以通過安排預約來避免在高峰時段發生不必要的等待時間。

為了實現這些目標,為我們的內部客戶支持平臺開發了兩個新工具:登記隊列和預約系統。

更加無縫的登記體驗

通過在我們的客戶支持平臺之上設計和實現的實時登記系統,為合作車主提供了更加無縫的支持體驗。使用此係統,合作車主會與禮賓人員登記,然後禮賓人員會根據與其帳戶關聯的電話號碼或電子郵件地址找到合作車主的個人資料。

一旦合作車主登記,GLH專家會從該網站的隊列中選擇他們。合作車主隨後會在手機和GLH內的監視器上收到推送消息,告知他們已與專家配對。一旦合作車主與支持站在通知中指定的專家會面,合作車主就會退出登記隊列。

我們的實時登記系統還彙總了客戶信息,例如過去的旅行和支持信息,使我們的專家能夠儘可能有效地解決問題。

Uber:如何建立司机支持服务的签到及预约系统

圖1: 在GLHs, 與專家配對時監控提醒用戶

提供實時專家隊列

由於WebSocket協議支持低延遲的長連接,所以我們利用它通過後端發送隊列更新。Go,Uber許多後端服務選擇的語言,通過讓我們使用管道和協程技術更加方便的將實時更新傳輸給web客戶端。

儘管如此,在使用WebSocket過程中我們遇到了一些有意思的挑戰。為了使我們的站點隊列能夠實時工作,我們決定將特定站點的所有WebSocket連接和隊列寫入維持在一個固定的主機。這樣,當隊列中的一個登記或者預約被更新,所有相關的連接客戶端也會被更新。 在我們寫入並將WebSocket連接到主機之前,使用單個主機處理這些請求需要在應用程序層上進行分片。

我們使用了Ringpop-go,我們的開源可擴展和容錯應用層分片用於Go應用,這有助於配置分片密鑰,以便具有相同密鑰的所有請求都將路由到同一主機。對於我們的分片密鑰,我們使用了GLH站點ID,因此在同一個GLH上發生的所有登記都會轉到同一主機,並更新相關客戶端上的所有站點隊列。

Uber:如何建立司机支持服务的签到及预约系统

圖2: 我們的面對面支持體系結構利用擁有特定GLH的主機的前端WebSocket連接。 來自活動數據中心的GLH專家前端和移動客戶端的請求通過Ringpop進行分割,並分配給擁有給定GLH的主機。 來自非活動數據中心的請求會重定向到活動數據中心。 與個人支持相關的數據存儲在優步內部數據存儲的Schemaless中

實現跨數據中心的高可靠性

為確保我們的GLH軟件平穩運行,我們需要保證高可用性。為了做到這一點,我們的服務運行在多個數據中心,處理來自全球的請求。如果某個數據中心由於某種不可預知的原因(如中斷)宕機,該服務將自行恢復並繼續從其他數據中心運行。

鑑於我們使用WebSocket,在多個數據中心中運行該服務帶來了一系列困難。 如果數據中心出現故障,我們不得不重新考慮如何正確處理WebSocket。雖然Ringpop分片在跨數據中心運行良好,但由於每次主機離開或進入環時都會發送跨數據中心的請求,因此會增加延遲。

為解決WebSocket降級問題,我們配置了我們的系統,以便每個數據中心都有一個環; 這樣,如果具有相同唯一GLH ID的兩個請求命中兩個不同的數據中心,它只會更新我們承載站點隊列的數據中心中的站點隊列。 無論請求來自哪個數據中心,我們都會將所有請求轉發給固定的數據中心。如果數據中心發生故障,我們會將請求轉發給其他數據中心。 我們同時也會將與出現故障的數據中心建立的所有WebSocket連接殺掉,並與新的數據中心重新建立連接。

增加預約

為了減少在GLH的等待時間並確保我們在高峰時段提供充足的支持,我們推出了一項新功能,讓我們的合作車主提前安排GLH預約,只需在UberAPP上輕鬆點擊幾下即可。

Uber:如何建立司机支持服务的签到及预约系统

圖3: 我們的面對面支持預約安排流程使合作車主

可以輕鬆安排我們的Greenlight中心的預約

Uber:如何建立司机支持服务的签到及预约系统

圖4: 當合作夥伴的應用程序到達Greenlight Hub時,合作伙伴會在Uber合作伙伴應用程序中收到簽入通知.

儘管合作車主的預約安排很簡單,但是幕後還有大量的工作來保證流程儘可能的無縫。例如,GLH管理者可以隨時指定有多少專家在其中心工作,以確保他們的團隊不會超額預訂; 那麼當合作車主進入應用程序時,他們只能看到基於專家數量的可用預約數。例如,如果週二早上9點在某個的GLH只有四名專家正在工作,那麼該中心的管理者當時可以設置四個預約的能力,從而限制可用預約的數量。

當合作車主安排預約時,他們會出現在GLH的當天預約列表中。當合作車主到達預定的預約時間時,他們可以通過他們的app輕鬆登記,並通知分配給他們的專家,他們已經到達。構建我們的預約系統包括在後端實施調度系統,在移動設備上添加預約功能,併為我們的GLH管理者開發基於瀏覽器的日曆界面。

建立全球調度系統

受Martin Fowler關於經常性日曆事件的論文的啟發,我們決定使用核心日曆服務構建我們的日程安排系統,具體實現可用的時間間隔(簡化為日曆間隔),系統將這些時間間隔視為規則來處理這些規範。

在Fowler模型中,這些規則可以由GLH管理者指定和修改,從而允許更靈活的調度。由於調度系統通常有許多需要考慮的邊界情況,因此我們逐步構建調度系統以避免範圍模糊,併為每一步提供一個功能正常的系統:

  1. 我們的第一次迭代使用GLH管理者最初設定的營業時間,併為每個站點指定了全球三名專家的容量,使我們能夠慢慢推出測試版本的軟件。

  2. 我們的第二次迭代使用由GLH管理者設置的日曆間隔,允許他們間隔多久設置一次專家池容量。

  3. 我們的第三次迭代結合了現有的日曆時間間隔,但也允許GLH管理者設置GLH關閉時間(即非營業時間和假日)。

然而,由於Uber的國際影響力,我們很快遇到了時區相關的問題,並且由於系統的各個組件需要協調正在使用哪個時區的環境而加劇了這一問題,例如, GLH時區或合作車主的時區。 另外,我們需要考慮夏令時的變化。 為了解決這些需求,我們採用了以下規則:

  • 與主要後端服務API交互的所有客戶端均採用其所選GLH的時區。

  • 所有預約時間在我們的數據庫中都會保存為UTC +0時區時間。

  • 主要的後端服務有一個內部層來處理持久層和API層之間的所有時區轉換。這使我們能夠抽象出日曆邏輯並調用與日曆相關的內部方法,而無需擔心時區問題。


重要的是要注意時區,即UTC偏移,不作為GLH對象的屬性存儲。 如果是這種情況,那麼夏令時改變會導致先前安排的預約時間在任一方向偏移一小時。為了正確處理這個問題,UTC偏移量將根據每個GLH的物理座標進行動態計算。

時區邊緣的情況

在構建我們的調度系統時,我們遇到了一些關於時區的特殊案例。當我們的系統將“日曆間隔”轉換為當地時區時,出現了一個問題。 由於UTC和當地時間之間的時區變化(取決於相關網站的時區),日期可能不正確。 例如,11月20日5:00 am UTC時間實際上是太平洋標準時間11月19日的下午9:00。因此,重要的是我們不要對相關時間段的日期做出假設,並且在時區跨越多天時進行測試。

另外,當我們將GLH營業時間從UTC時間轉換為當地時間時,我們遇到了類似的時區問題。 我們在當地時間節省了我們的工作時間,因為沒有日期,我們沒有足夠的上下文讓我們將其用UTC保存。 例如,一個GLH在星期一從上午9點到下午9點可能導致UTC營業時間從週一下午5:00開始週二早上5點結束。 由於沒有日期,不清楚這些當地時間提到的小時是一週中的哪天。 因此,每當創建新的日曆時間間隔,我們都必須將開放時間從已存儲的本地時間轉換成UTC時間。 根據業務邏輯所在的位置,這些場景可能需要在Web和移動客戶端以及服務器端進行廣泛的測試。

在移動設備上使用日期時間庫

對於合作車主實際使用我們的調度系統,我們需要為移動設備構建新的UX。 這涉及到修改支持表單屏幕給合作伙伴除提交按鈕之外的選項以獲得幫助,以及幫助主屏幕顯示他們可能會有的任何即將到來的預約。

還有一些與特定活動相關的新屏幕:選擇附近的GLH預約會面,根據該會場的可用選項選擇預約的特定日期和時間,確認選擇以創建預約,查看詳細信息 預訂預約並取消預訂,並查看有關該網站的詳細信息,例如地址。

由於我們與日期和時間打交道,並且因為我們希望我們的服務器API返回結構化的數據(例如ISO 8601),而不是預先格式化的本地化字符串(即用戶的偏好語言中的日期)供我們展示,所以我們假設將使用java.util.Date標準。在這個標準中,Date和相應的日曆類在處理時區時有許多已知的問題,所以我們想要探索一下其他選項是否能工作的更好。 例如,Joda-Time標準(一種Java 8 API)聽起來很有趣,但它還不兼容Android系統這種廣泛用於合作車主的設備的系統。

我們最終發現了ThreeTenBP-- Joda-Time的後繼者,它將Java 8的時間和日期API引入Java 6和7。然而,以前在Android上使用ThreeTenBP的嘗試遭遇啟動問題。在啟動時,這些庫從磁盤加載時區數據庫信息,對其進行分析並將其註冊到庫中以供稍後使用。這個庫的特定於Android的包裝器以更友好的方式加載數據,但仍然存在阻礙應用程序啟動的非平凡磁盤操作。在低至中檔設備上進行測試時,這會使Uber合作伙伴應用程序的啟動速度減慢超過200毫秒。

我們嘗試以多種方式優化ThreeTenBP,例如,通過在不同的線程上執行實際的磁盤操作,以便Application.onCreate的其餘部分可以並行發生,並在最後加入線程,從而確保Uber合作伙伴應用程序可以安全地使用 庫。 我們也嘗試使用其他類似的庫,它們在啟動時嘗試少做或沒有IO,但是不能將啟動時間降低到合理的延遲。

我們嘗試使用方法分析器,讓我們驚訝的是,通過解析代碼,我們看到在啟動過程中大量時間花費在常見的字符串方法中像string.split。根據我們閱讀源碼,甚至是來自Application.onCreate的步調試器,似乎都沒有發生這種情況。 在探查器中,重量級操作彙總到ZoneRulesProvider類中的靜態初始化程序中,其中(理論上)懶惰時區數據庫提供程序代碼正在註冊。 由於這個類正在被加載進行註冊,即使被註冊的對象完全是懶惰的,並且在註冊時沒有執行任何I / O,也會運行靜態初始化塊以試圖從ServiceLoader / META加載時區數據庫META-INF。這是Java服務器中典型的模式,而不是Android。它用了我們避免使用的同樣資源下載,由於它在Android上的性能很差。

我們最終修改了ThreeTenBP本身,以便可以輕鬆重寫此靜態初始化塊的行為。默認實現將保持不變,但會被抽象化在新的ZoneRulesInitializer類後。Android應用程序或庫將能夠提供自己的實現,以便在庫的第一次使用時通過Android資產加載時區數據庫。

我們更新了另一個面向Android的ThreeTenBP封裝器lazythreetenbp,以利用這個新接口,相當於ThreeTenABP有待更新。使用這個庫的啟動延遲是零,導致低延遲。但是,在靜態模塊初始化中有時區數據庫的加載的發生,意味著在需要時區數據之前不需要進行任何操作,這在典型的用戶會話期間甚至可能不會發生。(Uber App非常大,很少有功能需要操縱日期和時間會用到時區)。

Uber:如何建立司机支持服务的签到及预约系统

圖5: GLH管理者的日曆UI指定在任何給定時間段內,特定站點上有多少專家可用。

我們還為GLH管理者構建了一個日曆應用程序,可以輕鬆靈活地配置其網站的營業時間,可用的預約時間以及任意給定小時內的可用專家池。可用時間只能在營業時間內創建。日曆中的休息時間變灰。日曆還顯示當前已安排的預約。

在日曆的周視圖中,網站管理員可以從開始時間拖放到結束時間以創建可用時間。此外,他們還可以在移動應用程序中添加假期和午餐時間等關閉項目,從而防止網站管理員在現場關閉期間意外增加可用時間。

為了設計這個接口,我們使用Node.js,React / Redux,Styletron進行內聯樣式,ES2017(ES8)用於JavaScript,Lerna用於存儲monorepo的可重用組件,以及其他一些Uber類庫/框架,如Bedrock和Superfine。設計能夠提供卓越用戶體驗的日曆功能非常複雜,因此創建一個並保持高性能是一項重大挑戰。然而,我們並不想妥協我們的簡單,可讀和可擴展的代碼庫。另外,我們希望創建一些可重用的React組件,以適應將使用這些組件的其他前端項目。

在我們的軟件測試版中,每當日曆被拖動時,日曆中的許多元素都會被重新渲染。因此,小時範圍是動態顯示的,即使大多數這些元素沒有視覺更新。由於渲染日曆中的許多DOM元素,我們通過調整shouldComponentUpdate生命週期方法來減少需要渲染的元素數量,從而利用React的虛擬DOM。

然後,我們通過使用react-dnd的拖動源來檢查日曆中的元素是否在開始時間和結束時間的範圍內,並僅重新呈現那些元素。另外,我們使得閉包和可用時間的DOM元素不可更新,因為它們不允許重疊,略微提高了性能。結果,在拖放過程中由更新引起的200毫秒延遲減少,使其接近於0。

由於日曆應用程序對服務器進行了大量調用,並且包含許多性能調整,所以自開始以來,代碼複雜度顯著增加。為了保持代碼清潔和簡單,我們將代碼抽取到可重用組件和HOC以及一些環境設置中,並將其轉換為前端monorepo。我們將Lerna用於monorepo併發布軟件包。通過使用monorepo,幾個軟件包被存儲在一個回購站中,這樣可以節省引導新項目的時間,並且可以一次更新多個組件,從而更容易添加跨組件功能或修復錯誤。另外,為了增強React組件的可重用性,我們使用Styletron代替CSS來進行內聯樣式。這確保了其他開發人員不需要自己添加CSS,從而避免考慮樣式衝突,因為所有樣式都直接應用於JavaScript代碼中。

Uber的面對面支持工程的未來

開發此產品有助於提高合作車主在GLH上的體驗,從而提高客戶滿意度。遷移到新系統已經將等待時間平均縮短了15%以上,並且一旦與客戶支持專家匹配,問題解決時間減少了25%。最重要的是,這些新功能讓那些在GLH安排預約的合作車主幾乎不需要等待時間。

這只是為我們來自全球的合作車主和客戶支持專家準備的眾多產品的一小部分。我們一直持續在探索新技術以改善我們用戶的GLH體驗,從改進我們的分析到合作車主提交申請前主動提供支持服務。


分享到:


相關文章: