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

207 天前
 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 如何应对这种瞬间请求多的方式

3235 次点击
所在节点    PHP
30 条回复
cs5117155
206 天前
@yc8332 下午我写代码的时候也发现这个问题,第一次心跳全部都是设备发起 http 请求到 fpm ,我再加入队列的,再使用 workman 转发,问题是可能服务器受到瞬间请求,就已经卡死在 fpm,队列都无加入,那岂不是连 http 请求入口都要改为 workman 了?
encro
202 天前
每秒 3000 请求,确实比较高了,用 fpm 解决不了,除非你 fpm 相应比较快。
假设每个请求不超过 20ms ,那么每个 fpm 一秒钟能完成 50 个请求。3000 个请求需要 60 个 fpm ,每个 fpm 的占用内存 30M ,不考虑其他消耗那么需要大于 2G 内存。理论上 4G 内存可以处理完,如果机器上跑数据库,要处理请求,那么可能存在 cpu 不足。
如果是阿里云,那么会存在打开文件句柄过多(默认好像是 4096 ),需要增加打开文件数。
coderzhangsan
198 天前
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
198 天前
期待作者后续实践后的答复
Jeyfang
198 天前
仔细看了下代码,这个默认一进来就是同步去 request ,这个策略得换一下
cs5117155
197 天前
@encro 这个很早之前就设置文件句柄 65536 ,而且也无法保证每个请求 20ms ,因为之前试过有一个客户服务器崩了,后台一直转发给他,搞到自己的服务器也崩了,虽然我设置 curl 超时 1s,实际也没用。我现在目前的解决思路,就是保证 30ms 内,物联网设备请求进来,我直接丢队列,让 workman 取队列,再转发,但需要保证 30ms 内接收完一个请求,是我需要考虑的
cs5117155
197 天前
@coderzhangsan 使用异步包这个方案也不错,赞一个,之前我还不知道有这种包
cs5117155
197 天前
@Jeyfang 等我解决方案写好了,再来回复你😁
jy28520
182 天前
走 nginx 转发或是看看 workman 有个 TCP 转发的功能,都比较简单
fengshils
152 天前
好奇问题解决了吗

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

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

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

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

© 2021 V2EX