1. 應用服務 1.1. 任何系統的核心都在於實現應用需求的特定業務邏輯 1.2. 服務是可擴展軟體系統的核心 1.2.1. 它們將契約定義為一個API,向客戶端聲明它們的能力 1.3. 應用伺服器高度依賴於編程語言,但通常都會提供多線程編程模型,允許服務同時處理許多請求 1.4. 多服務配置意味著 ...
1. 應用服務
1.1. 任何系統的核心都在於實現應用需求的特定業務邏輯
1.2. 服務是可擴展軟體系統的核心
- 1.2.1. 它們將契約定義為一個API,向客戶端聲明它們的能力
1.3. 應用伺服器高度依賴於編程語言,但通常都會提供多線程編程模型,允許服務同時處理許多請求
1.4. 多服務配置意味著應用程式可以容忍單個實例的故障
1.5. 無狀態服務允許負載均衡器簡單地將請求重新發送到響應目標,從而輕鬆實現擴展並簡化故障場景
1.6. 大多數負載均衡器採用被稱為粘性會話的特性來支持有狀態服務,但有狀態服務會使得負載均衡和處理故障更加複雜
- 1.6.1. 不建議在高度可擴展的系統中採用有狀態服務
1.7. Java企業版(JEE)是一種成熟且被廣泛部署的伺服器端技術,它在不同範圍提供了高度抽象的規範,可用於構建豐富而強大的服務
2. 服務設計
2.1. API
-
2.1.1. Application Programming Interface,應用程式編程介面
-
2.1.2. 定義了客戶端與伺服器之間的契約
-
2.1.3. 指定了可能的請求類型、請求所需攜帶的數據以及請求所將獲得的結果
-
2.1.4. 主要風格還是基於HTTP的,它們通常被歸類為RESTful
-
2.1.4.1. REST是Roy T. Fielding在他的博士論文中定義的一種架構風格
-
2.1.4.2. HTTP中API的創建、讀取、更新和刪除(簡稱CRUD)模式
> 2.1.4.2.1. 有效利用了四個HTTP動詞,即POST、GET、PUT和DELETE
- 2.1.4.3. HTTP PATCH動詞來更新資源的各個屬性
> 2.1.4.3.1. PATCH允許對一個資源的部分屬性進行修改,而PUT則是將一個資源完整地替換成新的資源
-
2.1.4.4. 有效負載通常被格式化成JSON,當然XML和其他格式也是有可能的
-
2.1.4.5. HTTP動詞與URI組合在一起定義了API操作的語義
> 2.1.4.5.1. 用URI表示的資源在概念上類似於面向對象設計(OOD)中的對象或實體關係模型(ER模型)中的實體
-
2.1.5. HTTP API可以採用OpenAPI來聲明
-
2.1.5.1. SwaggerHub工具是OpenAPI中聲明API的事實標準
-
2.1.5.2. 該規範是用YAML標記語言來定義的
-
2.1.6. 一種常見的反模式是Chatty API,它使用多個API請求來執行一個邏輯操作
-
2.1.7. 對傳遞大負載的HTTP API進行壓縮
-
2.1.7.1. 所有現代Web伺服器和瀏覽器都支持採用Accept-Encoding和Content-Encoding的HTTP消息頭的壓縮內容
-
2.1.7.2. 壓縮可以將網路帶寬和延遲減少50%甚至更多
-
2.1.7.3. 要權衡的是壓縮和解壓縮內容過程中消耗的計算時間,與節省的網路傳輸時間相比一般都很小
2.2. 設計服務
-
2.2.1. 應用伺服器容器接收請求並將它們路由到適當的處理函數來處理請求
-
2.2.2. Spring框架利用一組註解來簡化服務代碼,註解定義依賴關係和實現依賴註入
2.3. 狀態管理
-
2.3.1. 實現可擴展服務的底線是要避免存儲會話的狀態
-
2.3.2. HTTP是無狀態的協議
-
2.3.2.1. 意味著每個請求都是獨立執行的,對來自同一個客戶端的早於它執行的請求一無所知
-
2.3.2.2. 無狀態意味著每個請求都需要是自包含的,不管客戶端之前的行為如何,客戶端要為Web伺服器提供足夠的信息來滿足請求
-
2.3.3. HTTP支持cookie,就是大家所知的HTTP狀態管理機制
-
2.3.4. HTTP/2支持流、壓縮和加密,所有這些行為都需要狀態管理
-
2.3.5. 客戶端和伺服器之間底層的socket連接保持打開狀態,這樣可以分攤同一個客戶端多個請求創建連接的開銷
-
2.3.5.1. HTTP/1及以上版本的預設行為
-
2.3.6. 會話狀態指的是在請求之間保留的所有信息,後續請求可以假定服務已保留有關先前交互的信息
-
2.3.6.1. 如果你有多個客戶端會話都要維護會話狀態,這將占用可用的服務記憶體
> 2.3.6.1.1. 占用的記憶體量將與服務所維護的客戶端數量成正比
- 2.3.6.2. 還必須註意保持會話狀態的時長
> 2.3.6.2.1. 如果你將此設置為較短的時長,客戶端可能會出現非預期的會話狀態丟失
> 2.3.6.2.2. 如果你將會話超時期限設置得太長,則可能會因為資源不足而降低服務性能
-
2.3.7. 無狀態服務不會假定之前調用的任何會話狀態已被保留
-
2.3.7.1. 服務不應該維護來自之前請求的任何信息,每一個請求都可以獨立處理
-
2.3.7.2. 無狀態服務要求客戶端提供所有必要的信息,以便服務處理請求並提供響應
-
2.3.8. 任何可擴展的服務都基於無狀態的API
-
2.3.9. 最重要的設計準則是,對於需要保留與客戶端會話相關的狀態的服務,狀態必須存儲在服務的外部,即外部數據存儲
3. 應用伺服器
3.1. 應用伺服器是可擴展應用程式的核心,它托管了組成應用程式的業務服務
3.2. 其基本作用是接收來自客戶端的請求,將程式邏輯應用於請求,並將請求結果返回給客戶端
3.3. Tomcat是JEE平臺的一個技術子集的開源實現,即Java servlet、Java Server Page(JSP)、Java Expression Language(EL)和Java WebSocket技術
3.4. 如果收到的請求多於伺服器可以排隊和處理的請求,那麼新的TCP/IP連接將被拒絕,客戶端將看到一些錯誤
-
3.4.1. 最終,超負荷的伺服器可能會因為資源耗盡而拋出異常並崩潰
-
3.4.2. 花時間調整配置參數以有效處理預期負載是值得的
-
3.4.3. 系統在達到100%利用率之前往往會降低性能
-
3.4.3.1. 一旦任何資源(CPU利用率、記憶體使用、網路、磁碟訪問等)接近100%利用率,系統就會表現出難以預測的性能問題
4. 水平擴展
4.1. 擴展系統的核心原則是能夠輕鬆添加新的處理能力來處理增加的負載
4.2. 部署多個無狀態伺服器資源實例,使用負載均衡器在實例之間分配請求
4.3. 無狀態服務副本和負載均衡器都是水平擴展所必需的
4.4. 服務副本部署在它們自己的(虛擬)硬體上
-
4.4.1. 增加的處理能力使系統能夠處理增加的負載
-
4.4.2. 水平擴展的目的是創建一個系統,它的處理能力是所有可用資源的總和
-
4.4.3. 伺服器需要是無狀態的,任何請求都可以發送到任何服務副本來處理
-
4.4.4. 發送到哪個服務副本是由負載均衡器決定的,它可以使用各種策略來分發請求
-
4.4.5. 如果負載均衡器可以使每個服務副本保持同樣繁忙,那麼我們就能有效地利用服務副本提供的處理能力
4.5. 如果我們的服務是有狀態的,那麼負載均衡器需要始終將來自同一伺服器的請求路由到同一個服務副本
- 4.5.1. 由於客戶端會話的持續時間是不確定的,這就可能導致某些服務副本的負載比其他副本高得多
4.6. 水平擴展也提高了可用性
-
4.6.1. 單服務實例如果失效了,服務就不可用了
-
4.6.1.1. 單點故障(SPoF)
-
4.6.1.2. 在任何可擴展的分散式系統中都應避免的一個問題
-
4.6.2. 多個副本提高了可用性。如果一個副本失敗,可以將請求定向到其他任何副本
5. 負載均衡
5.1. 負載均衡旨在有效利用服務集合的容量來優化每個請求的響應時間
5.2. 目的是避免某些服務超載而其他的服務利用不足
5.3. 客戶端向負載均衡器的IP地址發送請求,負載均衡器將請求重定向到目標服務,並將結果轉發回客戶端
- 5.3.1. 客戶端永遠不會直接與目標服務通信,這也有利於安全,因為服務可以存在於安全邊界之後並且不會暴露在互聯網上
5.4. 負載均衡器可以是網路級別或應用級別的,通常被稱為四層和七層負載均衡器,分別對應開放系統互連(OSI)參考模型中第4層的網路傳輸層和第7層的應用層
-
5.4.1. 網路級別的負載均衡器在網路連接級別分發請求,對單個TCP或UDP數據包進行操作
-
5.4.1.1. 路由決策是根據客戶端IP地址做出的
-
5.4.1.2. 負載均衡器就會使用一種被稱為網路地址轉換(NAT)的技術,將客戶端請求數據包中的目標IP地址從負載均衡器的地址更改為所選目標的地址
-
5.4.1.3. 當收到來自目標服務的響應時,負載均衡器將記錄在數據包頭中的源地址從目標服務的IP地址更改為自己的IP地址
-
5.4.1.4. 除了選擇目標服務和執行NAT功能之外,它們提供的功能很少
-
5.4.2. 應用級別的負載均衡器會重新組裝整個HTTP請求,並根據HTTP頭的值和消息的實際內容做出路由決策
-
5.4.2.1. 應用負載均衡器是複雜的反向代理
-
5.4.2.2. 意味著它們比網路負載均衡器稍微慢一些,但它們提供的強大功能可以用來彌補所產生的開銷
5.5. 負載分配策略
-
5.5.1. 決定了負載均衡器如何選擇目標服務來處理請求
-
5.5.2. 迴圈
-
5.5.2.1. 負載均衡器以迴圈(round robin)方式將請求分發到可用的伺服器
-
5.5.3. 最少連接數
-
5.5.3.1. 負載均衡器將新請求分發到打開連接數最少的一個伺服器
-
5.5.4. HTTP頭欄位
-
5.5.4.1. 負載均衡器根據特定HTTP頭欄位的內容來引導請求
-
5.5.5. HTTP操作
-
5.5.5.1. 負載均衡器根據請求中的HTTP動詞來引導請求
-
5.5.6. 負載均衡器還可以為服務分配權重
5.6. 健康檢測
-
5.6.1. 負載均衡器會定期給負載均衡池中的每個服務發送ping命令並嘗試連接以測試服務的健康狀況
-
5.6.1.1. 這些測試就被稱為健康檢查
-
5.6.2. 如果與服務的連接只是發生暫時性故障,負載均衡器將會在服務變為健康可用後重新添加進來
5.7. 彈性
-
5.7.1. 請求負載急劇增加會導致負載均衡器的服務容量達到飽和,導致更長的響應時間,並最終導致請求和連接失敗
-
5.7.2. 彈性指的是應用動態地提供新的服務容量以處理新增請求的一種能力
-
5.7.3. 隨著請求負載的增加,新的服務副本會被啟動,負載均衡器將請求定向到新的副本
-
5.7.4. 隨著負載的減少,負載均衡器則會停止不再需要的服務副本
-
5.7.5. 彈性要求負載均衡器與應用監控緊密集成,以便定義擴展策略來確定何時增加和減少服務容量
-
5.7.6. AWS Auto Scaling組是彈性負載均衡的一個實例
-
5.7.6.1. 定義了集合中服務實例數量的最大值和最小值
-
5.7.6.2. 負載均衡器將確保組中始終具有最小數量的可用服務,並且永遠不會超過最大數量
-
5.7.6.3. 有兩種方法可以控制一個組中的副本數量
> 5.7.6.3.1. 一種是基於時間計劃表,相關用例的請求負載的增加和減少是可預測的
> 5.7.6.3.2. 如果無法預測增加的負載峰值,則可以根據應用指標(例如平均CPU和記憶體使用率以及隊列中的消息數量)定義擴展策略來動態控制
> 5.7.6.3.2.1. 如果超過策略的上限閾值,那麼負載均衡器將啟動一個或多個新的服務實例,直至性能下降到指標閾值以下
-
5.7.7. 彈性是一個非常關鍵的特性,它允許服務隨著需求的增長而動態擴展
-
5.7.7.1. 對於負載頻繁波動的高度可擴展系統,它幾乎是一種以最低成本提供所需容量的必備能力
5.8. 會話保持(session affinity)
-
5.8.1. 會話保持或粘性會話(sticky session)是有狀態服務的負載均衡器特性
-
5.8.2. 使用粘性會話,負載均衡器會將來自同一客戶端的所有請求發送到同一服務實例
-
5.8.3. 對於高度可擴展的系統來說,粘性會話可能會有問題
-
5.8.3.1. 它們會導致負載不均衡的問題,隨著時間的推移,客戶端不會在服務之間均勻分佈
-
5.8.3.2. 發生負載不均衡的原因是客戶端會話保持的時間不同
> 5.8.3.2.1. 在一個擁有百萬級會話的系統中,持續不斷地創建和銷毀會話,負載不均衡是不可避免的
> 5.8.3.2.2. 這將導致一些服務副本不能被充分利用,而另一些則不堪重負,並可能由於資源耗盡而失敗
-
5.8.4. 通常,有狀態伺服器會產生在大規模系統中難以設計和管理的問題
-
5.8.5. 如果服務由於短暫的網路中斷而變慢,負載均衡器會將其從服務組中取出,直到它通過健康檢查或失敗
-
5.8.6. 無狀態服務提升了可擴展性,簡化了故障場景,並減輕了服務管理的負擔
5.9. 通過負載均衡擴展一組服務可能會導致負載均衡服務所依賴的下游服務或資料庫不堪重負