Laravel分类页面加载缓慢:Helper类curl请求优化求助
你的问题核心很明确:每个商品渲染时都调用Helper::getPRBcurr(),导致最多890次重复的缓存检查甚至外部API请求,哪怕有500ms超时,叠加起来的延迟也足以让页面卡顿。下面是几个逐步优化的解决方案,从最易实施到彻底根治:
1. 最直接的修复:提前获取一次汇率,复用给所有商品
现在的模板里,每个商品都会触发一次Helper::getPRBcurr()调用——哪怕缓存命中,890次缓存查询也是没必要的开销;如果缓存过期,890个并发的curl请求直接会把页面拖垮。
解决方法是在控制器里提前获取一次汇率,然后传递给视图,所有商品共用这个值:
控制器代码修改:
public function showCategory($categoryId) { // 先获取分类下的商品 $products = Product::where('category_id', $categoryId)->get(); // 只调用一次汇率方法,把结果存到变量里 $prbCurrency = Helper::getPRBcurr(); // 把商品和汇率一起传给视图 return view('category', compact('products', 'prbCurrency')); }
商品模板(product.blade.php)修改:
把原来的Helper::getPRBcurr()调用换成传入的变量:
<div class="price-prb"> <span itemprop="price"> @php $prb = intval($product->price * $prbCurrency); @endphp {{ $prb }} <span itemprop="priceCurrency" content="PRB"> {!! trans_choice('messages.currency.RUB', $prb) !!} </span> </span> </div>
这样不管有多少个商品,只会发起一次汇率查询(缓存命中则直接取缓存,否则一次curl请求),瞬间消除重复调用的开销。
2. 优化Helper类:防止缓存击穿(并发请求问题)
即使做了上面的优化,还是要考虑缓存过期时的并发请求问题:如果缓存刚好失效,此时有多个用户访问分类页,还是会触发多次curl请求。我们可以给Helper类加锁,保证同一时间只有一个请求去拉取汇率,其他请求等待或者使用旧值。
修改Helper::getPRBcurr()方法:
static public function getPRBcurr() { // 先尝试从缓存获取 $currency = Cache::get('currency_prb_ee_agroTimeout'); if ($currency) { return $currency; } // 使用缓存锁,10秒内只允许一个请求去拉取汇率 $lock = Cache::lock('currency_prb_fetch_lock', 10); try { // 再次检查缓存,防止等待锁的过程中已经被其他请求更新了 $currency = Cache::get('currency_prb_ee_agroTimeout'); if ($currency) { return $currency; } // 发起curl请求获取汇率 $contentCurrency = self::file_get_contents_curl('https://www.agroprombank.com/xmlinformer.php'); if (!$contentCurrency) { // 拉取失败时用存储的旧值,临时缓存60秒 $_currency = Storage::get('currency_prb_agro'); Cache::put('currency_prb_ee_agroTimeout', $_currency, 60); return $_currency; } // 解析XML并处理汇率值 $xmls = simplexml_load_string($contentCurrency); $currency = (string)$xmls->course[1]->currency[5]->currencySell; $currency = ($currency > 0) ? $currency : null; $currency = (strpos($currency, '1.0') !== false) ? '0.99' : $currency; // 更新缓存和存储 Cache::put('currency_prb_ee_agroTimeout', $currency, 720); Storage::put('currency_prb_agro', $currency); return $currency; } finally { // 释放锁 if ($lock->get()) { $lock->release(); } } }
这个修改确保了缓存过期时,只有一个请求会去调用外部API,其他请求要么等待锁释放后使用新缓存,要么在锁超时前返回旧值,避免了并发请求叠加的问题。
3. 终极方案:用任务调度预加载汇率
如果想彻底消除前端请求对外部API的依赖,可以用Laravel的任务调度,定期自动拉取汇率并更新缓存。这样用户的请求永远只会读取缓存,不会触发任何curl请求。
步骤1:创建Artisan命令
运行以下命令生成命令文件:
php artisan make:command UpdatePRBCurrency
步骤2:编写命令逻辑
打开app/Console/Commands/UpdatePRBCurrency.php,修改handle()方法:
public function handle() { $contentCurrency = Helper::file_get_contents_curl('https://www.agroprombank.com/xmlinformer.php'); if (!$contentCurrency) { $this->error('Failed to fetch PRB currency from API'); return 1; // 标记命令执行失败 } $xmls = simplexml_load_string($contentCurrency); $currency = (string)$xmls->course[1]->currency[5]->currencySell; $currency = ($currency > 0) ? $currency : null; $currency = (strpos($currency, '1.0') !== false) ? '0.99' : $currency; Cache::put('currency_prb_ee_agroTimeout', $currency, 720); Storage::put('currency_prb_agro', $currency); $this->info('PRB currency updated successfully!'); return 0; }
步骤3:配置任务调度
打开app/Console/Kernel.php,在schedule()方法里添加调度规则:
protected function schedule(Schedule $schedule) { // 每小时自动更新一次汇率 $schedule->command('update:prb-currency')->hourly(); }
最后,确保你的服务器配置了Laravel调度器的 cron 任务(可以参考Laravel官方文档的调度器配置)。这样缓存会被定期更新,用户访问页面时永远读取的是缓存值,彻底解决外部API调用带来的延迟问题。
内容的提问来源于stack exchange,提问作者Jadasdas

