使用自定义环境运行异步任务

在实际的业务场景中,总有很多的任务需要实时的长时间的执行,例如:

  • 需要使用tcp/http长连接去外部获取数据
  • 需要实时监控一些数据
  • 任务队列的处理,发短信、发报警、发邮件等等

在新浪云的云应用环境,可能大家熟知的有三种处理方式:

  • 通过定时任务处理,因为定时任务可以执行30分钟,普通的http请求最长只能处理300秒
  • 通过taskqueue任务队列处理,接口程序将一个大任务打散成小的任务,然后通过taskqueue将任务的参数通过http的GET参数通过taskqueue的异步回调到另外的接口处理,taskqueue天生有并发度,可以并行处理多个请求
  • 通过新浪云提供的后台进程服务实现,后台进程帮助你启动一个PHP的进程完成响应的处理,大多的后台进程都需要这样的死循环实现:
while (1) {  
   // 任务的执行逻辑
   sleep(1);
}

本文将为大家介绍一个更通俗、接地气的进程处理方式,使用新浪云提供的自定义运行环境实现可控的进程管理,以PHP的为例。

实现的流程图

为了达成我们的目的,主要有这么几个问题需要解决:

  • 运行环境的创建
  • 数据怎么获取
  • 代码怎么更新
  • 进程怎么启动、重启、管理
  • 日志的处理

为了说清楚这几个问题,画了一个示意图如下:

下面我们将逐步的讲述以上的点。

运行环境的制作

自定义的运行环境当前提供的都是一个空的操作系统,目前提供的分类有centos、ubuntu、debian、opensuse、alpine、fedora,每个操作系统的分类都支持几个主流的版本,你可以在创建应用的时候选择具体的分类。

本文假定你已经注册好新浪云账号并完成实名认证,且账户有钱了,如果以上的基本要求没达到,请从 https://www.sinacloud.com 注册账户并完成以上步骤。

进入云应用的创建应用页面https://sae.sina.com.cn/?m=apps&a=create ,选择开发语言选择『自定义』,部署方式选择『手工部署』,选定操作系统和版本(你最熟悉的即可),本文以centos的7.5.1804为例,如图:

创建完成后几秒种后,属于你的一个空的容器应用就完成了,进入容器管理页面,

可以看到容器已经在运行中了,点击上图标示的终端就可以进入到terminal,就可以开始运行环境的制作了。

安装PHP7的环境

安装PHP7需要使用remi的仓库,请参考以下步骤(如果不是centos的环境请自行解决)

yum -C install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm http://rpms.remirepo.net/enterprise/remi-release-7.rpm  
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 /etc/pki/rpm-gpg/RPM-GPG-KEY-remi /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7  
yum -y install yum-utils  
yum-config-manager --enable remi,remi-php70  
yum install -y vim git svn php php-gd php-intl php-tidy php-pdo php-cli php-process php-xml php-mysql php-mbstring php-bcmath php-pecl-swoole php-pecl-redis php-pecl-imagick php-pecl-zip php-pecl-xdebug which --skip-broken  
echo 'date.timezone=Asia/Shanghai' > /etc/php.d/00-docker-php-date-timezone.ini  
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin  

为了简便,也可以快速执行

curl 'http://cdn.sinacloud.net/opensource/php7/centos_init_php7.sh'|bash  

安装是一个比较漫长的过程,因为安装的软件包比较多,需要耐心等待。,以上步骤执行完成后,PHP7及常用扩展、svn、Git、vim等软件就都安装完成了,验证一下PHP的版本及安装的扩展:

看到这个输出就安装完成了:

Complete!  
All settings correct for using Composer  
Downloading...  
Composer (version 1.6.5) successfully installed to: /usr/bin/composer.phar  
Use it: php /usr/bin/composer.phar  

验证一下PHP的版本:

[root@demo1 /]# php -v 
PHP 7.0.31 (cli) (built: Jul 17 2018 15:30:29) ( NTS )  
Copyright (c) 1997-2017 The PHP Group  
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies  
    with Xdebug v2.6.0, Copyright (c) 2002-2018, by Derick Rethans

到此,运行环境就已经安装完成了。

如果你需要安装其他的扩展,你可以编译安装或者直接使用yum安装,如果使用yum 安装,类似执行即可:

yum install -y php-pecl-swoole  
# 提示,这是示例。这个swoole上面已经安装了,不用重复安装了
数据怎么获取

容器中可以使用同一个账户下的其他应用的数据(共享型数据库)及账户级服务的服务,例如redis、rds等。

代码怎么更新

以上在安装软件时,已经帮你装好了svn和git客户端,你可以在容器上直接svn checkout或者git clone其他标准环境应用的仓库,以SVN为例。

mkdir -p /data1/code/  
cd /data1/code  
svn checkout "https://svn.sinacloud.com/gequ/2/" .  
# 这里的https://svn.sinacloud.com/gequ/2/ 要换成你自己的代码仓库

如上图的步骤,就下载了应用gequ的版本2代码到容器的本地了。

注意,你提交带仓库的代码不会自动更新到容器上,每次修改后需要手工更新。

进程启动、重启、管理

写一个简单的进程示例代码process.php

