PHP 如何发起异步请求
来源:互联网 发布:哨兵数据波段介绍 编辑:程序博客网 时间:2024/04/29 22:45
有人说,限制激发创造力。如果真这样,PHP就是成熟的创造性解决方案。我刚上周构建了调用Segment.io的API的PHP库,发现了各种不同的方法可以提高服务端请求性能。
设计客户端类向API发送数据时,我们的首要任务之一就是保证我的代码不影响到你的核心程序。这是很棘手的,尤其是使用单线程,无共享的语言,如PHP。
服务商PHP安装方式很多,让问题更复杂。幸运的,你的服务商允许你创建进程,写入文件和安装自己的扩展。不幸的话,你就得和一些纠结的邻居分享同一个安装配置,只能上传文件。
理想状态,我们喜欢用最小的满足实现各种情况。当运行PHP时(可能就一两个脚本),你应该深入理解这些。
我们尝试用三种主要方法实现PHP发出请求,以下就是。
一:快速打开一个套接字(Socket)
搜索PHP异步请求,最先的结果都是相同的方法:写一个Socket然后在等待返回前关闭它。
这个想法是开启一次连接到服务端,连接好就写入内容。Socket写入是很快的,而且你不要返回信息,写入后直接关闭连接。这就节省了等待一次往返的事件。
但是当你看StackOverflow上的评论,Socket到底发生了什么有一些争论。也让我疑问:Socket怎么实现的异步?
下面是我们的Socket实现:
01
<?php
02
private
function
request(
$body
) {
03
04
$protocol
=
"ssl"
;
05
$host
=
"api.segment.io"
;
06
$port
= 443;
07
$path
=
"/v1/"
.
$body
;
08
$timeout
=
$this
->options[
'timeout'
];
09
10
try {
11
# Open our socket to the API Server.
12
$socket
=
fsockopen
(
$protocol
.
"://"
.
$host
,
$port
,
13
$errno
,
$errstr
,
$timeout
);
14
15
# Create the request body,
and
make the request.
16
$req
=
$this
->create_body(
$host
,
$path
,
$content
);
17
fwrite(
$socket
,
$req
);
18
# ...
19
} catch (Exception
$e
) {
20
# ...
21
}
22
}
23
?>
最初的结果并不乐观。一次fsockopen花了300毫秒,偶尔更长。
事实证明,fsockopen是阻塞的——不是异步的!要了解到底发生了什么,需要深入研究fsockopen是怎么工作。当fsockopen选择协议时,需要考虑使用哪种socket。这个过程在连接完成前是阻塞的。
复习一下,internet的基本协议是TCP。它使电脑之间的信息传递可靠并有序。几乎所有HTTP都运行于TCP上。我们用HTTP来简化自定义的客户端使用。
这是TCP Socket创建连接:
- 客户端发送SYN消息给服务端
- 服务端返回SYN-ACK消息确认包
- 客户端发送最终ACK包及传送数据
TCP连接比较快,罪魁祸首是SSL需要的额外握手。SSL也实现在TCP上。TCP握手后又开始TLS握手。
光SSL连接就需要三次握手,更不要说加上创建公共密匙的时间。
浏览器的SSL连接可以共享密匙,避免允许访问的客户端和服务端重复握手。可是PHP执行的Socket无法共享密匙,我们只能每次都是重新连接。
还可以使用socket_set_nonblock创建“非阻塞”的Socket。不过这是在打开Socket的时候不阻塞,你还是要等待完成才能写入内容。如果精确考虑打开Socket写入数据的时间,页面加载会慢约100ms。
总结起来:
- Socket可以在有权限限制的PHP上运行
- fscokopen是阻塞的,即使不阻塞Socket也需要等待再写入数据
- SSL连接明显减慢连接,因为额外的握手和加密过程
- 打开连接使页面延迟100ms
二,写日志文件
如果你没有其他系统权限的时候,Sokets是非常棒的方案。这儿我们介绍一种在性能上更好的方法,那就是把所有事件以日志方式写到文件。这个日志文件可以被工作进程或者cron做"带外"处理。
基于文件方法的优点是具有最小的API对外请求。当php代码发出track 或者identify请求的时候,通过这种方法工作进程可以同时处理100个事件的请求,而不是仅一个请求。
这种方法的另外一个优点是php进程可以相对更快的记录文件,一个写操作往往只需要几毫秒。当php打开一个文件句柄的时候,用fwrite进行追加写是很简单的操作。由于纯php不具有“共享内存队列”机制,在这日志文件实际上和“共享内存队列”具有异曲同工的效果。
为了读日志文件,我利用analytics-pythonlibrary库写了一个python上传脚本。为了防止日志文件太大,脚本自动进行更名操作。可以动态的写php文件,还可以写内存中的文件句柄,在老请求创建的地方,新请求会创建一个新的日志文件。
这种方法没有太多的逻辑,只要开发者多写点cron任务,并且通过PyPI分别安装我们的python库。(这两段感觉是在给他们的python库做公告,既然python那么好,用php干嘛,bs)
方法总结(关键点):
- 写文件较快,系统资源开销少。
- 需要消耗磁盘空间,要求守护进程对文件有写权限。
- 必须运行工作进程处理带外的记录消息。
三:调用Curl过程
还有一个可选择的方法,我们可以通过exec 操作curl工具来发出请求。curl请求才可以做为独立进程一部分来完成,允许php代码继续执行,而不会阻塞socket连接。
这种方法的性能介于前面两种方法之间,比soket方法快,比写文件的方法花费更少的系统资源。
操作 forkd curl 方法,最简单的例子如下:
01
<?php
02
private
function
request(
$url
,
$payload
) {
03
04
$cmd
=
"curl -X POST -H 'Content-Type: application/json'"
;
05
$cmd
.=
" -d '"
.
$payload
.
"' "
.
"'"
.
$url
.
"'"
;
06
07
if
(!
$this
->debug()) {
08
$cmd
.=
" > /dev/null 2>&1 &"
;
09
}
10
11
exec
(
$cmd
,
$output
,
$exit
);
12
return
$exit
== 0;
13
}
14
?>
如果运行在生产模式,我们不希望等着fork进程的消息输出。所以代码中加添了"> /dev/null 2>&1 &"让进程正确的执行 ,而把任何可能输出都丢弃掉。
同样功能的shell脚本如下:
1
curl -X POST -H
'Content-Type: application/json'
\
2
-d '{
"batch"
:[{
"secret"
:
"testsecret"
,
"userId"
:
"some_user"
,
3
"event"
:
"PHP Fork Queued Event"
,
"properties"
:null,
"timestamp"
:
4
"2013-01-30T14:34:50-08:00"
,
"context"
:{
"library"
:
"analytics-php"
},
5
"action"
:
"track"
}],
"secret"
:
"testsecret"
}' \
6
'https://api.segment.io/v1/import'
> /dev/null 2>&1 &
脚本花费了大概1秒多一点的时间,占用大约4k的的常驻内存。而curl进程用了 标准SSL 300毫秒完成请求,exce调用立刻相应php程序。这使得服务页面能很快相应用户。
笔者用一台一般水平的机器试验,这种方法curl可以每秒响应100个左右https请求,而没有任何的内存开销。如果不用SSL,响应的请求会更多。
不用等待输入,Fork一个进程非常快。
curl花费了和socket同样时间响应一个请求,但是这个外带的过程。
调用curl需要仅仅普通的unix基础。
Fork发起一个简单的请求,只需要几毫秒的时间,但是大量的同步调用(forks)会导致系统变慢。
使用析构函数减少出栈请求
虽然不是一个异步请求的方法,但是我们可以用析构函数帮助我们进行批量API请求。
为了减少请求的数量,我们首先将他们放在内存中,然后对他们进行批处理。如果不适用运行时扩展,他们只能在一个单一的PHP脚本中运行。要做到这一点,我们首先初始化一个队列,在程序脚本运行结束时,将所有队列请求批量发送出去。
01
<?php
02
class
Analytics_SomeConsumer {
03
04
public
function
__construct() {
05
$this
->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
06
socket_set_nonblock(
$this
->socket);
07
socket_connect(
$this
->socket,
$this
->host,
$this
->port);
08
$this
->queue =
array
();
09
}
10
11
public
function
__destruct() {
12
$payload
= json_encode(
$this
->queue);
13
# ...
// wait for socket to be writeable
14
socket_write(
$this
->socket,
$payload
);
15
socket_close(
$this
->socket);
16
}
17
18
public
function
track(
$item
) {
19
array_push
(
$this
->queue,
$item
);
20
}
21
?>
队列中的对象创建后,当它被销毁时将队列进行刷新,这样保证了队列在每次请求时只刷新一次。
另外,当PHP解释器忙着来渲染页面而我们等待实际写入套接字时,我们可以以非阻塞的方式在构造函数中创建套接字,然后写入析构函数,这样可以预留更多的时间来建立连接。
抉择?
最完美的方法是用纯php实现,而不是调用其他进程,这也是响应请求保守做法。我们更趋向于开发者最方便,不会把精力分散在其他地方。
实际中,这往往是不可触及的。基于处理的问题的大小,以及系统的限制,以上每种方法都是有缺点和限制。由于简单的方法不可能满足实际中的用户状况,我们创建不同的适配器以支持不同用户的不同需求。
我们以调用curl方法做为基础,调用一个进程不会导致重大的页面性能负担,同时他还支持扩展到每属主每秒处理多请求。请求的数量通过usinglimits.conf严格限制。
高并发用户或者拥有高系统权限的用户可以实用日志文件系统。系统权限受限的用户(虚拟空间等)可以使用sockets方法。
最后,需要你去了解一下实际中你能拥有的系统限制和系统的负载情况。这些都最终决定你选择更合适的方法。
- PHP 如何发起异步请求
- PHP 如何发起异步请求
- PHP发起异步请求
- php如何发起POST DELETE GET POST 请求
- PHP如何发送异步请求
- php、asp 发起post请求
- php+crontab+shell方案实现的秒级定时发起异步请求回调方案
- 使用php发起多线程cc请求
- php curl使用代理发起请求
- php使用curl发起http请求
- 如何使用HttpURLConnection发起这http请求
- Ecstore中如何调用发起Ajax请求
- curl如何发起DELETE/PUT请求
- java结合jQuery的ajax异步发起jsonp请求
- PHP异步请求函数
- PHP----Ajax异步请求
- PHP----Ajax异步请求
- php 异步请求
- C/S与B/S的区别
- Linux下常见的压缩文件常用方法
- 并行,并发和分布式的区别
- pclint相关
- Android 飞行模式的设置(打开/关闭飞行模式,获取飞行状态状态)
- PHP 如何发起异步请求
- java在ACM中的应用
- js对象定义
- 计算这个月有多少天
- Ten Ways to Check if an Integer Is a Power Of Two in C
- 将VMware虚拟化平台与EMC VPLEX配合使用(一) - VPLEX概述
- MFC消息处理流程概述
- CAJview
- hadoop深入研究:(二)——java访问hdfs