原生 curl 函数能每秒发送 3000 次请求吗

2024-05-29 11:38:52 +08:00
 cs5117155

我的伪代码,使用是 thinkphp5.0 。

    public function __construct()
    {
        $this->redis = new redis();
    }
    public function fire(Job $job, $data)
    {
        
        if ($job->attempts() > 2) {
         
            $this->redis->setHear($data);
            $job->delete();
        } else {
            $this->send($data);
            $job->delete();
        }
    }
    
    /**
     * 根据消息中的数据进行实际的业务处理
     * @param array|mixed    $data     发布任务时自定义的数据
     * @return boolean                 任务执行的结果
     */
    private function send($data)
    {
        try {         
            $result = CURLRequest($data['weburl'], $data, 'POST', $this->header);
        } catch (\Exception $e) {
            echo $e->getMessage();
            return false;
        }
        if (empty($result)) {
            echo json_encode(['errcode' => 1, 'msg' => '服务器异常:' . $data['SN'], 'data' => $result, 'url' => $data['weburl']], JSON_UNESCAPED_SLASHES);
            return false; //请求异常尝试重试
        } else {
          
            echo json_encode(['errcode' => 1, 'msg' => $data['SN'] . ' ' . date("Y-m-d H:i:s") . '' . "心跳 ok", 'data' => $result, 'url' => $weburl], JSON_UNESCAPED_SLASHES);
            return true; //请求成功退出
        }
    }
function CURLRequest($url, $params = [], $http_method = 'GET', $Header = [])
{
    $SSL = substr($url, 0, 8) == "https://" ? true : false;  //判断是否 https 连接
    $httpInfo = array();
    $ch = curl_init();                                       //初始化 CURL 会话
    //设置 CURL 传输选项
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 1);
    //设置响应头头文件的信息作为数据流输出
    curl_setopt($ch, CURLOPT_HEADER, 1); //返回 response 头部信息
    curl_setopt($ch, CURLINFO_HEADER_OUT, true); //TRUE 时追踪句柄的请求字符串,从 PHP 5.1.3 开始可用。这个很关键,就是允许你查看请求 header
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    // 返回 response_header, 该选项非常重要,如果不为 true, 只会获得响应的正文
    curl_setopt($ch, CURLOPT_HEADER, true);
    if ($http_method == 'POST') {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
    } else {
        if (count($params) >= 1) {
            $pstr = '?';
            foreach ($params as $pkey=>$pv) {
                $pstr .= $pstr == '?' ? $pkey.'='.$pv : '&'.$pkey.'='.$pv;
            }
            $url .= $pstr;
        }
    }
    curl_setopt($ch, CURLOPT_URL, $url);

    if (!empty($Header)) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $Header);//设置请求头信息
    }
    if ($SSL) {
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
    }
    $response = curl_exec($ch);                                      //执行 CURL 会话
    if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $header = substr($response, 0, $headerSize);
        $body = substr($response, $headerSize);
    }
    curl_close($ch);                                                 //关闭会话
    list($header, $body) = explode("\r\n\r\n", $response, 2);
    $headers = explode("\r\n", $header);
    $headList = array();
    foreach ($headers as $head) {
        $value = explode(':', $head);
        if(isset($value[1])) $headList[$value[0]] = $value[1];
    }
    $result = json_decode($body, true);//返回解析后的数据
    if ($response === FALSE OR empty($response)) {                    //错误返回 false
        $result = false;
    }
    return ['header' => $headList, 'result' => $result, 'raw_result' => $body];//header 响应头数据,格式化数组,原始数据
}

服务器配置

4G 8 核 5M

使用场景

物联网设备会通过 Http 方式回调心跳 30 秒一次,我搭建了中转服务器承担转发心跳,那么每次中转服务器同时间收到的请求可能是 1000 次,或者 500 次不等,这时又同时需要转发 1000 次或者 500 次不等

我的疑问

1.为何服务器重启后,CPU 会长时间占用率 100%,而且有队列不停的死循环,几百万次,我删除队列后,它依然不停增长到百万次,是不是队列中有异常,我没有捕获到

2.如果我分批转发心跳,它服务器能正常运行,比如 A B C D E F G 客户物联设备都关机,然后让客户再依次按顺序开机,中转服务器能正常运行

3.php 如何应对这种瞬间请求多的方式

3780 次点击
所在节点    PHP
30 条回复
cs5117155
2024-05-30 17:29:15 +08:00
@yc8332 下午我写代码的时候也发现这个问题,第一次心跳全部都是设备发起 http 请求到 fpm ,我再加入队列的,再使用 workman 转发,问题是可能服务器受到瞬间请求,就已经卡死在 fpm,队列都无加入,那岂不是连 http 请求入口都要改为 workman 了?
encro
2024-06-03 13:49:00 +08:00
每秒 3000 请求,确实比较高了,用 fpm 解决不了,除非你 fpm 相应比较快。
假设每个请求不超过 20ms ,那么每个 fpm 一秒钟能完成 50 个请求。3000 个请求需要 60 个 fpm ,每个 fpm 的占用内存 30M ,不考虑其他消耗那么需要大于 2G 内存。理论上 4G 内存可以处理完,如果机器上跑数据库,要处理请求,那么可能存在 cpu 不足。
如果是阿里云,那么会存在打开文件句柄过多(默认好像是 4096 ),需要增加打开文件数。
coderzhangsan
2024-06-07 10:09:06 +08:00
fpm 不适合做这种高并发请求业务,可以使用 workman/swoole 来代替。

如果非要使用 fpm 来实现,需要调整 fpm 配置以及尽可能的使用长连接:
1. max_requests 数值调高,延长 fpm 进程生命周期,在周期内处理的更多的请求,避免进程频繁创建销魂带来的开销。
2.使用 curl_multi_init 多句柄实现并发请求,并设置 curl_multi_setopt 选项,
开启 CURLMOPT_PIPELINING ,
提高 CURLMOPT_MAX_HOST_CONNECTIONS 缓存链接数
3.或者使用并发的异步包来实现,例如 amphp 或 reactphp ,可以参考其文档示例。

最后,机器配置也很重要,提高机器内存(由于提高了 max_requests 数,使得的单个 fpm 整个生命周期进程占用内存提升),并发处理与机器 cpu 核数相关,提高 cpu 核心数。
Jeyfang
2024-06-07 11:50:59 +08:00
期待作者后续实践后的答复
Jeyfang
2024-06-07 11:55:10 +08:00
仔细看了下代码,这个默认一进来就是同步去 request ,这个策略得换一下
cs5117155
2024-06-08 12:32:48 +08:00
@encro 这个很早之前就设置文件句柄 65536 ,而且也无法保证每个请求 20ms ,因为之前试过有一个客户服务器崩了,后台一直转发给他,搞到自己的服务器也崩了,虽然我设置 curl 超时 1s,实际也没用。我现在目前的解决思路,就是保证 30ms 内,物联网设备请求进来,我直接丢队列,让 workman 取队列,再转发,但需要保证 30ms 内接收完一个请求,是我需要考虑的
cs5117155
2024-06-08 14:39:46 +08:00
@coderzhangsan 使用异步包这个方案也不错,赞一个,之前我还不知道有这种包
cs5117155
2024-06-08 14:40:21 +08:00
@Jeyfang 等我解决方案写好了,再来回复你😁
jy28520
2024-06-23 11:51:19 +08:00
走 nginx 转发或是看看 workman 有个 TCP 转发的功能,都比较简单
fengshils
2024-07-23 17:03:47 +08:00
好奇问题解决了吗

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1044992

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX