中間件業務在網易輕舟容器平臺的性能調優實踐

隨着業務容器化的推進,經常有客戶抱怨應用 QPS 無法和在物理機或者雲主機上媲美,並且時常會出現 DNS 查詢超時、短連接 TIME_OUT、網絡丟包等問題,而在容器中進行調優與診斷的效果因爲安裝工具的複雜度大打折扣。本文基於網易輕舟中間件業務容器化實踐,總結容器場景下的性能調優心得,供讀者參考。

性能調優的“望聞問切”

在討論容器化場景的性能調優之前,先談一下性能調優中的“望聞問切”。對於性能問題,大部分人首先想到的是 CPU 利用率高,但這只是個現象,並不是症狀。打個比方:感冒看醫生時,病人跟大夫描述的是現象,包括頭部發熱、流鼻涕等;而大夫通過探查、化驗,得到的醫學症狀是病人的白細胞較多、咽喉紅腫等,並確診爲細菌性感冒,給開了 999 感冒靈。性能調優流程與此相似,也需要找到現象、症狀和解法。回到 CPU 利用率高的例子:已知現象是 CPU 利用率高,我們通過 strace 檢查,發現 futex_wait 系統調用佔用了 80% 的 CPU 時間——這纔是症狀;根據這個症狀,我們業務邏輯代碼降低了線程切換,CPU 利用率隨之降低。

大部分的性能調優都可以通過發現現象、探測症狀、解決問題這三個步驟來完成,而這在容器的性能調優中就更爲重要的,因爲在主機的性能調優過程中,我們有很多的經驗可以快速找到症狀,但是在容器的場景中,很多客戶只能描述問題的現象,因爲他們並不瞭解使用的容器引擎的工作原理以及容器化架構的實現方式。

容器化性能調優的難點

容器化場景中的性能調優主要面臨 7 個方面的挑戰。

中間件種類多,調優方法又各不相同,如何設計一種好的框架來適配

我們借鑑 redhat tuned 思想,提出一種與業務相關的內核參數配置新框架,它可以把我們對 Linux 系統現有的一些調優手段(包括電源管理工具,CPU、內存、磁盤、網絡等內核參數)整合到一個具體的策略 (profile) 中,業務場景不同,profile 不同,以此來快速實現雲計算對不同業務進行系統的性能調節的需求。

VM 級別的調優方式在容器中實現難度較大

在 VM 級別我們看到的即是所有,網絡棧是完整暴露的,CPU、內存、磁盤等也是完全沒有限制的。性能調優老司機的工具箱安個遍,診斷流程走一趟基本問題就查個八九不離十了,但是在容器中,很多時候,都是默認不自帶診斷、調優工具的,很多時候連 ping 或者 telnet 等基礎命令都沒有,這導致大部分情況下我們需要以黑盒的方式看待一個容器,所有的症狀只能從物理機或者雲主機的鏈路來看。但是我們知道容器通過 namespace 的隔離,具備完整網絡棧,CPU、內存等通過隔離,只能使用 limit 的資源,如果將容器當做黑盒會導致很多時候問題症狀難以快速發現,排查問題變難了。

容器化後應用的鏈路變長導致排查問題成本變大

容器的場景帶來很多酷炫的功能和技術,比如故障自動恢復、彈性伸縮、跨主機調度等,但是這一切的代價是需要依賴容器化的架構,比如 Kubernetes 網絡中需要 FullNat 的方式完成兩層網絡的轉發等,這會給排查問題帶來更復雜的障礙,當你不清楚編排引擎的架構實現原理的時候,很難將問題指向這些平時不會遇到的場景。例如上面這個例子中,FullNat 的好處是降低了網絡整體方案的複雜性,但是也引入了一些 NAT 場景下的常見問題,比如短連接場景中的 SNAT 五元組重合導致包重傳的問題等等,排查問題的方位變大了。

不完整隔離帶來的調優複雜性

容器本質是一種操作系統級虛擬化技術,不可避免涉及隔離性,雖然平時並不需要考慮隔離的安全性問題,但是當遇到性能調優的時候,內核的共享使我們不得不面對一個更復雜的場景。舉個例子,由於內核的共享,系統的 proc 是以只讀的方式進行掛載的,這就意味着系統內核參數的調整會帶來的宿主機級別的變更。在性能調優領域經常有人提到 C10K 或者 C100K 等類似的問題,這些問題難免涉及到內核參數的調整,但是越特定的場景調優的參數越不同,有時會有“彼之蜜糖,我之毒藥”的效果。因此同一個節點上的不同容器會出現非常離奇的現象。

