'陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性'

""陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

請求處理模塊、過濾模塊、負載均衡模塊均遵循unitform pipe and filter架構,每個模塊以統一的接口處理輸入,並以同樣的接口產生輸出,這些模塊串聯在一起提供複雜的功能。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

請求處理模塊、過濾模塊、負載均衡模塊均遵循unitform pipe and filter架構,每個模塊以統一的接口處理輸入,並以同樣的接口產生輸出,這些模塊串聯在一起提供複雜的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx把請求處理流程分為11個階段,所有請求處理模塊必須隸屬於某個階段,或者同時在多個階段中工作。每個處理階段必須依次向後執行,不可跳躍階段執行。

同階段內允許存在多個模塊同時生效,這些模塊串聯在一起有序執行。當然,先執行的模塊還有個特權,它可以決定忽略本階段後續模塊的執行,直接跳躍到下一個階段中的第1個模塊執行。

每個階段的功能單一,每個模塊的功能也很簡單,因此該設計擴展性很好。上圖中的灰色模塊Nginx框架中的請求處理模塊。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

請求處理模塊、過濾模塊、負載均衡模塊均遵循unitform pipe and filter架構,每個模塊以統一的接口處理輸入,並以同樣的接口產生輸出,這些模塊串聯在一起提供複雜的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx把請求處理流程分為11個階段,所有請求處理模塊必須隸屬於某個階段,或者同時在多個階段中工作。每個處理階段必須依次向後執行,不可跳躍階段執行。

同階段內允許存在多個模塊同時生效,這些模塊串聯在一起有序執行。當然,先執行的模塊還有個特權,它可以決定忽略本階段後續模塊的執行,直接跳躍到下一個階段中的第1個模塊執行。

每個階段的功能單一,每個模塊的功能也很簡單,因此該設計擴展性很好。上圖中的灰色模塊Nginx框架中的請求處理模塊。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖中右邊是 Openresty 默認編譯進Nginx的過濾模塊,它們是按序執行的。圖中用紅色框出的是關鍵模塊,它們是必須存在的,而且它們也將其他模塊分為三組,開發第三方過濾模塊時必須先決定自己應在哪一組,再決定自己應在組內的什麼位置。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

請求處理模塊、過濾模塊、負載均衡模塊均遵循unitform pipe and filter架構,每個模塊以統一的接口處理輸入,並以同樣的接口產生輸出,這些模塊串聯在一起提供複雜的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx把請求處理流程分為11個階段,所有請求處理模塊必須隸屬於某個階段,或者同時在多個階段中工作。每個處理階段必須依次向後執行,不可跳躍階段執行。

同階段內允許存在多個模塊同時生效,這些模塊串聯在一起有序執行。當然,先執行的模塊還有個特權,它可以決定忽略本階段後續模塊的執行,直接跳躍到下一個階段中的第1個模塊執行。

每個階段的功能單一,每個模塊的功能也很簡單,因此該設計擴展性很好。上圖中的灰色模塊Nginx框架中的請求處理模塊。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖中右邊是 Openresty 默認編譯進Nginx的過濾模塊,它們是按序執行的。圖中用紅色框出的是關鍵模塊,它們是必須存在的,而且它們也將其他模塊分為三組,開發第三方過濾模塊時必須先決定自己應在哪一組,再決定自己應在組內的什麼位置。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx中的變量分為:提供變量的模塊和使用變量的模塊。其含義我在《Nginx核心知識150講》第72課有介紹,關於框架提供的變量在第73、74課中有介紹。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

請求處理模塊、過濾模塊、負載均衡模塊均遵循unitform pipe and filter架構,每個模塊以統一的接口處理輸入,並以同樣的接口產生輸出,這些模塊串聯在一起提供複雜的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx把請求處理流程分為11個階段,所有請求處理模塊必須隸屬於某個階段,或者同時在多個階段中工作。每個處理階段必須依次向後執行,不可跳躍階段執行。

同階段內允許存在多個模塊同時生效,這些模塊串聯在一起有序執行。當然,先執行的模塊還有個特權,它可以決定忽略本階段後續模塊的執行,直接跳躍到下一個階段中的第1個模塊執行。

