You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用reqwest的json()处理响应数据速度缓慢的问题排查

异步请求npm Registry速度慢的问题排查与优化

问题根源分析

  1. JSON解析开销过大:使用serde_json::Value解析完整的npm包元数据会遍历整个JSON结构,动态类型的解析过程本身就比静态结构体耗时,尤其是当包的元数据较大时,这部分开销会被放大。
  2. 无限制并发触发限流:npm Registry对高频并发请求有限制,一次性发起大量请求会被服务端限流,导致请求等待时间变长,反而比控制并发的情况下更慢。
  3. 错误处理粗暴:unwrap()会导致单个请求失败就终止整个程序,同时无法定位具体是哪个请求出了问题,不利于排查性能瓶颈。

优化方案

  • 控制并发请求数:用tokio::sync::Semaphore限制同时发起的请求数量,避免触发服务端限流。
  • 精准JSON解析:定义仅包含所需字段的结构体,只解析需要的数据,减少解析开销。
  • 完善错误处理:替换unwrap()为错误捕获逻辑,避免程序崩溃并便于排查问题。

修改后的代码示例

use std::fs::read_to_string;
use std::time::Instant;
use tokio::sync::Semaphore;
use reqwest::Client;
use serde::Deserialize;

// 只定义需要的字段,示例取包名和最新版本,可按需扩展
#[derive(Debug, Deserialize)]
struct NpmPackageMeta {
    name: String,
    #[serde(rename = "dist-tags")]
    dist_tags: std::collections::HashMap<String, String>,
}

#[derive(Debug, Deserialize)]
struct PackageJson {
    dependencies: std::collections::HashMap<String, String>,
    #[serde(rename = "devDependencies")]
    dev_dependencies: std::collections::HashMap<String, String>,
}

#[tokio::main]
async fn main() {
    let package_json_string = read_to_string("package.json")
        .expect("读取package.json失败");
    
    let PackageJson {
        dependencies,
        dev_dependencies,
    } = serde_json::from_str(&package_json_string)
        .expect("解析package.json失败");
    
    let mut query_key_list = Vec::new();
    query_key_list.extend(dependencies.keys());
    query_key_list.extend(dev_dependencies.keys());
    
    let client = Client::new();
    // 限制并发数为10,可根据实际测试调整
    let semaphore = Semaphore::new(10);
    let mut tasks = Vec::new();
    
    let start = Instant::now();
    
    for key in query_key_list {
        let url = format!("https://registry.npmjs.org/{}", key);
        let client = client.clone();
        let permit = semaphore.acquire_owned().await.unwrap();
        
        tasks.push(tokio::spawn(async move {
            // 持有permit直到请求完成,自动释放
            let _permit = permit;
            
            match client.get(&url).send().await {
                Ok(response) => {
                    match response.json::<NpmPackageMeta>().await {
                        Ok(meta) => {
                            println!("{} 的最新版本: {}", meta.name, meta.dist_tags.get("latest").unwrap_or(&"未知".to_string()));
                            Ok(meta)
                        },
                        Err(e) => {
                            eprintln!("解析 {} 的JSON失败: {}", key, e);
                            Err(e)
                        }
                    }
                },
                Err(e) => {
                    eprintln!("请求 {} 失败: {}", key, e);
                    Err(e)
                }
            }
        }));
    }
    
    // 等待所有任务完成,忽略错误(也可根据需求收集结果)
    for task in tasks {
        let _ = task.await;
    }
    
    let duration = start.elapsed();
    println!("总耗时: {:?}", duration);
}

额外说明

  • 并发数建议在10-20之间测试,过高容易触发429限流,过低则无法充分利用异步优势。
  • 结构体解析是提升速度的核心:只解析需要的字段能减少CPU计算和内存占用,比解析完整的Value快很多。
  • 错误处理逻辑可以根据需求扩展,比如收集失败的请求后重试,或者统计成功率。

内容的提问来源于stack exchange,提问作者Scc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 22:13:13