子进程上传文件

最近看的阻塞和异步的资料看的比较多,对基础的概念也理解的深了一点点

因为上次项目中一个接口耗时比较长,所以来优化下

项目本身的逻辑上,填写手机号->生成小程序二维码->上传二维码到七牛->更改数据库邀请码图片->返回API响应

其中 生成二维码和上传二维码到七牛均是一个阻塞网络IO

改进的基本想法是:在还没有创建子进程时生成小程序二维码,得到url,然后创建进程,在主进程 响应API接口信息,子进程继续上传图片到七牛,上传成功修改数据库信息

废话不多说,直接上代码

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//getRandChar方法是自己编写的随机字符串方法
$file_name=uniqid(time().getRandChar(8)).'.jpg';

// 必须加载扩展
//pcntl_fork只有Linux才可以使用,因为本地调试,我写了一个不用子进程的方式兼容本地
if (function_exists ( "pcntl_fork" )) {
$pid = pcntl_fork (); // 创建子进程
Log::info('创建进程');
// 父进程和子进程都会执行下面代码
if ($pid == - 1) {
// 错误处理:创建子进程失败时返回-1.
Log::info('进程创建失败');
} else if ($pid) {
// 父进程会得到子进程号,所以这里是父进程执行的逻辑
// 如果不需要阻塞进程,而又想得到子进程的退出状态,则可以注释掉pcntl_wait($status)语句,或写成:

// 等待子进程中断,防止子进程成为僵尸进程。
pcntl_wait ( $status, WNOHANG );

//响应API信息
return show(['status'=>'10000','data'=> config('qiniu.domain').$file_name]);
} else {
//子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
//上传文件到七牛
$QrCodeUrl= $this->uploadQrCode($file_name,$image);
//原生mysql连接修改数据库
$this->updateQrCode($agent->agent_id,$QrCodeUrl);

exit ( 0 );
}
}else{
$QrCodeUrl= $this->uploadQrCode($file_name,$image);
$this->updateQrCode($agent->agent_id,$QrCodeUrl);

return show(['status'=>'10000','data'=> config('qiniu.domain').$file_name]);
}

show方法为自己封装的响应方法

uploadQrCode 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
$qiniuAuth = new QiniuAuth(config('qiniu.accessKey'), config('qiniu.secretKey'));
Log::info('文件上传配置accessKey:'.config('qiniu.accessKey').'-secretKey:'. config('qiniu.secretKey'));
$token = $qiniuAuth->uploadToken('aybc');
Log::info('文件上传token:'.$token);

$Upload = new qiniuUpload();
Log::info('子进程上传图片参数:'.$file_name);

list($ret, $error) = $Upload->put($token, $file_name, $image);
if($error != null){
Log::info('文件上传出错'.json_encode($error));
}
return $ret['key'];

R_DEVICE_ERROR错误

A PKCS #11 module returned CK R_DEVICE_ERROR, indicating that a problem has

原因大致是在进程中使用了外部的ssl秘钥的原因

https://stackoverflow.com/questions/15466809/libcurl-ssl-error-after-fork

https://stackoverflow.com/questions/26285311/ssl-requests-made-with-curl-fail-after-process-fork

以上是一些参考资料
解决方案我列举几个

  1. pcntl_exec
    讲子进程的请求外部资源的代码写到一个php文件,使用pcntl_exec 执行这个 PHP
    传参方式可以参考PHP官方文档pcntl-exec

  2. 子进程不使用curl方式
    使用fsockopen,file_get_content

  3. 使用 socket_create_pair

参考PHP文档socket_create_pair

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ary = array();
$strone = 'Message From Parent.';
$strtwo = 'Message From Child.';

if (socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $ary) === false) {
echo "socket_create_pair() failed. Reason: ".socket_strerror(socket_last_error());
}
$pid = pcntl_fork();
if ($pid == -1) {
echo 'Could not fork Process.';
} elseif ($pid) {
/*parent*/
socket_close($ary[0]);
if (socket_write($ary[1], $strone, strlen($strone)) === false) {
echo "socket_write() failed. Reason: ".socket_strerror(socket_last_error($ary[1]));
}
if (socket_read($ary[1], strlen($strtwo), PHP_BINARY_READ) == $strtwo) {
echo "Recieved $strtwo\n";
}
socket_close($ary[1]);
} else {
/*child*/
socket_close($ary[1]);
if (socket_write($ary[0], $strtwo, strlen($strtwo)) === false) {
echo "socket_write() failed. Reason: ".socket_strerror(socket_last_error($ary[0]));
}
if (socket_read($ary[0], strlen($strone), PHP_BINARY_READ) == $strone) {
echo "Recieved $strone\n";
}
socket_close($ary[0]);
}

  1. 不使用https
    父进程中采用http访问,或者所有子进程中都都采用http访问

PDO错误

PDO::prepare(): send of 66 bytes failed with errno=32 Broken pipe
如果你在子进程使用框架的Model修改数据库,可能会有这个保存
错误原因是框架里的数据库连接是使用的同一个连接
解决方案是在子进程使用原生方式创建一个数据库连接
updateQrCode 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$dbConfig = Config::get('database');

$mysqli = @new \mysqli($dbConfig['hostname'].':'.$dbConfig['hostport'], $dbConfig['username'], $dbConfig['password']);
if ($mysqli->connect_errno) {
Log::info("could not connect to the database:\n" . $mysqli->connect_error);//诊断连接错误
}
$mysqli->query("set names 'utf8';");//编码转化
$select_db = $mysqli->select_db($dbConfig['database']);
if (!$select_db) {
Log::info("could not connect to the db:\n" . $mysqli->error);
}
$sql = "UPDATE we_chat_agents SET qr_code = ? WHERE agent_id = ? ;";
$stmt = $mysqli->prepare($sql);

$stmt->bind_param("si",$qr_code,$agent_id );

/* Execute the statement */
if (!$stmt->execute()) {
Log::info("sql error:\n" . $mysqli->error);
}
$mysqli->close();
Log::info('子进程修改图片地址成功');

当然,并不一定都是这个原因

wait_timeout=3600

修改wait_timeout和关闭数据库持久连接都是可能的原因