每個階段的功能單一,每個模塊的功能也很簡單,因此該設計擴展性很好。上圖中的灰色模塊Nginx框架中的請求處理模塊。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖中右邊是 Openresty 默認編譯進Nginx的過濾模塊,它們是按序執行的。圖中用紅色框出的是關鍵模塊,它們是必須存在的,而且它們也將其他模塊分為三組,開發第三方過濾模塊時必須先決定自己應在哪一組,再決定自己應在組內的什麼位置。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx中的變量分為:提供變量的模塊和使用變量的模塊。其含義我在《Nginx核心知識150講》第72課有介紹,關於框架提供的變量在第73、74課中有介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

無論我們使用了哪些模塊,Nginx框架中的變量一定是默認提供的,它為我們提供了基礎功能,理解好它們是我們使用好Nginx變量的關鍵。

框架變量分為5類:

  • HTTP 請求相關的變量

  • TCP 連接相關的變量

  • Nginx 處理請求過程中產生的變量

  • 發送 HTTP 響應時相關的變量

  • Nginx 系統變量

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

請求處理模塊、過濾模塊、負載均衡模塊均遵循unitform pipe and filter架構,每個模塊以統一的接口處理輸入,並以同樣的接口產生輸出,這些模塊串聯在一起提供複雜的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx把請求處理流程分為11個階段,所有請求處理模塊必須隸屬於某個階段,或者同時在多個階段中工作。每個處理階段必須依次向後執行,不可跳躍階段執行。

同階段內允許存在多個模塊同時生效,這些模塊串聯在一起有序執行。當然,先執行的模塊還有個特權,它可以決定忽略本階段後續模塊的執行,直接跳躍到下一個階段中的第1個模塊執行。

每個階段的功能單一,每個模塊的功能也很簡單,因此該設計擴展性很好。上圖中的灰色模塊Nginx框架中的請求處理模塊。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖中右邊是 Openresty 默認編譯進Nginx的過濾模塊,它們是按序執行的。圖中用紅色框出的是關鍵模塊,它們是必須存在的,而且它們也將其他模塊分為三組,開發第三方過濾模塊時必須先決定自己應在哪一組,再決定自己應在組內的什麼位置。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx中的變量分為:提供變量的模塊和使用變量的模塊。其含義我在《Nginx核心知識150講》第72課有介紹,關於框架提供的變量在第73、74課中有介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

無論我們使用了哪些模塊,Nginx框架中的變量一定是默認提供的,它為我們提供了基礎功能,理解好它們是我們使用好Nginx變量的關鍵。

框架變量分為5類:

  • HTTP 請求相關的變量

  • TCP 連接相關的變量

  • Nginx 處理請求過程中產生的變量

  • 發送 HTTP 響應時相關的變量

  • Nginx 系統變量

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

最後我們來談談Openresty,它其實是Nginx中的一系列模塊構成的,但它由於集成了Lua引擎,又延伸出Lua模塊並構成了新的生態。看看Openresty由哪些部分組成:

  • Nginx,這裡指的是Nginx的框架代碼。

  • Nginx官方模塊,以及各類第三方(非Openresty系列)C模塊。

  • Openresty生態模塊,它包括直接在Nginx中執行的C模塊,例如上圖中的綠色模塊,也包括必須運行在ngx_http_lua_module模塊之上的Lua語言模塊。

  • 當然,Openresty也提供了一些方便使用的腳本工具。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

請求處理模塊、過濾模塊、負載均衡模塊均遵循unitform pipe and filter架構,每個模塊以統一的接口處理輸入,並以同樣的接口產生輸出,這些模塊串聯在一起提供複雜的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx把請求處理流程分為11個階段,所有請求處理模塊必須隸屬於某個階段,或者同時在多個階段中工作。每個處理階段必須依次向後執行,不可跳躍階段執行。

同階段內允許存在多個模塊同時生效,這些模塊串聯在一起有序執行。當然,先執行的模塊還有個特權,它可以決定忽略本階段後續模塊的執行,直接跳躍到下一個階段中的第1個模塊執行。

