HelloTalk 基於 OpenResty 的全球化探索之路

2019 年 12 月 14 日,又拍雲聯合 Apache APISIX 社區舉辦 API 網關與高性能服務最佳實踐丨Open Talk 廣州站活動,HelloTalk, Inc. 後臺技術負責人李凌作了題爲《HelloTalk 基於 OpenResty 的全球化探索之路》的分享。html

李凌,HelloTalk,Inc. 後端技術負責人,專一在服務出海和基於 Golang/CPP 的 IM 服務及相關技術平臺的架構,5 年基於 OpenResty 服務治理和使用經驗。nginx

如下是分享全文:web

你們好,我是來自 HelloTalk 的李凌,本次主要介紹 HelloTalk 作什麼業務以及基於怎樣的場景使用 OpenResty 和 Apache APISIX。算法

HelloTalk:技術上看是基於全球的 Tiny 版微信

HelloTalk 是全球最大的外語學習社交社區,全球 1600 萬用戶經過 HelloTalk 和全球語伴學習 150 門外語、進行跨文化交流及交友。用戶普遍分佈中國、日本、韓國、美、歐、巴西等國家,其中海外用戶佔 80%,從技術角度來看 HelloTalk 是一個基於全球的 Tiny 版微信。後端

HelloTalk 海外有不少 KOL 用戶在 YouTube、Instagram、Twitter等平臺協助作推廣,知名度較高,產品兼顧聊天、改錯、翻譯等功能,用戶能夠邊聊邊語音改文字。其中語音改文字和翻譯支持 100 多種語言。緩存

從運營層面看,不少企業出海時不知道怎樣去作第一步,技術上也一樣面臨這個問題——如何作到出海,而且爲全球用戶提供優質的服務。爲了讓每一個國家的用戶都能擁有更好的使用體驗,這裏就要不得不提到 OpenResty 給咱們帶來的幫助了。安全

如上圖所示,HelloTalk 的用戶分佈很散,咱們須要找到最佳的平衡點,以性價比最優的方式部署鏈接節點。亞太區域如韓國、日本,中國長三角和珠三角等地用戶分佈比較集中,比較好處理,可是在用戶高度分散的其餘地區(如上圖的歐洲、非洲、中東),對提供穩定可靠的服務提出了較高的挑戰。服務器

爲何使用 OpenResty

早期 HelloTalk 使用 C++ 寫 IM 服務,當時是用到某大廠的高性能網絡框架,協議都是內部擬定的,HTTP 協議都不多用。這對小公司而言成本很高,假設內部寫服務要曝露給外部使用,還須要本身開發 Proxy Server,而最新加的命令字要作適配,這就很是麻煩了。微信

因此從 2015 年開始,HelloTalk 開始引進 OpenResty,基於 OpenResty 在前面作代理,直接進行協議轉換傳到內部服務,減小了不少的成本。websocket

此外,服務簡單暴露給外部使用,會須要 WAF 的功能。咱們早期有些API 是基於 PHP 實現的,常常會由於框架緣由被發現一些漏洞,致使一些黑客作各類注入和攻擊,其中主要的手法就是 POST 各類 PHP 的關鍵字,或者在 URL 裏面攜帶 PHP 關鍵字。

當時咱們在 OpenResty 裏添加不多的代碼(基於正則)後解決了這個問題,後來發現即便增長 WAF 功能,性能上也不會有太大的損失。

  • TLV:0x09+Header(20 bytes)+Body+0x0A

早期咱們作 IM 開發都但願協議短小精悍,HelloTalk 的協議頭也比較精簡,所有是 TCP 的協議體。比較有意思的是經過先後加兩個特殊的字節符號,定義中間的內容,即 0x09+Header(20 bytes)+Body+0x0A,基本上能夠保證數據包不會出亂。若是沒有先後 0x09 和 0x0A 兩個包,其實仍是有必定機率會產生錯包的。

  • 自定協議->HTTP 的研發成本,急需高效的 proxy 服務作協議轉換

早期 HelloTalk 採用 TLV+PB 的協議模式,當時業務正快速發展,須要改爲對外的reastful+JSON,第一步要作的是 PB 轉 JSON。

