V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
pretty66
V2EX  ›  程序员

分享一个玩具,把 PHP 的 http 请求变为长连接,能有效降低请求的延迟

  •  2
     
  •   pretty66 · 235 天前 · 1943 次点击
    这是一个创建于 235 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Fastcar - PHP HTTP 长连接代理

    背景

    公司的一个 php 项目经常调用众多的三方平台的 api ,有 http 协议和 https 协议;由于 php-fpm 模式无法复用链接,所以每次进行 api 调用时需要经过:tcp 握手、( tls 握手)、http 报文交互、连接关闭几个阶段;比较明显的就是接口延迟高,业务量大时出现大量的 TIME_WAIT 。

    解决

    使用 go 语言开发了一个网络请求代理中间件,接管 php 的网络请求,在代理程序中保持连接的复用;经测试能极大的降低网络延迟,目前已在生产环境使用。

    测试百度翻译 api 的 http 和 https 请求延迟

    • http 模式下平均请求延迟降低了约 34.8%
    • https 模式平均请求延迟降低了约 48.4%

    Fastcar 原理简介

    • HTTPS 伪装握手:针对 https 服务的访问 Fastcar 会伪装服务端进行 TLS 握手,代替 php 程序同目标服务建立 tls 连接,并保持连接复用,能显著降低 php 短连接频繁握手导致的请求延迟。
    • 长连接支持:通过维护连接池,Fastcar 提供长连接支持,可以接管节点上所有的 php 程序网络请求,达到多服务连接共用;例如 k8s 形式单台节点上部署多个 pod 形式的业务能实现连接的共用,大大降低连接建立的开销。
    • 资源优化:Fastcar 本身非常轻量,只占用很少的系统资源;代码量极少,只依赖 go 官方库

    开源地址

    最后

    如果项目采用微服务架构,且包括由 PHP 开发的微服务,而这些微服务之间通过 HTTP 进行调用,那么可以使用 Fastcar 作为 PHP 的网络请求中间件。能有效地维持长连接,降低资源开销,以及减少网络延迟。

    37 条回复    2024-01-03 11:07:19 +08:00
    vibbow
        1
    vibbow  
       235 天前
    更正一个错误:php-fpm 模式下是可以复用链接的
    pretty66
        2
    pretty66  
    OP
       235 天前
    @vibbow 多个 php-fpm 生命周期之间有办法复用连接吗,有没有文档甩个我看看
    vibbow
        3
    vibbow  
       235 天前
    @pretty66 https://www.php.net/pfsockopen

    在我的配置下,单个 php-fpm 进程可以处理 1w 个请求,同一个 php-fpm 进程处理的请求是可以复用链接的。

    通常一个 web 实例也不会有太多 php-fpm 进程,所以不跨进程共享问题也不大。
    vibbow
        4
    vibbow  
       235 天前
    实际 pfsockopen 是底层一些的用法,一般情况下用 stream_socket_client + STREAM_CLIENT_PERSISTENT flag 去启用这个功能。

    像 redis 的长连接,也是用这个实现的。
    pretty66
        5
    pretty66  
    OP
       235 天前
    @vibbow 感谢分享,不知道这个在进行 http 、https 请求时配置是否复杂;后面深入研究下😄
    vibbow
        6
    vibbow  
       235 天前
    @pretty66 你可以看下 composer 的代码,composer 是用 stream_socket_client 创建 http 连接的。
    pretty66
        7
    pretty66  
    OP
       235 天前
    @vibbow 粗略的看了下 pfsockopen 和 stream_socket_client 这两个函数都是偏网络底层的,工作在 tcp4 层,如果处理 7 层的 http 协议需要基于这些函数自己实现协议的封装;相当于自己实现 http client 复杂度有点高;目前没有发现成熟开源库。简单的 http 请求用这些底层函数封装下还行,如果涉及到复杂的 http 请求不知道能否胜任。例如:服务端异常主动关闭连接的,http 的 chunked 数据响应,http2 的服务 不知道这几种情况能否很好的处理
    vibbow
        8
    vibbow  
       235 天前
    @pretty66 stream_socket_client 是可以直接发 http 请求的啊
    vibbow
        9
    vibbow  
       235 天前
    https://www.php.net/manual/en/context.http.php
    构建一个 context 丢给 stream_socket_client 就行
    changz
        10
    changz  
       234 天前 via Android
    业务侵入性太大,不如 hook dns 然后做个代理服务靠谱些
    louisxxx
        11
    louisxxx  
       234 天前 via iPhone
    没看懂你这个和 nginx 的反向代理 API 域名做连接保持区别在哪里? stream_socket_client 是很常用的方案啊
    rekulas
        12
    rekulas  
       234 天前   ❤️ 1
    你的需求用 webman 似乎就可以了
    demoshengxw
        13
    demoshengxw  
       234 天前 via iPhone
    我们公司是 java 和 php 还有 go 乱七八糟一堆异构系统,对于 php 这类链接 redis ,mysql 这种短链接,是直接在 k8s 内注入 sidercar 容器做代理,常链接都由 sidercar 代理去维护。
    kingofzihua
        14
    kingofzihua  
       234 天前
    这样算是复用连接吗?

    ```
    curl_setopt($ch, CURLOPT_FRESH_CONNECT, false);
    curl_setopt($ch, CURLOPT_FORBID_REUSE, false);
    ```
    pretty66
        15
    pretty66  
    OP
       234 天前
    @changz 如果项目代码封装比较好,只需要在请求调用的函数增加仅仅三行代码,对正常的业务无任何影响;做 hook dns 再做代理,代理你使用正向代理吗,正向代理你怎么保持连接复用
    fenglangjuxu
        16
    fenglangjuxu  
       234 天前
    有考虑过 fastcar 重启的问题么
    它挂掉了 是不是请求就中断了
    pretty66
        17
    pretty66  
    OP
       234 天前
    @demoshengxw 能用 sidecar 是很好的方案,你们公司业务规模肯定不小;一般的小公司的技术是用不起来这一套的,fastcar 也类似一个 sidecar 的程序内部维护连接池,只专注于解决 php http 请求的问题,比较适合小业务;只需要在程序调用的地方增加三行 curl 设置就能保持长连接。当然如果有实力使用 service mesh 架构肯定是极力推荐 sidecar 方案,我们公司也是使用的 service mesh 架构
    pretty66
        18
    pretty66  
    OP
       234 天前   ❤️ 1
    @kingofzihua chatgpt 亲自答:`CURLOPT_FORBID_REUSE` 和 `CURLOPT_FRESH_CONNECT` 是 cURL 中的两个参数,用于控制连接的复用和重新连接。它们的作用如下:

    1. `CURLOPT_FORBID_REUSE`:设置为 `true`(或 `1`)时,表示禁止复用连接。这意味着在请求之间不会重用现有的连接,而是每次请求都会创建一个新的连接。默认情况下,cURL 是允许复用连接的。

    2. `CURLOPT_FRESH_CONNECT`:设置为 `true`(或 `1`)时,表示强制每次请求都创建一个新的连接,即使之前的连接可复用。默认情况下,cURL 会尝试复用现有连接,以提高性能。

    在 PHP-FPM 不同的生命周期中,这两个参数的设置通常不会影响连接的复用。PHP-FPM 是一个进程管理器,它会在请求到达时启动一个 PHP 进程来处理请求,处理完请求后,该 PHP 进程会继续存在一段时间等待下一个请求。连接的复用通常是在同一 PHP-FPM 进程内进行的,而不是在不同 PHP-FPM 进程之间。

    如果你希望在不同的 PHP-FPM 进程之间共享连接池,你需要使用连接池管理工具或者设置共享内存等机制,这超出了 cURL 的 `CURLOPT_FORBID_REUSE` 和 `CURLOPT_FRESH_CONNECT` 参数的作用范围。

    因此,`CURLOPT_FORBID_REUSE` 和 `CURLOPT_FRESH_CONNECT` 主要用于在单个 PHP-FPM 进程内的不同请求之间控制连接的行为,而不会跨不同 PHP-FPM 进程。如果需要在不同 PHP-FPM 进程之间实现连接的共享和复用,需要考虑其他方法,如使用连接池工具或者共享内存。
    codersdp1
        19
    codersdp1  
       234 天前
    @vibbow #8 请教下,stream_socket_client 复用链接的话。stream_socket_client 创建的 fd,该保存在那里,在 fpm 下每个请求都会创建新的 php 解释实例,上个 php 实例创建的 fd 应该被销毁掉了。
    vibbow
        20
    vibbow  
       234 天前   ❤️ 2
    @codersdp1 fastcgi 模式下,一个 PHP 进程实例会处理很多请求(我这里是处理 10000 请求数后才会销毁),而不是处理一个请求新建一个进程的模式。

    所以 fd 是由 PHP-CGI 进程自己保存的。

    FD 的生命周期是跟着 PHP-CGI 进程的实例生命周期,而不是请求的周期了。
    mrpzx001
        21
    mrpzx001  
       234 天前
    workerman/swoole 能干这事吗?
    pretty66
        22
    pretty66  
    OP
       234 天前
    @mrpzx001 可以,但是项目用 workerman/swoole 这种架构重构,费时费力
    changz
        23
    changz  
       234 天前 via Android
    @pretty66 三行代码是少,耐不住服务数量多啊,你一个一个改?还不如在出口网关做代理,内网 tcp 握手效率一般还没到需要优化的时候
    pretty66
        24
    pretty66  
    OP
       234 天前
    @changz 出口网关? hook dns 流量到网关,然后如何做代理转发流量,你在代理这一层针对 https 类型的地址,你如何复用连接,是否会涉及到自签证书体系的管理,如果引入这一套东西这个架构的复杂度提升了不是一星半点。如果不包含自建证书体系,是否可以详细说说你的架构流程
    learningman
        25
    learningman  
       234 天前 via Android
    确实是玩具
    changz
        26
    changz  
       234 天前
    @pretty66 确实复杂,然而很多开源的组件例如 envoy 已经能做到这一点了
    pretty66
        27
    pretty66  
    OP
       234 天前
    @changz service mesh 体系小公司用起来也费劲的,envoy 和阿里的 mosn 这种 sidecar 主要用来解决的是微服务间的调用对 http 的支持很友好,针对调用第三方平台 api 特别是 https 是没办法支持连接复用的,可以看下 istio 的说明: https://istio.io/v1.8/zh/docs/tasks/traffic-management/egress/egress-control/#access-an-external-https-service
    vibbow
        28
    vibbow  
       233 天前 via Android
    @demoshengxw php 连接 redis/mysql
    地址前面加 p: 就是长连接模式
    demoshengxw
        29
    demoshengxw  
       233 天前 via iPhone
    @vibbow 但是 fpm 模式下,长链接意义不大。执行完毕还是会释放,所以找个代理维持和 redis 的链接是个好的方案。
    vibbow
        30
    vibbow  
       232 天前
    @demoshengxw 大哥,你不看回帖的么...
    fpm 模式下,长链接 是跨请求存在的
    shoaly
        31
    shoaly  
       229 天前
    @pretty66 挺好的一个思路, 一个小二美的东西
    pretty66
        32
    pretty66  
    OP
       228 天前
    @shoaly 感谢认可🤝
    shoaly
        33
    shoaly  
       227 天前
    @pretty66 其实老哥这个项目的尴尬是 , php 和 go 一般人都是 2 选 1, 不喜欢缝合, 我个人倒是也喜欢缝合在一起用, php 的效率和随意 + go 的一些网络请求的优势
    pretty66
        34
    pretty66  
    OP
       227 天前
    @shoaly 是的,多语言借助各自的优势相互配合能极大的提高效率;这个只是给有需要的人,还有一种针对老项目需要优化但是又不想重构的用这个比较方便。这个目前我们已经上线使用了,效果很不错,而且从开发到上线只用了一天
    shoaly
        35
    shoaly  
       227 天前
    @pretty66 只需要改一下 通用的请求方法, 或者不行就全盘搜索 curl , 理论上 也没几处
    dandankele
        36
    dandankele  
       115 天前
    @vibbow 有个问题,长连接是不是也需要服务端的支持?如果服务端在每次处理完成请求之后主动断开连接(例如对方服务端接口也是没使用长连接的常规的 php 实现的接口),即使客户端支持长连接,也无法维持这个长连接是吗?
    vibbow
        37
    vibbow  
       115 天前
    @dandankele 对于服务器端,长连接实际上不是 PHP 处理的,而是 apache/nginx 这些处理的,一般都是支持的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   954 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 19:32 · PVG 03:32 · LAX 12:32 · JFK 15:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.