每個階段的功能單一,每個模塊的功能也很簡單,因此該設計擴展性很好。上圖中的灰色模塊Nginx框架中的請求處理模塊。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖中右邊是 Openresty 默認編譯進Nginx的過濾模塊,它們是按序執行的。圖中用紅色框出的是關鍵模塊,它們是必須存在的,而且它們也將其他模塊分為三組,開發第三方過濾模塊時必須先決定自己應在哪一組,再決定自己應在組內的什麼位置。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx中的變量分為:提供變量的模塊和使用變量的模塊。其含義我在《Nginx核心知識150講》第72課有介紹,關於框架提供的變量在第73、74課中有介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

無論我們使用了哪些模塊,Nginx框架中的變量一定是默認提供的,它為我們提供了基礎功能,理解好它們是我們使用好Nginx變量的關鍵。

框架變量分為5類:

  • HTTP 請求相關的變量

  • TCP 連接相關的變量

  • Nginx 處理請求過程中產生的變量

  • 發送 HTTP 響應時相關的變量

  • Nginx 系統變量

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

最後我們來談談Openresty,它其實是Nginx中的一系列模塊構成的,但它由於集成了Lua引擎,又延伸出Lua模塊並構成了新的生態。看看Openresty由哪些部分組成:

  • Nginx,這裡指的是Nginx的框架代碼。

  • Nginx官方模塊,以及各類第三方(非Openresty系列)C模塊。

  • Openresty生態模塊,它包括直接在Nginx中執行的C模塊,例如上圖中的綠色模塊,也包括必須運行在ngx_http_lua_module模塊之上的Lua語言模塊。

  • 當然,Openresty也提供了一些方便使用的腳本工具。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Openresty中的Lua代碼並不用考慮異步,它是怎麼在Nginx的異步C代碼框架中執行的呢?

我們知道,Nginx框架由事件驅動系統、HTTP框架和STREAM框架組成。而Openresty中的ngx_http_lua_module和ngx_stream_lua_module模塊給Lua語言提供了編程接口,Lua語言通過它們編譯為C代碼在Nginx中執行。

我們在nginx.conf文件中嵌入Lua代碼,而Lua代碼也可以調用上述兩個模塊提供的SDK調動Nginx的功能。

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

請求處理模塊、過濾模塊、負載均衡模塊均遵循unitform pipe and filter架構,每個模塊以統一的接口處理輸入,並以同樣的接口產生輸出,這些模塊串聯在一起提供複雜的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx把請求處理流程分為11個階段,所有請求處理模塊必須隸屬於某個階段,或者同時在多個階段中工作。每個處理階段必須依次向後執行,不可跳躍階段執行。

同階段內允許存在多個模塊同時生效,這些模塊串聯在一起有序執行。當然,先執行的模塊還有個特權,它可以決定忽略本階段後續模塊的執行,直接跳躍到下一個階段中的第1個模塊執行。

每個階段的功能單一,每個模塊的功能也很簡單,因此該設計擴展性很好。上圖中的灰色模塊Nginx框架中的請求處理模塊。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖中右邊是 Openresty 默認編譯進Nginx的過濾模塊,它們是按序執行的。圖中用紅色框出的是關鍵模塊,它們是必須存在的,而且它們也將其他模塊分為三組,開發第三方過濾模塊時必須先決定自己應在哪一組,再決定自己應在組內的什麼位置。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx中的變量分為:提供變量的模塊和使用變量的模塊。其含義我在《Nginx核心知識150講》第72課有介紹,關於框架提供的變量在第73、74課中有介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

無論我們使用了哪些模塊,Nginx框架中的變量一定是默認提供的,它為我們提供了基礎功能,理解好它們是我們使用好Nginx變量的關鍵。

框架變量分為5類:

  • HTTP 請求相關的變量

  • TCP 連接相關的變量

  • Nginx 處理請求過程中產生的變量

  • 發送 HTTP 響應時相關的變量

  • Nginx 系統變量

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

