中间件业务在网易轻舟容器平台的性能调优实践
隨著業務容器化的推進,經常有客戶抱怨應用 QPS 無法和在物理機或者云主機上媲美,并且時常會出現 DNS 查詢超時、短連接 TIME_OUT、網絡丟包等問題,而在容器中進行調優與診斷的效果因為安裝工具的復雜度大打折扣。本文基于網易輕舟中間件業務容器化實踐,總結容器場景下的性能調優心得,供讀者參考。
1 性能調優的“望聞問切”
在討論容器化場景的性能調優之前,先談一下性能調優中的“望聞問切”。對于性能問題,大部分人首先想到的是 CPU 利用率高,但這只是個現象,并不是癥狀。打個比方:感冒看醫生時,病人跟大夫描述的是現象,包括頭部發熱、流鼻涕等;而大夫通過探查、化驗,得到的醫學癥狀是病人的白細胞較多、咽喉紅腫等,并確診為細菌性感冒,給開了 999 感冒靈。性能調優流程與此相似,也需要找到現象、癥狀和解法。回到 CPU 利用率高的例子:已知現象是 CPU 利用率高,我們通過 strace 檢查,發現 futex_wait 系統調用占用了 80% 的 CPU 時間——這才是癥狀;根據這個癥狀,我們業務邏輯代碼降低了線程切換,CPU 利用率隨之降低。
大部分的性能調優都可以通過發現現象、探測癥狀、解決問題這三個步驟來完成,而這在容器的性能調優中就更為重要的,因為在主機的性能調優過程中,我們有很多的經驗可以快速找到癥狀,但是在容器的場景中,很多客戶只能描述問題的現象,因為他們并不了解使用的容器引擎的工作原理以及容器化架構的實現方式。
2 容器化性能調優的難點
容器化場景中的性能調優主要面臨 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 的詭異行為。環境的問題本就需要嚴格管控,但是當遇到了容器,很多時候我們會分不清哪些不經意的行為會帶來嚴重的問題,警惕性因為容器鏡像能夠正常啟動而降低了。
3 性能優化步驟和調優管理變更流程
性能優化通常可以通過如表五個步驟完成:
調優管理變更和性能優化并不直接相關,但可能是性能調優成功最重要的因素。以下總結來源于網易輕舟團隊的實踐和經驗:
-
在調優之前,實施合理的調優管理流程變更
-
永遠不要在生產系統上調優
-
壓測的環境要相對獨立,不能受其他壓力的影響,這里說的不僅是壓測的服務器端,而且包括壓測客戶端
-
在調優過程中,每次只修改一個變量
-
反復測試提升性能的參數,有時候,統計來的結果更加可靠
-
把成功的參數調整整理成文檔,和社區分享,即使你覺得它們微不足道
-
生產環境中獲得的任何結果對 Linux 性能都有很大用處
4 調優手段
1. 通用調優
對于容器業務來說,盡量讓 CPU 訪問本地內存,不要訪問遠端內存。
CPU 訪問不同類型節點內存的速度是不相同的,訪問本地節點的速度最快,訪問遠端節點的速度最慢,即訪問速度與節點的距離有關,距離越遠訪問速度越慢,此距離稱作 Node Distance。正是因為有這個特點,容器應用程序要盡量的減少不同 Node 模塊之間的交互,也就是說,我們根據容器內存 Node 親和性,選擇容器使用的 CPU 固定在一個 Node 模塊里,因此其性能將會有很大的提升。有一種特殊場景除外,如果一個容器申請的 request 大于單個 Node 上預留的 CPU 后,這種親和性的綁定就會失效,此時回歸到原始的跨 Node 范圍綁定,對于之前已經做了親和性的容器(申請的 request 小于單個 Node 上預留的 CPU)我們的策略是繼續維持不變。
2. 針對不同的場景的參數調優
針對不同的場景,可以考慮以下參數著手進行調優。
與 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 相關的配置
| 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。
3. 網絡調優
軟中斷隔離:將容器網卡的軟中斷綁定到某幾個專用 CPU 上,而容器業務進程綁定到其他 CPU 上,這樣可以減少業務和網卡軟中斷之間的影響,避免頻繁的上下文切換,特別適用于對網絡性能要求極高的服務例如 Redis。對于云內普通 VPC 和云內 SR-IOV VPC 都適用:
-
云內普通 VPC:需要把容器使用的虛擬網卡 veth 的軟中斷綁定到預留的某幾個專用 CPU 上
-
云內 SR-IOV VPC:需要把容器使用的 VF 網卡的軟中斷綁定到預留的某幾個專用 CPU 上
-
SR-IOV 直通網卡:優化后端 DPDK PMD 線程配比,一般由 1 個調大至 2 個或者 4 個。
5 調優效果
?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%。
?作者簡介
劉迎冬,網易杭州研究院輕舟云原生內核開發專家,十年以上虛擬化和內核經驗,先后在華為、網易從事虛擬化和內核相關工作,目前在網易杭研主要負責虛擬化和輕舟容器內核的交付、運維工作和性能調優工作,主要關注運行容器的新一代操作系統技術、混合部署隔離技術以及內核性能調優技術。
總結
以上是生活随笔為你收集整理的中间件业务在网易轻舟容器平台的性能调优实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试常考的树,我这样讲给你听!
- 下一篇: 谁能想到,我给技术总监“上了一课”?