不同語言對 cgroup 的支持

這個問題在大多數場景下無需考慮,列在第四位是期望能夠引起大家重視。網易輕舟在一次排查“ES 容器(使用 java 11)將 CPU requests 都配置成 8 時,其性能低於將 request CPU 都配置成 1”的問題時,發現是 Java 的標準庫中對 cgroup 的支持不完全導致的,好在這點在大多數場景中沒有任何影響。

網絡方案不同帶來的特定場景的先天缺欠

提到容器架構避不開網絡、存儲和調度,網絡是評判容器架構好壞的一個核心標準,不同的網絡方案也會有不同的實現方式與問題。比如網易輕舟 Kubernetes 中使用了 Flannel 的 CNI 插件實現的網絡方案,標準 Flannel 支持的 Vxlan 的網絡方案,Docker 的 Overlay 的 macVlan,ipvlan 的方案,或者 OpenShift SDN 網路方案,還有網易輕舟自研的雲內普通 VPC 和 SRIOV+VPC 方案等等。這些不同的網絡方案無一例外都是跨宿主機的二層網絡很多都會通過一些 vxlan 封包解包的方式來進行數據傳輸,這種方式難免會增加額外的 CPU 損耗,這是一種先天的缺欠,並不是調優能夠解決的問題。有的時候排查出問題也只能繞過而不是調優。

鏡像化的系統環境、語言版本的差異

應用容器化是一個需要特別注意的問題,很多公司並沒有嚴格的配管流程,比如系統依賴的內核版本、語言的小版本等等,很多時候都是選擇一個大概的版本,這會帶來很多語言層級的 BUG,比如 Java 應用程序運行在容器中可能會遇到更長的應用程序暫停問題、Java 7 無法感知 CPU 個數導致 GC 線程過多問題和 PHP7.0 中 php-fpm 的詭異行爲。環境的問題本就需要嚴格管控,但是當遇到了容器,很多時候我們會分不清哪些不經意的行爲會帶來嚴重的問題,警惕性因爲容器鏡像能夠正常啓動而降低了。

性能優化步驟和調優管理變更流程

性能優化通常可以通過如表五個步驟完成:

調優管理變更和性能優化並不直接相關,但可能是性能調優成功最重要的因素。以下總結來源於網易輕舟團隊的實踐和經驗:

  • 在調優之前,實施合理的調優管理流程變更
  • 永遠不要在生產系統上調優
  • 壓測的環境要相對獨立,不能受其他壓力的影響,這裏說的不僅是壓測的服務器端,而且包括壓測客戶端
  • 在調優過程中,每次只修改一個變量
  • 反覆測試提升性能的參數,有時候,統計來的結果更加可靠
  • 把成功的參數調整整理成文檔,和社區分享,即使你覺得它們微不足道
  • 生產環境中獲得的任何結果對 Linux 性能都有很大用處

調優手段

  1. 通用調優 對於容器業務來說,儘量讓 CPU 訪問本地內存,不要訪問遠端內存。

CPU 訪問不同類型節點內存的速度是不相同的,訪問本地節點的速度最快,訪問遠端節點的速度最慢,即訪問速度與節點的距離有關,距離越遠訪問速度越慢,此距離稱作 Node Distance。正是因爲有這個特點,容器應用程序要儘量的減少不同 Node 模塊之間的交互,也就是說,我們根據容器內存 Node 親和性,選擇容器使用的 CPU 固定在一個 Node 模塊裏,因此其性能將會有很大的提升。有一種特殊場景除外,如果一個容器申請的 request 大於單個 Node 上預留的 CPU 後,這種親和性的綁定就會失效,此時迴歸到原始的跨 Node 範圍綁定,對於之前已經做了親和性的容器(申請的 request 小於單個 Node 上預留的 CPU)我們的策略是繼續維持不變。

  1. 針對不同的場景的參數調優 針對不同的場景,可以考慮以下參數着手進行調優。

與 CPU 相關的配置