最後我們來談談Openresty,它其實是Nginx中的一系列模塊構成的,但它由於集成了Lua引擎,又延伸出Lua模塊並構成了新的生態。看看Openresty由哪些部分組成:

  • Nginx,這裡指的是Nginx的框架代碼。

  • Nginx官方模塊,以及各類第三方(非Openresty系列)C模塊。

  • Openresty生態模塊,它包括直接在Nginx中執行的C模塊,例如上圖中的綠色模塊,也包括必須運行在ngx_http_lua_module模塊之上的Lua語言模塊。

  • 當然,Openresty也提供了一些方便使用的腳本工具。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Openresty中的Lua代碼並不用考慮異步,它是怎麼在Nginx的異步C代碼框架中執行的呢?

我們知道,Nginx框架由事件驅動系統、HTTP框架和STREAM框架組成。而Openresty中的ngx_http_lua_module和ngx_stream_lua_module模塊給Lua語言提供了編程接口,Lua語言通過它們編譯為C代碼在Nginx中執行。

我們在nginx.conf文件中嵌入Lua代碼,而Lua代碼也可以調用上述兩個模塊提供的SDK調動Nginx的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Openresty的SDK功能強大,我個人把它分為以下8大類:

  • Cosocket提供了類似協程的網絡通訊功能,它的性能優化很到位,許多其他Lua模塊都是基於它實現的。

  • 基於共享內存的字典,它支持多進程使用,所有worker進程間同步通常通過Shared.DICT。

  • 定時器。

  • 基於協程的併發編程。

  • 獲取客戶端請求與響應的信息

  • 修改客戶端請求與響應,包括髮送響應

  • 子請求,官方Nginx就提供了樹狀的子請求,用於實現複雜功能,Openresty用比C簡單的多的Lua語言又實現了一遍。

  • 工具類,大致包含以下5類:

    • 正則表達式

    • 日誌

    • 系統配置

    • 編解碼

    • 時間類

最後做個總結。在恰當的時間做恰當的事,聽起來很美好,但需要我們有大局觀。我們要清楚大規模分佈式網絡通常存在哪些問題,也要清楚分佈式網絡的常用解決方案,然後才能談如何用Nginx解決上述問題。而用好Nginx,必須系統的掌握Nginx的架構與設計原理,理解模塊化設計、階段式設計,清楚Nginx的核心流程,這樣我們才能恰到好處地用Nginx解決掉問題。

說明:以上為《深入理解 Nginx: 模塊開發與架構解析》作者、杭州智鏈達數據有限公司陶輝老師在 GOPS 2019 · 深圳站的分享。

性能的提升永無止境,TLS1.3 及其之上的HTTP3 將於今年推出,想親臨現場聽陶輝老師關於 HTTP 性能極限調優的乾貨分享?來 GOPS 2019 · 上海站就對了!

"陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

講師簡介

陶輝

杭州智鏈達數據有限公司CTO

《深入理解 Nginx:模塊開發與架構解析》作者

本文是我對2019年GOPS深圳站演講的文字整理。這裡我希望帶給各位讀者的是,如何站在整個互聯網背景下系統化地理解Nginx,因為這樣才能解決好大流量分佈式網絡所面臨的高可用問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

本文的題目有“巧用”二字,什麼是巧用?同一個問題會有很多種解決方案,但是,各自的約束性條件卻大不相同。巧用就是找出最簡單、最適合的方案,而做到這一點的前提就是必須系統化的理解Nginx!本文分四個部分講清楚如何達到這一目的:

  1. 首先要搞清楚我們面對的是什麼問題。這裡會談下我對大規模分佈式集群的理解;

  2. Nginx 如何幫助集群實現可伸縮性;

  3. Nginx 如何提高服務的性能;

  4. 從 Nginx 的設計思路上學習如何用好它。

