PHP/Laravel代码加入sleep后API调用重复执行两次是什么原因?
问题代码
public function createall() { $users = User::orderBy('id', 'ASC') ->where('server', 'Demo') ->where('enable', '1') ->where('mt4_account', '!=', 0) ->take(4) ->get(); foreach ($users as $user) { $server = Server::where('Serverdesc', $user->server)->first(); $post = [ 'server' => $server->serverdesc, 'api_key' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'login' => intval($user->mt4_account), ]; $datapost = json_encode($post); // initialize cURL $ch = curl_init(); // set options curl_setopt($ch, CURLOPT_URL, "http://xxxxxxxxxxxxxxxxx/closetrades"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $datapost); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json', 'Content-Length: ' . strlen($datapost)) ); $result = curl_exec($ch); curl_close($ch); sleep(5); $ptr = fsockopen($server->serveraddress, $server->serverport); // Checking errors if (!$ptr) { dump("Connection error"); exit; } $useracc = $user->mt4_account; $userpass = $user->mt4_pass; $request = "WUSERINFO-login=" . $useracc . "|password=" . $userpass . " QUIT "; fputs($ptr, $request); while (!feof($ptr)) { // Reading the string of symbols $line = fgets($ptr, 128); // End of result transmission flag if ($line == "end\r\n") { fclose($ptr); break; } // Processing $data = (explode(chr(9), $line)); if (substr($data[0], 0, 4) == "Bala") { $balance = substr($data[0], 9, strlen($data[0]) - 11); dump($data, $useracc, $userpass, $balance); } } } }
问题描述
在Laravel项目中编写了上述createall方法,逻辑为筛选符合条件的用户,遍历后先通过cURL发起POST请求调用closetrades接口,随后执行sleep(5),再通过fsockopen查询MT4账户余额。
当前遇到异常:保留sleep(5)时,每条用户记录对应的API调用都会执行两次,通过服务端终端的API日志可以确认该现象;删除sleep(5)后API调用次数恢复正常,单条记录仅触发一次调用。其余代码逻辑如变量dump输出仅出现一次,说明后续逻辑没有重复执行。
问题成因
最核心的原因是上游服务/调用端触发了超时自动重试:
你的Web服务器(如Nginx、Apache)、FastCGI进程管理器或者发起该请求的调用端的超时阈值刚好接近或小于你加了sleep(5)后的单轮请求总耗时。第一次请求执行完cURL调用closetrades接口后进入sleep,还没执行到后续的fsockopen和dump逻辑就因为达到超时时间被中断,上游触发自动重试,第二次请求完整走完了所有逻辑,所以你会看到closetrades接口被调用了两次(第一次中断前的请求+第二次完整请求),但dump仅输出一次(只有第二次请求执行到了该步骤)。
你可以在cURL执行完成后立即写入本地日志,记录当前的请求时间、进程ID,大概率会看到两条不同的日志记录,对应两次独立的HTTP请求调用createall方法。
修复方案
- 调整上游超时配置:将Nginx的
proxy_read_timeout、fastcgi_read_timeout,或者PHP-FPM的request_terminate_timeout调整为大于你脚本最大执行时间的值,避免超时中断。 - 脚本执行前取消PHP时间限制:在方法开头加上
set_time_limit(0);,关闭PHP单请求最大执行时间限制。 - 优化执行逻辑:如果该方法是同步触发的Web请求逻辑,建议改为异步队列任务执行,避免长耗时阻塞Web请求,从根源上避免超时重试问题。
内容的提问来源于stack exchange,提问作者Danny Scheers
相关产品推荐
相关产品推荐