cpu相關的參數 說明
force_latency 如果爲1的話,表示強制把cstate置爲c0
governor=performance 表示開啓intel睿頻技術
energy_perf_bias=performance 通過x86_energy_perf_policy工具允許管理員定義相關性能和能效,分爲三種:1. performance:默認設置,不會考慮節省能源2. normal:潛在到能耗節省,容忍部分性能妥協。3. powersave:接收嚴重性能降級,最大能效。
min_perf_pct 如果設置爲100的話,其含義是儘量讓CPU跑在P0就是Turbo的最高頻率,對於CPU,有一個標稱頻率(Base Freqency)。比如某臺服務器的CPU的Base Freqency是3.0GHz,也就是在普通狀態下,BIOS關閉Turbo開關的話,CPU的最高頻率不會超過3.0GHz。Pn指的是硬件上的最低頻率,P1是Base Frequency。在這範圍的頻率就是P1,P2,P3…Pn,後面的數字越小,就說明頻率越高。那麼P1以上的頻率就稱爲Turbo頻率,P0就是Turbo的最高頻率。Turbo就是我們所說的超頻,就是超出標稱頻率。
kernel.sched_min_granularity_ns 表示進程最少運行時間,爲了防止任務頻繁的切換,可以設置得較大些。
kernel.sched_migration_cost_ns 其值越大表示task更不容易被遷走,該變量用來判斷一個進程是否還是hot,如果進程的運行時間(now - p->se.exec_start)小於它,那麼內核認爲它的code還在cache裏,所以該進程還是hot,那麼在遷移的時候就不會考慮它。
sysctl_sched_wakeup_granularity 該變量表示進程被喚醒後至少應該運行的時間的基數,它只是用來判斷某個進程是否應該搶佔當前進程,並不代表它能夠執行的最小時間(sysctl_sched_min_granularity),如果這個數值越小,那麼發生搶佔的概率也就越高(見wakeup_gran、wakeup_preempt_entity函數)

與內存相關的配置

內存相關的參數 說明
transparent_hugepages(默認值是madvise) 設置爲never表示關閉透明大頁
vm.dirty_ratio 表示當系統髒頁到達內存X%,強制同步WriteBack(WB),如果髒頁越多,WB會變慢,響應就會變慢,所以響應要快的話,此值可以設置得較小些。
vm.dirty_background_ratio 當系統髒頁到達內存X%,強制異步WB,在低水位的時候就刷的話,刷的就相對越快,所以響應要快的話,此值可以設置得較小些。

磁盤相關的參數

磁盤相關的參數 說明
readahead(一般默認值是256) Linux的文件預讀readahead,指Linux系統內核將指定文件的某區域預讀進頁緩存起來,便於接下來對該區域進行讀取時,不會因缺頁(page fault)而阻塞。因爲從內存讀取比從磁盤讀取要快很多。預讀可以有效的減少磁盤的尋道次數和應用程序的I/O等待時間,所以業務是磁盤密集型的,此值可以設置得較大些。

與 sysctl 相關的配置

sysctl參數 說明
net.core.busy_read net.core.busy_poll 這兩個參數默認都是0,它表示是繁忙輪詢,有助於減少網絡接收路徑中的延遲,使socket層代碼查詢網絡設備的接收隊列並禁用網絡中斷,這可以消除由於中斷和由此產生的環境切換所造成的延誤。但是,它會增加CPU的使用率。繁忙輪詢可以防止CPU進入睡眠狀態,睡眠狀態會造成額外的功耗。
net.ipv4.tcp_fastopen tcp_fastopen(TFO)提高性能的關鍵是省去了熱請求的三次握手,適用於對小包的應用場景進行調優
vm.max_map_count kernel.pid_max kernel.threads-max 這三個參數可以用於調節os支持的最大線程數量
fs.inotify.max_queued_events fs.inotify.max_user_instances fs.inotify.max_user_watches max_queued_events表示調用inotify_init時分配給inotify instance中可排隊的event的數目的最大值,超出這個值的事件被丟棄,但會觸發IN_Q_OVERFLOW事件;max_user_instances表示每一個real user ID可創建的inotify instatnces的數量上限,默認128;max_user_watches表示同一用戶同時可以添加的watch數目(watch一般是針對目錄,決定了同時同一用戶可以監控的目錄數量).調大這三個參數是爲了解決“當運行docker鏡像時,報Error: ENOSPC: System limit for number of file watchers reached”問題
net.core.somaxconn 定義了系統中每一個端口最大的監聽隊列的長度
net.netfilter.nf_conntrack_max net.netfilter.nf_conntrack_tcp_be_liberal net.netfilter.nf_conntrack_checksum options nf_conntrack hashsize配合使用 調大netfilter ct參數,防止連接數過多,導致“nf_conntrack: table full, dropping packet on…”問題
net.ipv4.ip_forward 開啓數據包轉發的功能,所謂轉發即當主機擁有多於一塊的網卡時,其中一塊收到數據包,根據數據包的目的ip地址將數據包發往本機另一塊網卡,該網卡根據路由表繼續發送數據包
net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-arptables net.bridge.bridge-nf-call-ip6tables 這三個參數置爲1表示二層的網橋在轉發包時也會被iptables/arptables/ip6tables的FORWARD規則所過濾
net.core.netdev_max_backlog 默認是1000,在每個網絡接口接收數據包的速率比內核處理這些包的速率快時,允許送到隊列的數據包的最大數目
net.core.netdev_budget 默認是300,net_rx_action budget表示一個CPU單次輪詢(poll)所允許的最大收包數量,如果在內核緩存層面發現有丟包的話,可以調整net.core.netdev_max_backlog和net.core.netdev_budget這兩個參數
net.ipv4.neigh.default.gc_thresh1 net.ipv4.neigh.default.gc_thresh2 net.ipv4.neigh.default.gc_thresh3 這三個參數是爲了解決“neighbour table overflow”問題
net.ipv4.conf.eth0.arp_ignore 1表示只響應目的IP地址爲接收網卡上的本地地址的arp請求
net.ipv4.tcp_rmem=“P1 P2 P3” 第一個值是爲socket接收緩衝區分配的最少字節數;第二個值是默認值(該值會被rmem_default覆蓋),緩衝區在系統負載不重的情況下可以增長到這個值;第三個值是接收緩衝區空間的最大字節數(該值會被rmem_max覆蓋)。
net.ipv4.tcp_wmem=“P1 P2 P3” 第一個值是爲socket發送緩衝區分配的最少字節數;第二個值是默認值(該值會被wmem_default覆蓋),緩衝區在系統負載不重的情況下可以增長到這個值;第三個值是發送緩衝區空間的最大字節數(該值會被wmem_max覆蓋)。
net.ipv4.udp_mem=“P1 P2 P3” UDP的情況,含義同上