1. 大規模分佈式集群的特點

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網是一個巨大的分佈式網絡,它有以下特點:

  • 多樣化的客戶端。網絡中現存各種不同廠商、不同版本的瀏覽器,甚至有些用戶還在使用非常古老的瀏覽器,而我們沒有辦法強制用戶升級;

  • 多層代理。我們不知道用戶發來的請求是不是通過代理翻牆過來的;

  • 多級緩存。請求鏈路上有很多級緩存,瀏覽器、正反向代理、CDN等都有緩存,怎麼控制多級緩存?RFC規範中有明確的定義,但是有些Server並不完全遵守;

  • 不可控的流量風暴。不知道用戶來自於哪些地區,不知道他們會在哪個時間點集中訪問,不知道什麼事件會觸發流量風暴;

  • 網絡安全的高要求:信息安全問題要求通信數據必須加密;

  • 快速迭代的業務需求:BS架構使軟件開發方式發生了巨大變化,我們可以通過快速迭代、發佈來快速驗證、試錯。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是典型的REST架構,圖中包括客戶端、正反向代理、源服務器,$符號代表緩存可以服務於上游,也可以服務於下游。

通過IP地址標識主機,通過域名系統簡化使用,URI則指向具體資源,每種資源有許多種表述,而服務器通過HTTP協議將表述轉移至客戶端上展示。這便是REST名為表述性狀態轉移的緣由,我在極客時間《Web協議詳解與抓包實戰》課程第7、8節課中對此有詳細的介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

設計架構時有許多關注點,與本文主題相關的有4個要點:

  • 可伸縮性。核心點在於如何有效的、動態的、灰度的均衡負載。

  • 可擴展性指功能組件的獨立進化。可以理解為某個 Nginx 模塊獨立升級後,並不影響Nginx整體服務的屬性。

  • 網絡效率,也就是如何提升信息傳輸的效率。

  • HTTP協議功能的全面支持。HTTP1的RFC規範非常多,畢竟它經歷了20多年的變遷,而這20多年裡互聯網的巨大變化是HTTP1的設計者無法預料到的,這些規範也並不被所有Server、Client支持。 當然HTTP2和HTTP3相對情況會好很多。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性
  • Nginx有優秀的可插拔模塊化設計,它基於統一管道架構。

    • 其中有一類模塊我稱它為 upstream 負載均衡模塊,官方 Nginx 便提供了最小連接、RoundRobin、基於變量控制的 hash、一致性 hash 等負載均衡策略,而大量的第三方模塊更提供了許多定製化的負載均衡算法。

    • 基於 Lua 語言的 Openresty 有自己的生態,這些 Lua 模塊也提供了更靈活的實現方式。

  • Nginx 在性能優化上做得非常極致,大家知道最近F5收購了 Nginx 公司,為什麼要收購?因為 Nginx 的性能可以與基於硬件的、價格昂貴的F5媲美!

  • Nginx 對 HTTP 協議的支持是比較全面的,當我們使用一些小眾的替代解決方案時,一定要明確自己在HTTP協議有哪些獨特需求。

  • 優秀的可配置性,在nginx.conf配置文件裡我們可以使用腳本指令與變量實現複雜的功能。

2. Nginx與scalability

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

在討論 Nginx 的負載均衡策略前,我們先來了解AKF擴展立方體,它能使我們對此建立整體思維。AKF擴展立方體有X、Y、Z軸,這三個軸意味著可以從3個角度實現可伸縮性:

  • X軸指只需要增加應用進程,不用改代碼就能水平的擴展。雖然最方便 ,但它解決不了數據不斷增長的問題。

  • Y軸按功能切分應用,它能解決數據增長的問題,但是,切分功能意味著重構代碼,它引入了複雜性,成本很高。

  • Z軸基於用戶的屬性擴展服務,運維Nginx時這招我們最常用,通常我們基於變量取到用戶的IP地址、URL或者其他參數來執行負載均衡。

當然,這三個軸可以任意組合以應對現實中的複雜問題。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當然,要想解決可伸縮性問題,還必須在功能上支持足夠多的協議。面向下游客戶端主要是HTTP協議,當然Nginx也支持OSI傳輸層的UDP協議和TCP協議。受益於Nginx優秀的模塊化設計,對上游服務器Nginx支持非常多的應用層協議,如grpc、uwsgi等。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖是Nginx執行反向代理的流程圖,紅色是負載均衡模塊,任何一個獨立的開發者都可以通過開發模塊來添加新的LB策略。

