應用容器化優化指南 - Golang篇 頂 原

摘要: 前言 隨着容器技術的興起,越來越多不同類型的應用開始使用容器的方式進行交付。Golang作爲服務器端非常熱門的一門語言同時也是容器技術的主要編寫語言備受關注。那麼將一個Golang應用進行容器化的時候,需要注意哪些事情,在出現問題時該如何進行調優和診斷呢? 先談談Golang本身的設計 Golang是谷歌發佈的第二款開源編程語言。

前言

隨着容器技術的興起,越來越多不同類型的應用開始使用容器的方式進行交付。Golang作爲服務器端非常熱門的一門語言同時也是容器技術的主要編寫語言備受關注。那麼將一個Golang應用進行容器化的時候,需要注意哪些事情,在出現問題時該如何進行調優和診斷呢?

先談談Golang本身的設計

Golang是谷歌發佈的第二款開源編程語言。Golang專門針對多處理器系統應用程序的編程進行了優化,使用Golang編譯的程序可以媲美C或C++代碼的速度,而且更加安全、支持並行進程。Golang在容器相關的場景和領域以及高併發的服務器程序場景下扮演着非常重要的角色。

Golang具有如下三個特點:

  • 簡潔 快速 安全
  • 並行 有趣 開源
  • 內存管理 數組安全 編譯迅速

在學習一門語言前,通常我會主要關注如下三個方面:第一這門語言的特性是什麼;第二這門語言解決的場景和問題是什麼;第三這門語言的內部設計是否有需要注意的地方。上面的介紹已經爲我們解答了第一個和第二個問題,那麼接下來我們主要來討論第三個問題。那麼Golang的這些優秀的特性內部的設計方式是什麼樣子的,使用起來是否有什麼需要特別注意的呢?爲了詳細解答這個問題,我們將問題拆分成了二個部分分別爲大家解答。

  • Golang是如何實現並行的

高併發是Golang被大家接納和認可的最重要一環。對於大型的互聯網項目而言,高併發可以說是應用性能的立足之本,再棒的功能與特性也不如穩定運行來得讓人安心。從前大家在關注C10K問題,而現在越來越多的人開始思考如何解決C10M問題。從C10K問題到C10M問題,解決問題的方式已經不是簡簡單單的調整內核參數那麼簡單的。更多的是要從架構甚至應用自身的角度來解決,一個高效的併發模型,可以從應用程序的交付壓榨系統的性能。目前比較成熟的併發模型,主要是通過進程、線程與協程三種不同方式來進行實現的。

進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。每個進程都有自己的獨立內存空間,不同進程通過進程間通信來通信。由於進程比較重量,佔據獨立的內存,所以上下文進程間的切換開銷(棧、寄存器、虛擬內存、文件句柄等)比較大,但相對比較穩定安全。

線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。

協程是一種用戶態的輕量級線程,協程的調度完全由用戶控制。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快。

Golang的併發模型是基於協程的,而協程在Linux底層的調度是依賴進程的調度的,而這之間的轉換都通過Golang自身的調度器進行了管理,無需開發者關心。但是這個時候有經驗的開發者就會提出問題了,golang本身是編譯型的語言沒有類似JVM一樣的虛擬機可以在運行時指定參數,那麼Goroutine這種方式是否有參數需要設置來保證性能。

此處給大家講述一個關於Goroutine棧擴容的問題,我們內部有一個全雙工的高併發寫離線數據的服務,在底層數據出現消費慢的時候快速出現OOM,問題產生的原因就是由於Goroutine棧擴容,最後可以通過通過拆分Goroutine的邏輯到上半段和work group的方式實現,由於篇幅的原因不過多的贅述,可以參考如下這篇博客更深入瞭解棧擴容的問題。

  • Golang的內存是如何管理的