而作協議解析遇到一個問題:OpenResty 使用的是雲風寫的 PBC 解析器,解析寫起來很是麻煩,必需要知道里層的結構。假設結構有三層,你得寫三層判斷代碼,一層一層地把它拋出來。但後來發現 Apache APISIX 是基於 lua-protobuf,因此咱們也改爲了用 lua-protobuf 庫,它能夠直接把一個 PB 對象直接轉成了 JSON,很是方便。

  • 基於 cosocket 的 TCP 協議安全解析

協議的解析過程基本上是不斷地讀 socket,讀到上圖中的包頭裏的 Length 字段,再去讀 body 段,這裏能夠看出本身要實現協議解析比較麻煩,由於要對每一個協議作適配。

  • 快速實現一個 Web IM

咱們當時作完 C++ 的 IM 通信服務後,看到主流的 IM App 如 WhatsApp、微信都有 Web IM,咱們很快的基於 OpenResty 對他們的協議進行兼容和改造,大概兩週時間,咱們就從服務端快速實現了一個 WebIM 版本的 HelloTalk。

和微信網頁版本同樣掃描登陸聊天,基本不對協議作改動,只在中間添加一層 OpenResty 作 WebSocket 協議轉換。

  • 控制消息頻率

公共服務若是暴露出去,會有人頻繁地給全部的人發消息,所以咱們須要作消息限流,這是直接基於 resty.limit.req 作的,固然 API 頻率控制也是如此進行的。

  • WAF 保護 PHP 業務

作過 PHP 開發應該知道,全部的入侵實際上是各類注入 PHP 的函數名字、關鍵字。但當我把全部的 PHP 的函數名全放在 WAF 後,我再也沒發現過被攻擊,但在日誌裏發現不少,這說明所有被攔截了,到不了 PHP 那邊。

三步走:

一、純 TCP 協議快速實現;

二、基於 Openresty 的 HTTP 服務暴露;

三、API網關(Apache APISIX) 加 Golang 微服務開發和治理。

國際化過程當中的挑戰和問題

  • HelloTalk 用戶分佈區域很是分散,須要想辦法解決用戶分佈區域分散的問題;

  • HelloTalk 國內大概有 20% 的用戶,面臨防火牆的問題;

  • 海外語言環境和網絡環境同樣複雜,語言適配問題難以處理。

怎樣提升用戶的全球接入質量

我比較過市面上不少服務商提供的方案:

一、阿里雲全球加速(BGP + 專線),直接就是 4 層加速。

二、阿里雲 DCDN 全站加速。

三、AWS的 Global Accelerator 方案。

四、Ucloud的 XPath方案 。

五、專線服務(兩端 VPC,中間專線,邊緣卸載https)

六、Zenlayer。

但咱們須要考慮兩個問題:成本,真正的服務質量。

在解決跨境問題時,因爲要考慮到國內 20% 的用戶和公司總部地理位置,因此咱們是基於阿里雲全站加速展開,本來是所有用公網代理到香港阿里雲,採用兩邊是 VPC、中間專線的形式,但有時候會遇到專線網絡抖動致使延時提升的問題,因此在深圳作了基於 OpenResty 的網關代理。而實際狀況是:若是專線不通就選擇走公網,公網延時大概 14ms,專線是 4ms。

這裏會涉及到上游檢測,線路不通時須要快速的切換到另一條線路,這部分問題是基於又拍雲提供的 Resty 庫在解決。

香港阿里機房到香港騰訊騰訊機房感受實際上是在同一個區域,由於咱們測試延時大概在 0.3ms~0.4ms。

對於海外其餘用戶,基本所有是直接加速回到香港阿里,但直接加速會致使客戶端的網絡質量受地域問題影響嚴重,因此咱們設置了一些 failover 的機制來保障用戶的使用體驗。

接入線路控制和流量管理

  • 專線網絡的帶來的穩定性,例如歐洲到香港,延時:244 ms -> 150 ms;

  • 動態 upstream 控制 (lua-resty-healthcheck),多服務商線路之間靈活切換,保證服務的可靠性;

  • 部分邏輯能夠直接在邊緣處理,serverless (原理都是基於 pcall+loadstring 實現),serverless 這塊咱們如今正則將其改形成 Apsche APISIX + ETCD。

接入節點和質量把控