Nginx必須解決無狀態HTTP協議帶來的信息冗餘及性能低下問題,而Cache緩存是最重要的解決手段,我們需要對Cache在反向代理流程中的作用有所瞭解。當下遊是公網帶寬並不穩定,且單用戶信道較小時,通常Nginx應緩存請求body,延遲對上游應用服務建立連接的時間;反之,若上游服務的帶寬不穩定,則應緩存響應body。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

理解nginx配置文件的3個關鍵點是:

  1. 多級指令配置。通過大括號{},我們可以層層嵌套指令,借用父子關係來模塊化的配置代碼。

  2. 變量,這是我們實現複雜功能,且不影響Nginx模塊化設計的關鍵。變量是不同模塊間低耦合交互的最有效方式!

  3. 腳本引擎。腳本指令可以提供應用編程功能。很多人說Nginx的if指令是邪惡的,比如上圖中的代碼,其實我們只有理解if指令是如何影響父子嵌套關係後,才能正確的使用if。在《Nginx核心知識150講》第141課我有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx官方迭代速度很快,在前兩年差不多是兩週一個版本,現在是一個月一個版本。頻繁的更新解決了Bug也推出了新功能。但我們更新Nginx時卻不能像更新其他服務一樣,因為Nginx上任一時刻處理的TCP連接都太多了,如果升級Nginx時不能很好的應對就會出現大規模的用戶體驗問題。

Nginx採用多進程結構來解決升級問題。它的master進程是管理進程,為所有worker進程保留住Syn半連接隊列,所以升級Nginx時不會導致大規模三次握手失敗。相反,單進程的HAProxy升級時就會出現連接建立失敗問題。

3. Nginx與集群performance

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

緩存有兩個實現維度:時間與空間。基於空間的緩存需要基於信息來預測,提前把用戶可能請求的字節流準備好。而基於時間的緩存如上圖所示,藍色線條的請求觸發了緩存(public share cache),這樣紅色線條的第二次請求可以直接命中緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

瀏覽器中的是私有緩存,私有緩存只為一個用戶服務。Nginx上實現了共享緩存,同時Nginx也可以控制瀏覽器中私有緩存的有效時間。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

RFC規範定義了許多緩存相關的頭部,如果我們忽略了這些規則會很難理解Nginx如何基於下游的請求、上游的響應控制私有緩存及共享緩存,而且不瞭解這些規則其實不容易讀懂nginx.conf中緩存相關指令的說明文檔。在《Web協議詳解與抓包實戰》課程第29到32課我詳細的介紹了緩存相關的規則。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

有些同學會問我,為什麼部署Nginx之後沒有看到上圖中的Cache Loader和Cache Manger進程呢?因為我們沒有啟用Nginx的緩存。當然,即使我們開啟緩存後,Cache Loader進程可能還是看不到的。

為什麼呢?因為 Nginx 為了高性能做了很多工作。當重啟Nginx時,之前保存在磁盤上的緩存文件需要讀入內存建立索引,但讀文件的IO速度是很慢的,讀緩存文件(文件很大很多)這一步驟可能耗時非常久,對服務器的負載很大,這會影響worker進程服務用戶請求的能力。CL進程負責每次只讀一小部分內容到共享內存中,這大大緩解了讀IO慢的問題。CM進程負責淘汰過期緩存。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

當下遊有一份過期資源時,它會來詢問Nginx時:此資源還能用嗎?能用的話,通過304告訴我,不要返回響應body(可能很大!)了。

當Nginx緩存的資源可能過期時,它也可以問上游的web應用服務器:緩存還能用嗎?能用的話通過304告訴我,我來更新緩存Age。

RFC7033文檔詳細定義了這一過程,我在《Web協議詳解與抓包實戰》第28課有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx的not_modified過濾模塊便負責執行這一功能。我在《Nginx核心知識150講》課程第97、98課對此有詳細介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

如果我們突然發佈了一個熱點資源,許多用戶請求瞬間抵達訪問該資源,可是該資源可能是一個視頻文件尺寸很大,Nginx上還沒有建立起它的緩存,如果Nginx放任這些請求直達上游應用服務器(比如可能是Tomcat),非常可能直接把上游服務器打掛了。因為上游應用服務器為了便於功能的快速迭代開發,性能上是不能與Nginx相提並論的。這就需要合併回源請求。

