一次 web 请求响应中,通常那个部分最耗时?

文章目录

  • 一次Web请求的完整旅程
    • [1. DNS解析](#1. DNS解析)
    • [2. TCP连接建立](#2. TCP连接建立)
    • [3. 发送HTTP请求](#3. 发送HTTP请求)
    • [4. 服务器处理](#4. 服务器处理)
    • [5. 服务器响应](#5. 服务器响应)
    • [6. 浏览器渲染](#6. 浏览器渲染)
  • 哪个环节通常最耗时?
    • [1. 数据库查询](#1. 数据库查询)
    • [2. 外部API调用](#2. 外部API调用)
    • [3. 复杂的业务逻辑](#3. 复杂的业务逻辑)
  • 如何优化各个环节?
    • [1. 数据库优化](#1. 数据库优化)
    • [2. 缓存策略](#2. 缓存策略)
    • [3. 异步处理](#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应用大多需要查询数据库:

sql 复制代码
-- 一个复杂的查询可能需要几百毫秒甚至几秒
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调用

很多应用需要调用第三方服务:

  1. 支付接口调用
  2. 地图服务API
  3. 短信发送服务

每个外部调用都可能增加几百毫秒的延迟,而且还可能失败重试。

3. 复杂的业务逻辑

  1. 服务之间的调用(RPC)
  2. 复杂的业务逻辑(电商的价格计算)
  3. 数据处理(格式转换与序列化)
  4. 上传大文件、文件压缩(Zip)
  5. 权限校验(用户身份认证)
  6. 记录日志(操作日志持久化到磁盘)
    这些操作都要销毁大量的时间,耗时比较久。

如何优化各个环节?

1. 数据库优化

sql 复制代码
-- 给常用查询字段加索引
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. 缓存策略

java 复制代码
@Service
public class ProductService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private 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. 异步处理

java 复制代码
@RestController
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private 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 {
    
    @Async
    public void sendOrderEmail(Long orderId) {
        // 发送订单确认邮件(耗时操作)
        emailService.sendOrderConfirmation(orderId);
    }
    
    @Async  
    public void updateInventory(List<OrderItem> items) {
        // 更新库存(可能需要调用多个服务)
        inventoryService.updateStock(items);
    }
}

主线程专注处理核心业务,耗时操作放到后台慢慢处理,整体吞吐量大大提升。

总结

所以一次Web请求响应中最耗时的大多数情况下是服务器处理时间,特别是数据库查询和外部API调用。