在高并發場景下,送你這三根“救命毫毛”
高可用是指,系統在面對各種挑戰時,如硬件故障、軟件缺陷、網絡問題等,依然能夠持續提供服務。高可用通常用“五個九”(99.999%的可用性)這樣的術語來衡量,意味著系統一年中的停機時間只有幾分鐘。
分布式系統的高可用架構設計如圖所示:
- 用戶請求:所有用戶的請求都首先到達負載均衡器。
- 負載均衡器:它是高可用架構的關鍵組成部分。它接收來自用戶的請求,并將其均勻地分配給多個服務器,如服務器A和服務器B。這樣做的目的是防止任何單個服務器過載并提高資源利用率。負載均衡器還負責進行健康檢查,以確保所有流量僅被轉發到健康的服務器。
- 服務器A和服務器B:代表應用服務器的集群,每個服務器都能獨立處理請求。這些服務器是冗余的,如果一個服務器失敗,則負載均衡器可以將流量重定向到其他健康的服務器。
- 數據庫A和數據庫B:代表數據層的冗余,確保數據的持久性和可用性。通過主從復制或其他同步機制,可以在一個數據庫發生故障時快速切換到另一個數據庫,以維護服務的持續性。
- 監控系統:負載均衡器也可以連接一個監控系統,用來監控整個架構的健康狀態,包括服務器和數據庫的性能指標。監控系統可以自動響應某些事件,如在檢測到服務器故障時,自動從服務器池中剔除故障服務器,或者在資源使用率達到某個閾值時觸發擴容。
圖片
【實戰1】在高并發場景下,使用“限流”防止系統崩潰
在設計高可用架構時,除需要考慮冗余和備份機制外,還需要實施一系列服務治理策略,來確保系統在面對異常流量、服務依賴故障等特殊情況時的穩定性和彈性。這些策略包括限流、降級、熔斷和隔離等,它們有助于系統在壓力下保持核心功能,避免全面崩潰。
是電商系統中常用的一種保護機制,用于控制流入系統的請求速率,防止系統因短時間內請求過多而過載。
限流的常用策略如下。
- 固定窗口計數器:在固定的時間窗口(如每分鐘)內,只允許一定數量的請求通過。
- 滑動窗口計數器:在滑動的時間窗口內,計算請求的速率,并動態調整允許的請求數量。
- 令牌桶(Token Bucket):系統以固定速率生成令牌,請求需要消耗令牌才能通過,令牌的生成和消耗類似于漏桶模型。這種方式允許突發流量在短時間內高速傳入,但長期速率限制在固定值。
- 優先級隊列:對請求進行優先級排序,高優先級的請求先通過限流檢查。在電商系統中,可能會對以下幾方面實施限流。
- 用戶級別的限流:通過用戶ID進行識別,為每個用戶都設置獨立的限流規則,防止單個用戶頻繁請求,如每秒限制用戶下單次數。例如,使用Redis等緩存數據庫記錄每個用戶的請求時間戳,只有當時間間隔滿足要求時,才允許新的請求通過。
- 接口級別的限流:對特定的API設置限流規則,如每個API在一定時間內只能處理一定數量的請求。可以使用Sentinel等工具,為每個接口都配置限流規則,并在網關或API控制器中實現限流邏輯。
- 全局級別的限流:對整個系統或服務集群設置總體的流量控制,防止整個系統因流量過大而崩潰。可以在負載均衡器或網關層面實現全局限流,如Nginx的限流模塊。
1.接口級別的限流
以訪問電商系統中的商品詳情頁為例,假設我們希望限制每個用戶每秒對商品詳情頁的訪問次數不超過5次,則可以通過使用令牌桶算法實現。Spring Cloud Gateway提供了集成令牌桶算法限流的能力,結合Redis使用,可以實現分布式的限流策略。Spring Cloud的代碼如下。
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
@Configuration
public class GatewayConfig {
// 定義路由規則,指向商品詳情服務
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
return builder.routes()
.route("product_detail_route", r -> r.path("/product/details/**")
.filters(f -> f.requestRateLimiter().configure(c -> c.setRateLimiter(redisRateLimiter())))
.uri("lb://PRODUCT-SERVICE"))
.build();
}
// 應用限流過濾器,使用Redis實現
@Bean
RedisRateLimiter redisRateLimiter() {
// 創建RedisRateLimiter實例
// 參數分別是允許用戶每秒進行5次請求,突發流量大小為10個令牌
return new RedisRateLimiter(5, 10);
}
}
對代碼的解析如下。
requestRateLimiter():應用限流過濾器,使用Redis實現。
RedisRateLimiter(5, 10):配置令牌桶參數,表示每秒生成5個令牌,允許的突發流量大小為10個令牌。
通過上述配置,當用戶對商品詳情頁的訪問頻率超過每秒5次時,額外的請求將被限流策略攔截,從而保護了背后的商品詳情服務不會因為過高的訪問頻率而過載。
提示:在實際部署時,需要考慮Redis的性能和高可用配置,確保限流服務本身不成為系統的瓶頸。
全局級別的限流
在電商系統中,全局限流是一個關鍵的策略,它能夠幫助系統在面對大量用戶請求時保持穩定和高效運行。全局限流策略通常涉及多個層面,包括客戶端、接入層、應用層、數據層的優化。
圖10-2是一個常見的電商系統架構,在該架構的基礎上實施全局限流的具體步驟和策略如下。
圖片
(1)接入層限流。
接入層限流是對進入系統的請求進行控制的第一道防線,旨在保護后端服務不被突發的高流量擊潰。這通常在API網關(位置③)或負載均衡器層面實現南北流量的限流。通過定義流量閾值來限制短時間內處理的請求量,超過閾值的請求會被暫時阻塞或拒絕,以此來確保系統穩定運行并防止服務過載。
(2)讀緩存的優化。
①客戶端緩存。客戶端緩存(位置①)主要針對變化不頻繁的數據,如用戶的配置信息、商品的靜態詳情等。通過在客戶端(如Web瀏覽器、移動應用)存儲這些數據,可以減少對后端服務的請求次數。
②CDN緩存。CDN(內容分發網絡)(位置②)可以緩存靜態資源如圖片、CSS、JS文件等,使用戶能就近訪問這些資源。這不僅加速了用戶的訪問速度,還大大減輕了后端服務器的讀取壓力。
③Redis緩存。將熱點數據(如頻繁查詢的商品庫存信息、用戶會話等)緩存到Redis(位置⑦),可以顯著提高讀取效率,減輕數據庫的查詢壓力。
【實戰2】在高并發場景下,使用“熔斷”防止服務崩潰
在電商系統中,服務間的依賴關系復雜,一旦某個服務發生故障,很容易引發連鎖反應,導致整個系統癱瘓,這就是所謂的“服務雪崩”效應。
以商品詳情頁服務為例,這個服務在電商系統中非常關鍵,它需要從多個依賴服務中獲取數據,如商品描述服務、商品價格服務、商品評論服務等。如果其中一個服務發生故障,沒有熔斷保護,則導致用戶無法正常訪問商品詳情頁,嚴重時甚至可能影響整個系統,如圖10-3所示。
圖片
熔斷策略是一種有效的機制,用于防止這種情況的發生。就像電路中的保險絲一樣,當電流過大時,保險絲會熔斷以保護電路不受損害。在高可用架構中,熔斷器也能夠在服務出現問題時“斷開”,從而保護系統不受進一步損害。
在上述商品詳情頁服務中添加熔斷器后,如圖10-4所示。
圖片
熔斷器主要有如下三種狀態。
- 關閉(CLOSED):在正常狀態下,所有請求都可以直接調用服務。
- 打開(OPEN):當錯誤率超過閾值時,熔斷器打開。此時,對該服務的調用會立即失敗,不會執行實際的業務邏輯。這個狀態會持續一段時間,被稱為“熔斷時間”。
- 半開(HALF-OPEN):熔斷時間過后,熔斷器進入半開狀態。這時,允許有限的請求通過以測試服務是否恢復正常。如果這些請求都成功了,則熔斷器回到閉合狀態;如果仍有過多的請求失敗,則熔斷器再次打開。
以下是使用Spring Cloud Hystrix實現的熔斷器示例。假設有一個獲取商品評論的服務,當評論服務發生故障時,熔斷器將自動斷開,調用備用方法返回默認評論。
(1)添加Hystrix依賴到項目中(以Maven為例),代碼如下。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
(2)在Spring Boot應用主類或配置類上添加@EnableCircuitBreaker注解來啟用Hystrix,代碼如下。
@SpringBootApplication
@EnableCircuitBreaker
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
(3)創建一個服務類,使用@HystrixCommand注解定義熔斷策略,代碼如下。
@Service
public class CommentService {
@HystrixCommand(fallbackMethod = "getDefaultComments")
public String getComments(String productId) {
// 模擬從評論服務獲取評論的操作,可能會因為服務發生故障而拋出異常
if (new Random().nextInt(10) < 8) { // 假設有80%的概率服務會失敗
throw new RuntimeException("評論服務異常");
}
return "正常獲取評論數據";
}
// 定義熔斷時的備用方法
public String getDefaultComments(String productId) {
return "評論服務當前不可用,請稍后再試";
}
}
在上述代碼中,getComments()方法嘗試獲取商品的評論信息。這里我們通過隨機數模擬服務的不穩定性。@HystrixCommand注解指定了當getComments()方法調用失敗時,將自動調用getDefaultComments()方法作為備用邏輯,向用戶返回一個友好的提示信息。
通過上面的示例,我們可以將這種策略應用到所有的服務調用中,以保證即使在某個服務發生故障時,也能向用戶提供最基本的服務,從而提高系統的整體可用性和用戶的體驗。
此外,在Node.js中可以使用Hystrix.js庫來實現熔斷策略,代碼如下。
const Hystrix = require('hystrix-js');
// 創建一個熔斷器,設置錯誤率閾值為50%
const paymentHystrix = new Hystrix({
metrics: {
healthPercentThresholds: [50]
}
});
// 包裝支付服務的調用
function makePaymentRequest() {
return paymentHystrix.wrap(((resolve, reject) => {
// 假設的支付服務調用
payService支付((err, result) => {
if (err) {
reject(err); // 支付失敗,觸發熔斷器
} else {
resolve(result); // 支付成功
}
});
})).then(() => {
// 處理支付成功
}).catch(err => {
// 處理支付失敗,可能觸發熔斷器
console.error('支付失敗:', err);
});
}
在這段代碼中,Hystrix對象包裝了支付服務的調用。如果支付服務的錯誤率達到了50%,則將觸發熔斷器,之后的支付請求將會立即失敗,從而保護系統不受進一步的損害。
【實戰3】在高并發場景下,使用“降級”應對性能瓶頸
在高并發場景下,電商系統經常會遇到性能瓶頸,這可能導致用戶體驗下降,甚至服務中斷。為了應對這種情況,開發團隊通常會采用降級策略,有計劃地降低系統的部分功能,以確保核心功能的穩定運行。
常用的降級策略如下。
- 服務降級:在后端服務響應慢時,返回緩存的數據或者簡化的響應內容。
- 功能降級:暫時關閉或簡化一些非核心功能,如商品推薦、促銷活動等。
- 用戶體驗降級:在無法提供完整服務時,只提供基本的服務,如用文本描述代替圖片等。
1.服務降級
對于電商系統的后端服務(如商品服務),服務降級的具體實現策略如下。
(1)使用緩存數據。
- 系統預先將熱門商品的詳情頁信息緩存起來,如緩存在Redis或Memcached中。
- 當后端服務響應變慢或不可用時,系統自動切換為返回緩存數據。
- 可以定期更新緩存,以保證信息的相對新鮮度。
(2)簡化響應內容。
- 對于一些非核心的信息(如用戶評論、推薦商品等),可以在系統負載高時不加載這些內容。
- 提供一個簡化版本的商品詳情頁,只包含最關鍵的購買信息,如價格、庫存和基本描述。這樣可以減少網絡傳輸的數據量和前端渲染的復雜度。
- 在某些情況下,可以根據實時的系統負載動態決定是否觸發降級處理。例如,如果檢測到CPU或內存使用率超過預設閾值,則自動開始返回簡化的響應內容。
下面是一段簡單的Java代碼,展示了如何根據后端服務的狀態返回不同的響應。
public class ProductService {
private CacheManager cacheManager;
public ProductService(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
// 獲取商品詳情
public ProductDetail getProductDetail(String productId) {
try {
// 嘗試從數據庫中獲取最新的商品詳情頁
ProductDetail productDetail = fetchProductDetailFromDB(productId);
return productDetail;
} catch (Exception e) {
// 如果數據庫訪問出錯或超時,則使用緩存數據
ProductDetail cachedDetail = cacheManager.getCachedProductDetail(productId);
if (cachedDetail != null) {
return cachedDetail; // 返回緩存的商品詳情頁信息
}
// 如果沒有緩存,則返回簡化的商品信息
return new ProductDetail(productId, "暫無詳細描述,請稍后再試。");
}
}
// 模擬從數據庫中獲取商品詳情頁
private ProductDetail fetchProductDetailFromDB(String productId) throws Exception {
// 模擬數據庫操作可能的延遲
Thread.sleep(200); // 假設有200ms的延遲
return new ProductDetail(productId, "完整的商品描述。");
}
}
對代碼的解析如下。
- ProductService類負責處理商品詳情頁的獲取。
- 如果從數據庫中獲取數據失敗(如拋出異常),則嘗試從緩存中獲取商品詳情頁。
- 如果緩存也不存在,則返回一個包含極簡信息的ProductDetail對象,提示用戶稍后再試。
通過這種方式,即使后端服務由于某些原因而性能下降,用戶依然可以得到必要的商品信息,保證了用戶體驗的連續性和系統的高可用。
2.功能降級
假設電商系統中有一個商品推薦服務,它根據用戶的瀏覽歷史和購買歷史來推薦商品。雖然這項服務可以增強用戶體驗,但它并不是完成交易的核心部分。在高流量事件中,如大型促銷活動或購物季等,服務器可能會面臨極大的壓力。
這時就需要對商品推薦服務進行降級處理,處理步驟如下。
(1)定義服務狀態控制。
在服務的Java實現中,定義一個靜態變量isServiceEnabled,用來標識推薦服務當前是否可用。
(2)服務降級開關。
在服務的Java實現中,提供一個公共方法setServiceStatus(boolean status),允許動態地控制推薦服務的開啟或關閉。
(3)服務方法調整。
在推薦方法中,首先檢查isServiceEnabled的狀態。如果服務被設置為不可用,則進行降級處理,推薦方法將直接返回一個空列表或預設的簡單推薦列表,這樣可以大幅減少對系統資源的占用。
上述降級處理的具體代碼如下。
public class RecommendationService {
// 標志位,用于標識推薦服務是否可用
private static volatile boolean isServiceEnabled = true;
// 設置服務狀態的方法,可以被外部調用來開啟或關閉服務
public static void setServiceStatus(boolean status) {
isServiceEnabled = status;
}
// 推薦商品的方法
public List<Product> recommendProducts(User user) {
// 檢查推薦服務是否被降級(即關閉)
if (!isServiceEnabled) {
// 在推薦服務被關閉時,返回一個空列表或者預設的簡單推薦列表
return Collections.emptyList(); // 這里為了簡化,直接返回空列表
}
// 正常情況下的推薦邏輯
// 假設有一個復雜的邏輯,根據用戶的瀏覽歷史、購買歷史等信息生成推薦列表
// ……
return new ArrayList<>(); // 在實際業務中應有復雜邏輯返回推薦商品
}
}
在系統監控工具觀察到服務器負載達到臨界點時,系統管理員或自動化腳本可以調用setServiceStatus(false)方法來關閉推薦服務,減輕服務器的壓力。當系統負載恢復正常后,再次調用setServiceStatus(true)方法來重新開啟推薦服務。
3.用戶體驗降級
假設一個電商網站在特價促銷期間遭遇流量激增,服務器負載急劇上升,導致圖片服務器響應變慢。為了不影響核心購買流程,網站可以實施用戶體驗降級策略,即在圖片加載困難時用文本描述或圖標作為替代,確保頁面加載速度和核心交易功能的正常運行。
用戶體驗降級策略如下。
- 用文本描述代替圖片:在圖片加載緩慢或失敗時,使用文本描述來代替圖片,讓用戶至少知道商品的基本信息。
- 簡化頁面布局:在前端頁面渲染能力受限時,提供一個簡化的頁面布局,只包含必要的元素和信息。
- 延遲加載:對于非關鍵的頁面元素,如廣告和推薦商品,可以采用延遲加載的方式,優先保證核心內容的加載。
例如,在前端代碼中實現降級邏輯,確保在后端服務不穩定或網絡條件差時,用戶仍然能夠獲取基本信息。前端的代碼如下。
// 假設這是一個商品詳情頁的前端代碼片段
function fetchProductDetails(productId) {
fetch(`/api/product/${productId}`)
.then(response => {
if (!response.ok) {
// 如果請求失敗,則返回降級內容
return getDegradedProductDetails(productId);
}
return response.json();
})
.then(details => renderProductDetails(details))
.catch(error => {
// 如果請求失敗,則返回降級內容
getDegradedProductDetails(productId).then(degradedDetails =>{
renderDegradedProductDetails(degradedDetails);
});
});
}
function getDegradedProductDetails(productId) {
// 這里可以從本地緩存中獲取,或者是更簡單的數據
return new Promise(resolve => {
resolve({
name: '商品名稱',
description: '商品描述',
price: '商品價格'
});
});
}
function renderDegradedProductDetails(degradedDetails) {
// 使用簡化的模板或布局渲染降級內容
const container = document.getElementById('product-details');
container.innerHTML = '
<h1>${degradedDetails.name}</h1>
<p>${degradedDetails.description}</p>
<p>價格:$${degradedDetails.price}</p>
';
}
在上述代碼中,fetchProductDetails()方法嘗試從后端獲取完整的商品詳情頁。如果請求失敗,則它會調用getDegradedProductDetails()方法來獲取一個簡化的商品詳情頁,并使用 renderDegradedProductDetails()方法來渲染。