注:瞭解以上網絡參數的具體含義,需學習 Linux 內核收發包原理,強烈推薦《Monitoring and Tuning the Linux Networking Stack》系列文章。詳見文末參考文獻 4-7。

網絡調優

軟中斷隔離:將容器網卡的軟中斷綁定到某幾個專用 CPU 上,而容器業務進程綁定到其他 CPU 上,這樣可以減少業務和網卡軟中斷之間的影響,避免頻繁的上下文切換,特別適用於對網絡性能要求極高的服務例如 Redis。對於雲內普通 VPC 和雲內 SR-IOV VPC 都適用:

  • 雲內普通 VPC:需要把容器使用的虛擬網卡 veth 的軟中斷綁定到預留的某幾個專用 CPU 上
  • 雲內 SR-IOV VPC:需要把容器使用的 VF 網卡的軟中斷綁定到預留的某幾個專用 CPU 上

SR-IOV 直通網卡:優化後端 DPDK PMD 線程配比,一般由 1 個調大至 2 個或者 4 個。

調優效果

Redis

調優後 SR-IOV 下 QPS 接近 BGP 物理網絡,99.99% 時延從 BGP 物理網絡的 990ms 下降到 140ms。

Flink

調優後簡單 ETL 任務 QPS 比 YARN 上高 20%,複雜 ETL 任務 QPS 比在 YARN 上高 30%。

RDS

對於 RDS MGR 集羣,K8S 容器部署相比 RDS2.0 雲主機 VM 部署,同等規格下性能提升可以達到 30%~170%;

經過優化後,與物理機部署相比,常規模式下,只寫場景、只讀場景和讀寫混合場景的性能差距保持在 5~10% 之間。

RocketMQ

異步複製集羣:普通容器單分片相比物理機性能有 40% 差距,增加生產消費者數量,集羣整體性能有所提升,但依然與標準物理機有 25% 左右差距;增加生產消費者數量,容器調優後的性能基本持平標準物理機,差距 5% 以內。

同步複製集羣:普通容器性能略差於標準物理機,差距在 10% 左右,容器調優後的性能基本持平標準物理機,差距縮小到 5%。

參考文檔

1.tuned官方介紹

2.討論在Linux Control Groups中運行Java應用程序的暫停問題

3.聊聊新版JDKdocker容器的支持

4.monitoring-tuning-linux-networking-stack-receiving-data

5.Linux 網絡棧監控和調優:接收數據(譯文)

6.monitoring-tuning-linux-networking-stack-sending-data

7.Linux 網絡棧監控和調優:發送數據(譯文)

8.CFS Bandwidth Control

作者簡介

劉迎冬,網易杭州研究院輕舟雲原生內核開發專家,十年以上虛擬化和內核經驗,先後在華爲、網易從事虛擬化和內核相關工作,目前在網易杭研主要負責虛擬化和輕舟容器內核的交付、運維工作和性能調優工作,主要關注運行容器的新一代操作系統技術、混合部署隔離技術以及內核性能調優技術。

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