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

PHP BigQuery Client长任务401认证错误及令牌刷新求助

解决PHP BigQueryClient长时间运行401令牌过期问题

问题根源

使用服务账号密钥初始化的BigQueryClient,默认获取的OAuth2令牌有效期为1小时。当循环拉取大表数据的时间超过1小时,旧令牌失效,而客户端实例不会自动刷新令牌,导致触发401认证错误。

解决方案1:定期重建BigQueryClient实例

在循环过程中,每隔55分钟(提前于1小时令牌有效期)重新初始化BigQueryClient,获取新的有效令牌。

修改后的代码示例:

// 封装客户端创建方法
private function createBigQueryClient(array $keyFile, string $projectId): BigQueryClient
{
    return new BigQueryClient([
        'keyFile' => $keyFile,
        'projectId' => $projectId
    ]);
}

// 初始化初始客户端
$bigQuery = $this->createBigQueryClient($array[$key], $this->projectId);
$info = $bigQuery->dataset($this->datasetId)->table($tableName)->info();

$this->info($this->messagePrepend . "Rows: " . number_format($info['numRows']) . ", Chunk size: " . number_format($this->chunkSize) . " – Processing.." . PHP_EOL);

$startTime = Carbon::now();
$file = fopen(storage_path("tmp/{$tableName}.csv"), 'w');
fputcsv($file, $this->tableHeaders);

$orderBy = match ($keyword) {
    default => "id"
};

// 设置令牌刷新间隔:55分钟(提前5分钟避免过期)
$refreshInterval = 55 * 60;

while ($info['numRows'] > $offset) {
    // 检查是否需要刷新客户端
    if (Carbon::now()->diffInSeconds($startTime) >= $refreshInterval) {
        $bigQuery = $this->createBigQueryClient($array[$key], $this->projectId);
        $startTime = Carbon::now();
    }

    $config = $bigQuery->query("SELECT * FROM {$this->datasetId}.{$tableName} ORDER BY {$orderBy} ASC LIMIT {$this->chunkSize} OFFSET {$offset}");
    $job = $bigQuery->startQuery($config);
    $queryResults = collect($job->queryResults());

    $queryResults->map(function ($row) use ($file) {
        $line = [/* 构造行数据的逻辑 */];
        fputcsv($file, $line);
    });

    $offset += $this->chunkSize;
}

fclose($file);

解决方案2:使用可自动刷新的凭据

借助Google Auth库的ServiceAccountCredentials类,手动创建支持自动刷新的凭据注入到BigQueryClient,令牌过期时会自动刷新。

首先确保安装依赖:composer require google/auth,然后修改代码:

use Google\Auth\Credentials\ServiceAccountCredentials;
use Google\Cloud\BigQuery\BigQueryClient;

// 初始化可自动刷新的凭据
$credentials = new ServiceAccountCredentials(
    'https://www.googleapis.com/auth/bigquery',
    $array[$key]
);

$bigQuery = new BigQueryClient([
    'projectId' => $this->projectId,
    'credentials' => $credentials
]);

// 后续循环逻辑保持不变,客户端会自动处理令牌刷新
$info = $bigQuery->dataset($this->datasetId)->table($tableName)->info();

$this->info($this->messagePrepend . "Rows: " . number_format($info['numRows']) . ", Chunk size: " . number_format($this->chunkSize) . " – Processing.." . PHP_EOL);

$startTime = Carbon::now();
$file = fopen(storage_path("tmp/{$tableName}.csv"), 'w');
fputcsv($file, $this->tableHeaders);

$orderBy = match ($keyword) {
    default => "id"
};

while ($info['numRows'] > $offset) {
    $config = $bigQuery->query("SELECT * FROM {$this->datasetId}.{$tableName} ORDER BY {$orderBy} ASC LIMIT {$this->chunkSize} OFFSET {$offset}");
    $job = $bigQuery->startQuery($config);
    $queryResults = collect($job->queryResults());

    $queryResults->map(function ($row) use ($file) {
        $line = [/* 构造行数据的逻辑 */];
        fputcsv($file, $line);
    });

    $offset += $this->chunkSize;
}

fclose($file);

额外优化:替换OFFSET分页提升性能

对于超大规模表,OFFSET分页会随着偏移量增大导致查询效率急剧下降,建议改用游标分页(pageToken)配合自动刷新方案:

// 初始查询获取第一页及游标
$config = $bigQuery->query("SELECT * FROM {$this->datasetId}.{$tableName} ORDER BY {$orderBy} ASC LIMIT {$this->chunkSize}");
$job = $bigQuery->startQuery($config);
$queryResults = $job->queryResults();

do {
    // 处理当前页数据
    foreach ($queryResults as $row) {
        $line = [/* 构造行数据的逻辑 */];
        fputcsv($file, $line);
    }
    // 获取下一页游标
    $pageToken = $queryResults->pageToken();
    // 存在游标则继续查询下一页
    if ($pageToken) {
        $queryResults = $job->queryResults(['pageToken' => $pageToken]);
    }
} while ($pageToken);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 04:55:23