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

247 天前
 pretty66

Fastcar - PHP HTTP 长连接代理

背景

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

解决

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

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

Fastcar 原理简介

开源地址

最后

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

1969 次点击
所在节点    程序员
37 条回复
vibbow
247 天前
更正一个错误:php-fpm 模式下是可以复用链接的
pretty66
247 天前
@vibbow 多个 php-fpm 生命周期之间有办法复用连接吗,有没有文档甩个我看看
vibbow
247 天前
@pretty66 https://www.php.net/pfsockopen

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

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

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

```
curl_setopt($ch, CURLOPT_FRESH_CONNECT, false);
curl_setopt($ch, CURLOPT_FORBID_REUSE, false);
```
pretty66
246 天前
@changz 如果项目代码封装比较好,只需要在请求调用的函数增加仅仅三行代码,对正常的业务无任何影响;做 hook dns 再做代理,代理你使用正向代理吗,正向代理你怎么保持连接复用
fenglangjuxu
246 天前
有考虑过 fastcar 重启的问题么
它挂掉了 是不是请求就中断了
pretty66
246 天前
@demoshengxw 能用 sidecar 是很好的方案,你们公司业务规模肯定不小;一般的小公司的技术是用不起来这一套的,fastcar 也类似一个 sidecar 的程序内部维护连接池,只专注于解决 php http 请求的问题,比较适合小业务;只需要在程序调用的地方增加三行 curl 设置就能保持长连接。当然如果有实力使用 service mesh 架构肯定是极力推荐 sidecar 方案,我们公司也是使用的 service mesh 架构
pretty66
246 天前
@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
246 天前
@vibbow #8 请教下,stream_socket_client 复用链接的话。stream_socket_client 创建的 fd,该保存在那里,在 fpm 下每个请求都会创建新的 php 解释实例,上个 php 实例创建的 fd 应该被销毁掉了。
vibbow
246 天前
@codersdp1 fastcgi 模式下,一个 PHP 进程实例会处理很多请求(我这里是处理 10000 请求数后才会销毁),而不是处理一个请求新建一个进程的模式。

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

FD 的生命周期是跟着 PHP-CGI 进程的实例生命周期,而不是请求的周期了。

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

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

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

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

© 2021 V2EX