怎麼合併回源請求呢?第一個請求過來了,放行!第二個請求也到了,但因為第1個請求還沒有完成,所以上圖中的請求2、4、5都不放行,直到第6步第1個請求的響應返回後,再把緩存的內容作為響應在第8、9、10中返回。這樣就能緩解上游服務的壓力。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

減少回源請求是一個解決方案,但如果Nginx上有過期的響應,能不能先將就著發給用戶?當然,同時也會通過條件請求去上游應用那裡獲取最新的緩存。我們經常提到的互聯網柔性、分級服務的原理與此是相同的。既然最新內容暫時由於帶寬、性能等因素不能提供,不如先提供過期的內容,當然前提是不對業務產生嚴重影響。

Nginx中的proxy_cache_use_stale指令允許使用stale過期緩存,上圖中第1個請求放行了,第2、3請求使用舊緩存。從這裡可以看出Nginx應對大流量有許多成熟的方案。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

我們在網頁上會使用播放條拖動著看視頻,這可以基於Http Range協議實現。但是,如果不啟用Slice模塊Nginx就會出現性能問題,比如現在瀏覽器要訪問一個視頻文件的第150-249字節,由於滿足了緩存條件,Nginx試圖先把文件拉取過來緩存,再返回響應。然而,Nginx會拉取完整的文件緩存!這是很慢的。

怎麼解決這個問題呢?使用Nginx的slice模塊即可,如果配置100字節作為基礎塊大小,Nginx會基於100-199、200-299產生2個請求,這2個請求的應用返回並存入緩存後再構造出150-249字節的響應返回給用戶。這樣效率就高很多!通常,Nginx作為CDN使用時都會打開這一功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

互聯網解決信息安全的方案是TLS/SSL協議,Nginx對其有很好的支持。比如,Nginx把下游公網發來的TLS流量卸載掉TLS層,再轉發給上游;同時,它也可以把下游傳輸來的HTTP流量 ,根據配置的證書轉換為HTTPS流量。在驗證證書時,在nginx.conf中我們可以通過變量實現證書或者域名驗證。

雖然TLS工作在OSI模型的表示層,但Nginx作為四層負載均衡時仍然可以執行同樣的增、刪TLS層功能。Nginx的Stream模塊也允許在nginx.conf中通過變量驗證證書。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx處理TLS層性能非常好,這得益於2點:

  • Nginx本身的代碼很高效,這既因為它基於C語言,也由於它具備優秀的設計。

  • 減少TLS握手次數,包括:

    • session緩存。減少TLS1.2握手中1次RTT的時間,當然它對集群的支持並不好,而且比較消耗內存。

    • Ticket票據。Ticket票據可應用於集群,且並不佔用內存。

當然,減少TLS握手的這2個策略都面臨著重放攻擊的危險,更好的方式是升級到TLS1.3。我在《Web協議詳解與抓包實戰》第80課有詳細介紹。

4. 巧用Nginx

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx模塊眾多,我個人把它分為四類,這四類模塊各自有其不同的設計原則。

  • 請求處理模塊。負責生成響應或者影響後續的處理模塊,請求處理模塊遵循請求階段設計,在同階段內按序處理。

  • 過濾模塊。生成了HTTP響應後,此類模塊可以對響應做再加工。

  • 僅影響變量的模塊。這類模塊為其他模塊的指令賦能,它們提供新的變量或者修改已有的變量。

  • 負載均衡模塊。它們提供選擇上游服務器的負載均衡算法,並可以管理上游連接。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

請求處理模塊、過濾模塊、負載均衡模塊均遵循unitform pipe and filter架構,每個模塊以統一的接口處理輸入,並以同樣的接口產生輸出,這些模塊串聯在一起提供複雜的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx把請求處理流程分為11個階段,所有請求處理模塊必須隸屬於某個階段,或者同時在多個階段中工作。每個處理階段必須依次向後執行,不可跳躍階段執行。

同階段內允許存在多個模塊同時生效,這些模塊串聯在一起有序執行。當然,先執行的模塊還有個特權,它可以決定忽略本階段後續模塊的執行,直接跳躍到下一個階段中的第1個模塊執行。

