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 是全球最大的外語學習社交社區,全球 1600 萬用戶經過 HelloTalk 和全球語伴學習 150 門外語、進行跨文化交流及交友。用戶普遍分佈中國、日本、韓國、美、歐、巴西等國家,其中海外用戶佔 80%,從技術角度來看 HelloTalk 是一個基於全球的 Tiny 版微信。後端
HelloTalk 海外有不少 KOL 用戶在 YouTube、Instagram、Twitter等平臺協助作推廣,知名度較高,產品兼顧聊天、改錯、翻譯等功能,用戶能夠邊聊邊語音改文字。其中語音改文字和翻譯支持 100 多種語言。緩存
從運營層面看,不少企業出海時不知道怎樣去作第一步,技術上也一樣面臨這個問題——如何作到出海,而且爲全球用戶提供優質的服務。爲了讓每一個國家的用戶都能擁有更好的使用體驗,這裏就要不得不提到 OpenResty 給咱們帶來的幫助了。安全
如上圖所示,HelloTalk 的用戶分佈很散,咱們須要找到最佳的平衡點,以性價比最優的方式部署鏈接節點。亞太區域如韓國、日本,中國長三角和珠三角等地用戶分佈比較集中,比較好處理,可是在用戶高度分散的其餘地區(如上圖的歐洲、非洲、中東),對提供穩定可靠的服務提出了較高的挑戰。服務器
早期 HelloTalk 使用 C++ 寫 IM 服務,當時是用到某大廠的高性能網絡框架,協議都是內部擬定的,HTTP 協議都不多用。這對小公司而言成本很高,假設內部寫服務要曝露給外部使用,還須要本身開發 Proxy Server,而最新加的命令字要作適配,這就很是麻煩了。微信
因此從 2015 年開始,HelloTalk 開始引進 OpenResty,基於 OpenResty 在前面作代理,直接進行協議轉換傳到內部服務,減小了不少的成本。websocket
此外,服務簡單暴露給外部使用,會須要 WAF 的功能。咱們早期有些API 是基於 PHP 實現的,常常會由於框架緣由被發現一些漏洞,致使一些黑客作各類注入和攻擊,其中主要的手法就是 POST 各類 PHP 的關鍵字,或者在 URL 裏面攜帶 PHP 關鍵字。
當時咱們在 OpenResty 裏添加不多的代碼(基於正則)後解決了這個問題,後來發現即便增長 WAF 功能,性能上也不會有太大的損失。
早期咱們作 IM 開發都但願協議短小精悍,HelloTalk 的協議頭也比較精簡,所有是 TCP 的協議體。比較有意思的是經過先後加兩個特殊的字節符號,定義中間的內容,即 0x09+Header(20 bytes)+Body+0x0A,基本上能夠保證數據包不會出亂。若是沒有先後 0x09 和 0x0A 兩個包,其實仍是有必定機率會產生錯包的。
早期 HelloTalk 採用 TLV+PB 的協議模式,當時業務正快速發展,須要改爲對外的reastful+JSON,第一步要作的是 PB 轉 JSON。
而作協議解析遇到一個問題:OpenResty 使用的是雲風寫的 PBC 解析器,解析寫起來很是麻煩,必需要知道里層的結構。假設結構有三層,你得寫三層判斷代碼,一層一層地把它拋出來。但後來發現 Apache APISIX 是基於 lua-protobuf,因此咱們也改爲了用 lua-protobuf 庫,它能夠直接把一個 PB 對象直接轉成了 JSON,很是方便。
協議的解析過程基本上是不斷地讀 socket,讀到上圖中的包頭裏的 Length 字段,再去讀 body 段,這裏能夠看出本身要實現協議解析比較麻煩,由於要對每一個協議作適配。
咱們當時作完 C++ 的 IM 通信服務後,看到主流的 IM App 如 WhatsApp、微信都有 Web IM,咱們很快的基於 OpenResty 對他們的協議進行兼容和改造,大概兩週時間,咱們就從服務端快速實現了一個 WebIM 版本的 HelloTalk。
和微信網頁版本同樣掃描登陸聊天,基本不對協議作改動,只在中間添加一層 OpenResty 作 WebSocket 協議轉換。
公共服務若是暴露出去,會有人頻繁地給全部的人發消息,所以咱們須要作消息限流,這是直接基於 resty.limit.req 作的,固然 API 頻率控制也是如此進行的。
作過 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 層加速得不到客戶端的 IP,(注:有些雲廠商是支持的但須要在服務器上打個補丁),它在 TCP 的包裏提供了此功能,也不是很友好,若是打補丁出了問題,誰來負這個責任呢?
此外,監控質量也成了問題,咱們須要知道哪條線路行、哪條線路不行,雖然有切換機制,但咱們要知道它真實的通信路線。事實上咱們在每一個流量層代理時都會把真實 IP 帶着跑,若是採用阿里雲,那阿里雲會幫咱們填到一個頭裏面去,不斷地把客戶端的真實 IP 帶給下一個節點。
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 實現假裝動態化
咱們早期是直接改 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 羣、郵件響應及時。