文章目錄
- 一次Web請求的完整旅程
- 1. DNS解析
- 2. TCP連接建立
- 3. 發送HTTP請求
- 4. 服務器處理
- 5. 服務器響應
- 6. 瀏覽器渲染
- 哪個環節通常最耗時?
- 1. 數據庫查詢
- 2. 外部API調用
- 3. 復雜的業務邏輯
- 如何優化各個環節?
- 1. 數據庫優化
- 2. 緩存策略
- 3. 異步處理
- 總結
一次Web請求的完整旅程
當用戶在瀏覽器地址欄輸入網址并按下回車,到最終看到頁面內容,這中間經歷了什么?我們來拆解一下這個過程:
1. DNS解析
瀏覽器首先要把域名轉換成IP地址。比如把"www.baidu.com"轉換成"120.5.5.46"。這個時間取決于DNS服務器的響應速度、是否有DNS緩存還有良好的網絡環境。
2. TCP連接建立
找到IP地址后,瀏覽器需要和服務器建立TCP連接,這就是三次握手,主要受網絡延遲影響。
3. 發送HTTP請求
連接建立后,瀏覽器發送HTTP請求到服務器,這個過程通常很快,除非請求數據很大。
4. 服務器處理
服務器接收到請求后,開始處理業務邏輯。這通常是最"邪門"的環節。
5. 服務器響應
服務器將處理結果發送回瀏覽器。主要取決于響應數據的大小和網絡帶寬。
6. 瀏覽器渲染
瀏覽器接收到HTML后,開始解析、渲染頁面,時間取決于加載的庫、腳本等需要網絡的組件的數量的網絡消耗。
哪個環節通常最耗時?
答案:通常是服務器處理時間
在大多數情況下,服務器處理時間是最大的性能瓶頸。為什么這么說?
1. 數據庫查詢
現在的Web應用大多需要查詢數據庫:
-- 一個復雜的查詢可能需要幾百毫秒甚至幾秒
SELECT u.name, p.title, COUNT(c.id) as comment_count
FROM users u
JOIN posts p ON u.id = p.user_id
LEFT JOIN comments c ON p.id = c.post_id
WHERE u.created_at > '2023-01-01'
GROUP BY u.id, p.id
ORDER BY comment_count DESC
LIMIT 20;
如果沒有合適的索引,這樣的查詢輕松就能花掉幾秒鐘。
2. 外部API調用
很多應用需要調用第三方服務:
- 支付接口調用
- 地圖服務API
- 短信發送服務
每個外部調用都可能增加幾百毫秒的延遲,而且還可能失敗重試。
3. 復雜的業務邏輯
- 服務之間的調用(RPC)
- 復雜的業務邏輯(電商的價格計算)
- 數據處理(格式轉換與序列化)
- 上傳大文件、文件壓縮(Zip)
- 權限校驗(用戶身份認證)
- 記錄日志(操作日志持久化到磁盤)
這些操作都要銷毀大量的時間,耗時比較久。
如何優化各個環節?
1. 數據庫優化
-- 給常用查詢字段加索引
CREATE INDEX idx_user_id ON posts(user_id);-- 用JOIN替代循環查詢
SELECT p.title, u.name
FROM posts p JOIN users u ON p.user_id = u.id;
索引是一種高效的數據結構,可以提高數據的查詢效率。
2. 緩存策略
@Service
public class ProductService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate ProductMapper productMapper;public List<Product> getHotProducts() {String cacheKey = "hot_products";// 先查緩存List<Product> cached = (List<Product>) redisTemplate.opsForValue().get(cacheKey);if (cached != null) {return cached;}// 緩存沒有,查數據庫List<Product> products = productMapper.getHotProducts();// 存入緩存,5分鐘過期redisTemplate.opsForValue().set(cacheKey, products, 5, TimeUnit.MINUTES);return products;}
}
熱點數據被頻繁查詢,每次都查數據庫會把數據庫拖垮。緩存后,100個用戶訪問只需要查1次數據庫,這樣不僅提高的數據的查詢,還減少了磁盤IO,縮短了時間。
3. 異步處理
@RestController
public class OrderController {@Autowiredprivate OrderService orderService;@Autowiredprivate AsyncTaskService asyncTaskService;@PostMapping("/order")public ResponseEntity<?> createOrder(@RequestBody OrderRequest request) {// 先快速創建訂單并響應Order order = orderService.createOrder(request);// 異步處理耗時任務asyncTaskService.sendOrderEmail(order.getId());asyncTaskService.updateInventory(order.getItems());return ResponseEntity.ok(Map.of("orderId", order.getId(), "status", "processing"));}
}@Service
public class AsyncTaskService {@Asyncpublic void sendOrderEmail(Long orderId) {// 發送訂單確認郵件(耗時操作)emailService.sendOrderConfirmation(orderId);}@Async public void updateInventory(List<OrderItem> items) {// 更新庫存(可能需要調用多個服務)inventoryService.updateStock(items);}
}
主線程專注處理核心業務,耗時操作放到后臺慢慢處理,整體吞吐量大大提升。
總結
所以一次Web請求響應中最耗時的大多數情況下是服務器處理時間,特別是數據庫查詢和外部API調用。