每個階段的功能單一,每個模塊的功能也很簡單,因此該設計擴展性很好。上圖中的灰色模塊Nginx框架中的請求處理模塊。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

上圖中右邊是 Openresty 默認編譯進Nginx的過濾模塊,它們是按序執行的。圖中用紅色框出的是關鍵模塊,它們是必須存在的,而且它們也將其他模塊分為三組,開發第三方過濾模塊時必須先決定自己應在哪一組,再決定自己應在組內的什麼位置。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Nginx中的變量分為:提供變量的模塊和使用變量的模塊。其含義我在《Nginx核心知識150講》第72課有介紹,關於框架提供的變量在第73、74課中有介紹。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

無論我們使用了哪些模塊,Nginx框架中的變量一定是默認提供的,它為我們提供了基礎功能,理解好它們是我們使用好Nginx變量的關鍵。

框架變量分為5類:

  • HTTP 請求相關的變量

  • TCP 連接相關的變量

  • Nginx 處理請求過程中產生的變量

  • 發送 HTTP 響應時相關的變量

  • Nginx 系統變量

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

最後我們來談談Openresty,它其實是Nginx中的一系列模塊構成的,但它由於集成了Lua引擎,又延伸出Lua模塊並構成了新的生態。看看Openresty由哪些部分組成:

  • Nginx,這裡指的是Nginx的框架代碼。

  • Nginx官方模塊,以及各類第三方(非Openresty系列)C模塊。

  • Openresty生態模塊,它包括直接在Nginx中執行的C模塊,例如上圖中的綠色模塊,也包括必須運行在ngx_http_lua_module模塊之上的Lua語言模塊。

  • 當然,Openresty也提供了一些方便使用的腳本工具。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Openresty中的Lua代碼並不用考慮異步,它是怎麼在Nginx的異步C代碼框架中執行的呢?

我們知道,Nginx框架由事件驅動系統、HTTP框架和STREAM框架組成。而Openresty中的ngx_http_lua_module和ngx_stream_lua_module模塊給Lua語言提供了編程接口,Lua語言通過它們編譯為C代碼在Nginx中執行。

我們在nginx.conf文件中嵌入Lua代碼,而Lua代碼也可以調用上述兩個模塊提供的SDK調動Nginx的功能。

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性

Openresty的SDK功能強大,我個人把它分為以下8大類:

  • Cosocket提供了類似協程的網絡通訊功能,它的性能優化很到位,許多其他Lua模塊都是基於它實現的。

  • 基於共享內存的字典,它支持多進程使用,所有worker進程間同步通常通過Shared.DICT。

  • 定時器。

  • 基於協程的併發編程。

  • 獲取客戶端請求與響應的信息

  • 修改客戶端請求與響應,包括髮送響應

  • 子請求,官方Nginx就提供了樹狀的子請求,用於實現複雜功能,Openresty用比C簡單的多的Lua語言又實現了一遍。

  • 工具類,大致包含以下5類:

    • 正則表達式

    • 日誌

    • 系統配置

    • 編解碼

    • 時間類

最後做個總結。在恰當的時間做恰當的事,聽起來很美好,但需要我們有大局觀。我們要清楚大規模分佈式網絡通常存在哪些問題,也要清楚分佈式網絡的常用解決方案,然後才能談如何用Nginx解決上述問題。而用好Nginx,必須系統的掌握Nginx的架構與設計原理,理解模塊化設計、階段式設計,清楚Nginx的核心流程,這樣我們才能恰到好處地用Nginx解決掉問題。

說明:以上為《深入理解 Nginx: 模塊開發與架構解析》作者、杭州智鏈達數據有限公司陶輝老師在 GOPS 2019 · 深圳站的分享。

性能的提升永無止境,TLS1.3 及其之上的HTTP3 將於今年推出,想親臨現場聽陶輝老師關於 HTTP 性能極限調優的乾貨分享?來 GOPS 2019 · 上海站就對了!

陶輝:巧用 Nginx 實現大規模分佈式集群的高可用性"

相關推薦

推薦中...