目前 HelloTalk 的接入節點主要分佈在:美國東部,法蘭克福,新加坡,東京,香港。美國直接到香港有可能會不通,此時會按照既定機制經轉德國再回到香港,日本和韓國也是回到香港。巴西也有不少用戶,但巴西雲廠商只有 AWS 在作,基本上所有是連到美國,若是連不通也會多個線路之間作選擇。這個環節實際上是雲廠商或者是 CDN 廠商完成,但實際發現總有一些地區作的並很差,因此爲了保證用戶體驗不受損,咱們得有些 failover 機制保證多個服務商之間切換,保證用戶的服務是可靠的。

7 層和 4 層加速的選擇

不少服務商會提供 7 層加速和 4 層加速,但也會有一些問題須要解決。

  • 4層加速:SSL握手時間過長,容易失敗,得不到客戶端的 IP,不方便作節點質量統計。

4 層加速得不到客戶端的 IP,(注:有些雲廠商是支持的但須要在服務器上打個補丁),它在 TCP 的包裏提供了此功能,也不是很友好,若是打補丁出了問題,誰來負這個責任呢?

此外,監控質量也成了問題,咱們須要知道哪條線路行、哪條線路不行,雖然有切換機制,但咱們要知道它真實的通信路線。事實上咱們在每一個流量層代理時都會把真實 IP 帶着跑,若是採用阿里雲,那阿里雲會幫咱們填到一個頭裏面去,不斷地把客戶端的真實 IP 帶給下一個節點。

  • 7 層加速:不能保證 IM 服務須要長鏈接保持消息的可靠到達

7 層加速的問題在於使得 IM 服務機制變成了 long polling 或者是短鏈接輪循機制,但在實際過程當中咱們發現它比較耗流量,並且 IM 服務須要長鏈接保持消息的可靠和及時到達,但大部分 7 層加速廠商不支持 WebSocket,個別支持 WebSocket 的廠商邊緣卸載 HTTPS 又很貴的,尤爲是國外的像 AWS 挺貴的。此外,若是雲廠商邊緣節點宕機,會對用戶形成比較差的影響,所以咱們就在多個雲廠商之間的客戶端作了不少 failover 邏輯設計(內置 IP 機制),一旦故障可以切實保障切換到另一個節點,保證鏈接質量。

多雲環境下的全球接入的管理方案

  • 支持 websocket 的 7 層加速。(雲服務+自建)

  • 自建低速率的 VPC+專線通道。(性價比考慮,IM 自身流量並很少,只作通知消息下發)

  • 長短鏈接混合收發消息:websocket+long polling+httpdns + 內置 IP failover 機制

固然內置哪一個 IP 到客戶端也是一個問題,好比對於歐洲用戶,其實確定是要分配歐洲的 IP,那麼首先咱們服務端要把歐洲的服務端 IP 存起來,怎麼存?何時存?這裏咱們是經過騰訊雲的 httpdns + openresty timer 機制分配、緩存、更新的,上圖中的 IP 就是用戶的真實 IP,這個時候 httpdns 服務商就會根據 IP 參數作域名的 IP 解析。

從自建 API Gateway 到深刻體驗 Apache APISIX

自建 API Gateway 實現假裝動態化

咱們早期是直接改 nginx.conf,我本身以爲裸的 nginx 性能確定是最高的。但問題是不少人不必定記得 Location 配製的優先級順序規則,咱們也常常會改錯。並且咱們的需求比較固定:動態更新 SSL 證書、Location、upstream,當時的作法相似如今的 K8S 的 ingress 更新機制,即經過模本生成:nginx_template.conf+JSON -> PHP -> nginx.conf -> PHP-cli> Reload 來實現動態化。但這個方案在遇到 Apache APISIX 以後能夠考慮替換掉了。

Apache APISIX 成爲 HelloTalk 的選擇:

  • 自身需求比較簡單,依賴 RDMS 以爲過重,帶來額外的維護成本;

  • 代碼極致簡單易懂,在我的能力範圍內,能夠理解;

  • 基於 ETCD,節省維護成本;

  • 項目主要維護者幾乎實時在線支持,QQ 羣、郵件響應及時。

推薦閱讀

從 0 到 1:Apache APISIX 的 Apache 之路

阿里巴巴王發康:阿里七層流量入口負載均衡算法演變之路

相關文章
相關標籤/搜索