從 PHP 到 Go:我們是如何將 API 速度提升 8 倍

NearTheShore發表於2020-04-06

從PHP到Go:我們是如何將API速度提升8倍

最近,我們把人臉識別API架構所使用的PHP升級到了Go語言。在本文中,我們分享了我們做出此舉的原因,它如何從根本上改善了效能以及在此過程中獲得的一些教訓。

預備,開始!

2017年,在我們的#DiversityRecognition演示成為一種網際網路現象之後,我們的流量有了巨大的持續增長,突如其來的增長量提醒我們注意迄今為止,從未考慮過的效能問題,以及隨後支援超高使用率應用的成本。我們開始想做些改變,而這次活動將這一改變推到了我們榜首的位置。

在研究了各種選項(包括遷移到PHP 7,Java和其他語言)之後,我們決定遷移到純Go和C解決方案-這對我們來說是最有效率的。考慮到我們最初的面部識別API體系結構是用PHP,Python和C組合編寫的,所以簡化程式碼庫是一個主要目標。

根據我們的敏捷哲學,我們從小規模開始,用Go重寫了PHP部分應用,而不是從完整的API開始。這種方法意味著我們可以在進行過程中應對任何意外的挑戰,保持我們的程式碼高效,並收集使用者的快速反饋。以我的經驗,選擇程式語言是一回事。最終,如何編寫程式同樣重要(甚至更重要)。

“隨著多執行緒之間Go效率的提高,部署規模的減小,記憶體佔用的減少以及執行更少的Docker容器的整體使用,我們能夠將Kubernetes叢集中的主機數量減少50%以上。”  -Kairos技術長Cole Calistra。

升級到Go的好處

正如我上面提到的,雖然桌上還有其他選擇,但是,就以下關鍵方面而言,Go代表了最大的淨贏球(最優解):

速度

通過我們的PHP構建,客戶需要花費1-2秒的API時間。在遷移到Go時,相同的請求現在不超過300毫秒。值得注意的是,一些較低階別的功能將在100ms以下。總體來說,自從使用Go以來,我們平均將速度提高了8倍。

易於部署

使用PHP,我們必須部署承載完整Linux發行版,Nginx安裝和PHP-FPM的容器。容器還必須包括許多庫和驅動程式,以將PHP連線到其他服務,例如MySQL和Redis。

使用Go,我們可以部署精簡版的Linux安裝程式Debian-Slim,並且只需要分發生成的單個Go可執行檔案即可。我們在Go中有一個528MB的Docker PHP映像,一次能減少到267MB,另一個366MB的Docker映像也減少到了170MB。實質上將我們的記憶體佔用量減少了近50%

對於單個容器來說,這似乎不是一個巨大的變化,因為許多機器可以處理執行中的數千兆位元組的容器而不會出現問題。但是,在規模化並且試圖將盡可能多的容器打包到每個主機中時,大小成為整體容量和拆分新容器的速度的一個因素。

多執行緒

我們利用Go的許多內建功能(包括通道和go例程)來更有效地充分利用每個容器的CPU和記憶體容量。

成本

隨著多執行緒之間Go效率的提高,部署規模的減小,記憶體佔用的減少以及執行更少的Docker容器的整體使用,我們能夠將Kubernetes叢集中的主機數量減少50%以上。

以前,我們執行了200多個PHP API容器來處理基準流量,這些流量在高峰負載時將達到600-700個容器。使用我們新的Go部署,我們執行了不到20個容器,這些容器在40-50個峰值左右達到最高負載,比我們在PHP API上看到的要高。

專業發展

我們的工程師都沒有Go的開發經驗,但是是其他多種語言的專家,包括PHP,Python,Ruby,Java,C ++和C。這為我們提供了學習如何在現實世界中進行開發的機會。對公司和我們的客戶有重大影響的專案。它也有助於完成一項熟悉而重要的任務來推動我們的學習。這比建立簡單的“ Hello,World!”更為可取。應用程式型別。我們需要在生產中動手。

持續學習是Kairos這裡文化的核心宗旨。使我們的工程師對外界更具市場吸引力,這使Kairos成為一個更好的工作場所。如果我們將團隊紮根於過時的技能,則對任何人都無所提升。更不用說,Go是一種非常適合銷售的技能-根據PayScale的這項研究,它是需求量排名第二的語言。

Golang的廣泛吸引力吸引了許多流行的開源專案,例如Docker和Kubernetes。在基礎架構中實施了此類工具並從其編碼最佳實踐中學習後,我們繼續從其他頂級工程師那裡提高我們的技能和策略。

從PHP到Go:我們是如何將API速度提升8倍

我們沿途學到的東西

對於任何新的軟體開發任務,希望您都在專案的整個生命週期中學習新的東西。以下是從我們離開PHP到Go中獲得的一些經驗教訓:

使用Cgo和Go進行記憶體洩漏和垃圾回收

不要以為記憶體正在洩漏或哪些物件可能正在洩漏記憶體。對您的程式碼進行效能分析將為您提供答案。垃圾收集器負責除去周圍閒置的任何未使用的物件,只要沒有對垃圾收集器檢查的物件的引用即可。如果確實存在對物件的引用,則垃圾無法觸及它們。這包括在程式碼其他部分中引用的Cgo物件。

從Redis封送JSON物件的問題

這裡的挑戰是找到一種方法來捕獲非常大的Redis結果,並方便地將其格式化為資料結構。我們的主要目標是效率(速度),並且正如我們發現的那樣,標準JSON包對於我們的特定用例不可行。解決此問題的另一種方法是處理Redis傳送給我們的RAW資料,以避免不必要的JSON處理。
在優化程式碼之前收集資料。使用Go的分析工具,可以節省時間

在優化程式碼之前收集資料,使用Go的分析工具,可以節省時間

如上所述,在沒有適當上下文的情況下進行優化會浪費時間。您必須找出可能導致問題的程式碼瓶頸。剖析程式碼的作用,您將能夠看到問題的根源或對此有一些瞭解。此過程會非常節省時間。

如果您是新的Go開發人員,建議經常分析您的程式碼。您做的越多,就越熟悉,您未來的Go專案將得到改善。

Go如何在內部管理記憶體

Go是一種垃圾回收語言,因此它具有一個執行時來為您管理記憶體分配。但是請注意,垃圾收集器不能替代編寫良好且高效的程式碼。

轉義分析(Go確定記憶體分配位置的方式)

Go具有稱為“轉義分析”的功能。它負責在編譯時確定執行時將在哪裡分配記憶體,即分配給堆疊還是堆。這很容易克服,特別是在處理大量資料分配的大型應用程式中。併發應用程式(例如基於REST的API)就是您最能體會到這種情況的示例。
重用物件,而不是重新分配一遍又一遍使用的物件。為了提高效率,請尋找機會重用先前分配的資料結構。如果沒有,這些資料結構或物件將被重新分配,垃圾收集器將做更多的工作。

走向未來

如您所見,Kairos從PHP遷移到Go的經歷並非沒有挑戰。但是,我們的集體學習以及專案的成功,意味著我們可以繼續使用Go來為客戶服務。

原文: PHP to Go: How we boosted API performance by 8X

本作品採用《CC 協議》,轉載必須註明作者和本文連結

Reflection.

相關文章