內存管理對於C++與Java的開發者而言是最熟悉不過的了。C++的開發者必須通過代碼手動的申請與釋放內存,因此必須熟悉內存佈局和使用;Java的開發者雖然有JVM幫助進行內存的管理與回收,但JVM不同的內存參數配置會導致程序因爲回收內存帶來不同的性能表現。而Golang作爲一門高級編程語言,同樣無需開發者直接操作內存,但是Golang中的GC設計是存在一些缺欠的。主要的問題在GC時的卡頓上,具體的問題可以參考如下文章,不過這點也無需大家特別關心,建議直接使用Golang1.9以後的版本進行編譯即可。深入瞭解Golang的GC可以參考如下文章

Golang容器化建議

  • 常規容器化建議

首先需要進行的是常規的容器化優化,具體的內容可以參考如下文章進行體積的精簡和優化。

  • Golang 中DNS的問題

不同語言對於DNS的Lookup處理會有所不同,在Java或者Node.JS等常見的語言和框架中對DNS Lookup都提供語言級別的內置的Cache,而在Golang中卻不存在類似的能力,這會導致對於高併發的場景中,Golang程序有可能會出現大量的DNS查詢,而在kubernetes中,DNS是通過內部的coredns或者kube-dns的方式提供的,因此有可能會因爲大流量的Golang DNS導致集羣異常,爲了解決這個問題,建議開發者在Golang的Dockerfile中集成nscd進行DNS的Cache,具體的操作步驟可以參考如下文檔

  • Golang 中GC的問題

在本文的上面的部分,爲大家講解了Golang GC的一些缺欠以及如何避免GC問題的方式,在容器化的時候是否還需要做其他的優化呢?面對內存的異常,我們要如何定位是一個GC的問題呢?這裏要給大家介紹的是Golang自來的pprof,pprof是Golang語言中內置的性能調優工具,可以協同Flame-Graph,排查CPU性能、內存性能、GC回收等問題,建議在容器的場景中,在代碼中集成pprof,並通過環境變量的方式進行開關設置,容器的Dockerfile中保留端口的保留,當出現問題的時候可以設置環境變量的方式進行開啓,快速進行線上問題的診斷。pprof的使用,可以參考如下文章

  • Golang 中CGO的問題
    我們知道Golang作爲一門編譯型的語言,可以通過開啓CGO的標籤,直接使用C的代碼並編譯爲二級制文件直接使用。但是這種方式非常不建議在容器中開啓,特別是使用類似Alpine這種最小鏡像的場景下。因爲開啓CGO的場景下,會動態鏈接系統的C庫,而在Alpine上,很多的目錄佈局是有所差異的,另外有些最簡化的版本glibc的支持並不完善,因此非常不建議使用CGO的方式編譯Golang在容器中使用。
  • Golang 中監控的建議 
    容器中很多監控的方式都無法很好的直接複用,建議大家使用更Docker的方式來解決,例如使用Prometheus的方式暴露Golang應用內部的指標進行監控,這也是目前非常多Golang開源項目的標配了。使用方式參考下文,與容器服務結合可以參考如下文章
  • Golang 中性能的問題
    Golang與容器的結合通常是爲了高性能的場景,那麼通常需要對內核參數進行部分的調整,具體的調整方式可以參考如下文章

最後

Golang相對而言算是非常”省心“的一門語言了,在老版本的Golang中還需要通過runtime設置GOMAXPROCS,但是在最新版本的Golang中已經基本無需關心runtime的任何參數設置了,這些參數就像nginx的auto一樣,會隨着探測的配置自動變化,而在容器中,我們依然需要GOMAXPROCS,因爲GOMAXPROCS的識別方式是通過獲取系統資源的方式確定的,而在容器中是通過只讀掛載宿主機的文件實現的,因此獲取的資源還是宿主機的數值。因此,Golang的應用容器化,更多的還是要做好標準鏡像優化的步驟,以及在代碼級別做好避免觸發GC和Goroutine的問題。

阿里雲雙十一1折拼團活動:已滿6人,都是最低折扣了

【滿6人】1核2G雲服務器99.5元一年298.5元三年 2核4G雲服務器545元一年 1227元三年

【滿6人】1核1G MySQL數據庫 119.5元一年

【滿6人】3000條國內短信包 60元每6月

參團地址:http://click.aliyun.com/m/1000020293/

原文鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章