<?php  
if (!function_exists('get')) {  
    function get($url, $timeout = 5, $headers = false)
    {
        $s = curl_init($url);
        curl_setopt($s, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
        curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($s, CURLINFO_HEADER_OUT, true);
        if ($headers) {
            curl_setopt($s, CURLOPT_HTTPHEADER, $headers); 
        }
        curl_setopt($s, CURLOPT_TIMEOUT, $timeout);
        $ret = curl_exec($s);
        $info = curl_getinfo($s);
        curl_close($s);
        if ($info['http_code'] > 0) {
            return $ret;
        }
        return false;
    }
}

while (1) {  
    $url = 'http://ip.cn';
    $ip = get($url, 5, array('User-Agent: curl/7.29.0'));
    $time_now = date('Y-m-d H:i:s', time());
    echo(sprintf("Now: %s\n", $time_now));
    echo(sprintf("%s\n", $ip));
    sleep(10);
}

以上程序每10秒钟请求一个外部的地址获取本机的出口ip,然后输出,直接执行 php process.php

[root@demo1 /data1/code/process]# php process.php 
Now: 2018-08-03 10:35:23  
当前 IP:123.125.23.213 来自:北京市 联通
Now: 2018-08-03 10:35:34  
当前 IP:123.125.23.213 来自:北京市 联通

可以看到每十秒钟就输出了一段IP信息。可以看到通过php process.php就可以启动一个PHP的进程,去完成需求,那么也不可能一直挂在这个页面上一直执行着,因此需要一个进程管理器去管理这个进程,supervisor横空出世。关于supervisor的介绍请参考 http://www.supervisord.org/ 它们官网。

安装supervisor
yum install supervisor -y  
写supervisor的配置文件

supervisor的默认配置文件是/etc/supervisord.conf,其中配置的加载配置设置为

[include]
files = supervisord.d/*.ini  

从配置可以看出,只要是/etc/supervisord.d/ 目录下所有以ini结束的文件都会额外加载。

将示例的进程写到配置文件/etc/supervisord.d/process.ini
[root@demo1 /data1/code/process]# cat /etc/supervisord.d/process.ini
[program:process]
command=php /data1/code/process/process.php  
stdout_logfile=/tmp/process.log  
autostart=true  
autorestart=true  
startsecs=5  
priority=3  
stopasgroup=true  
killasgroup=true  
启动supervisord
[root@demo1 /data1/code/process]# supervisord 
/usr/lib/python2.7/site-packages/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directo
ry); you probably want to specify a "-c" argument specifying an absolute path to a configuration file for improved security.  
  'Supervisord is running as root and it is searching '
查看进程列表和状态
[root@demo1 /data1/code/process]# supervisorctl status
process                          RUNNING   pid 426, uptime 0:00:08  

可以看到进程已经启动。

查看输出日志

如supervisor的配置文件中设置,将日志定位到了 /tmp/process.log

[root@demo1 /data1/code/process]# tail -f /tmp/process.log
当前 IP:123.125.23.213 来自:北京市 联通
Now: 2018-08-03 10:45:35  
当前 IP:123.125.23.213 来自:北京市 联通
Now: 2018-08-03 10:45:45  
当前 IP:123.125.23.213 来自:北京市 联通
Now: 2018-08-03 10:46:00  
Now: 2018-08-03 10:46:11  
当前 IP:123.125.23.213 来自:北京市 联通

可以看到,日志确实是输出了。

注意:容器的本地文件系统大小是有限制的,你可以将日志的输出写到stdout,这样新浪云的容器管理进程会接管你输出的日志,这样从日志中心就可以查看。

稍微修改一下supervisor的配置/etc/supervisord.d/process.ini

[root@demo1 /data1/code/process]# cat /etc/supervisord.d/process.ini
[program:process]
command=php /data1/code/process/process.php  
stdout_logfile=/proc/1/fd/1  
stderr_logfile=/proc/1/fd/2  
stdout_logfile_maxbytes=0  
stderr_logfile_maxbytes=0  
autostart=true  
autorestart=true  
startsecs=5  
priority=3  
stopasgroup=true  
killasgroup=true  

重启supervisor

supervisorctl reload  

注意:修改了配置supervisor配置文件需要使用reload重启supervisord进程,如果是修改了PHP代码,使用supervisorctl restart process即可,如果重启所有进程,使用supervisorctl restart all

将日志写到 /proc/1/fd/1 这个路径即可,为什么需要配置stdoutlogfilemaxbytes,请参考 https://stackoverflow.com/questions/25195880/supervisord-redirect-process-stdout-to-console

现在从管理面板的日志中心就可以看到日志了,这样日志文件就不会占用容器的磁盘,如图:

MySQL has gone away的问题

在从写PHP的接口到写PHP的进程时基本所有人都会遇到这个错误:

MySQL error 2006: mysql server has gone away  

这个是一个很常见的问题,而且不算是系统的bug,因为MySQL的服务器为了确保能及时回收无用的连接,设定了这个参数,即客户端连上MySQL后过了N秒没有任何活动,MySQL就主动断开连接,客户端再用这个连接去查询数据,就会报这个错误,在新浪云的共享型MySQL上,这个N是30秒,且不可修改。

因此客户端需要自己处理这个错误,怎么处理呢?其实很简单,就是执行完sql后判断下错误码,如果是2006,就重新连接MySQL,然后再执行一下sql即可。

以mysql扩容为例,伪代码是这样的:

$link = mysql_connect(主机:端口,用户名,密码);
mysql_select_db(数据库名, $link);  
$ret = mysql_query("SQL语句", $link);
if (!$ret && mysql_errno($link) == 2006) {  
    // 重新连接
    $link = mysql_connect(主机:端口,用户名,密码);
    mysql_select_db(数据库名, $link);
    $ret = mysql_query("SQL语句", $link);
} 

注意:以上是伪代码描述,不能直接使用,实际使用中请结合你的MySQL操作封装修改逻辑。