您正在查看: 技术Note 分类下的文章

Centos7.9安装mysql5.7

Step1: 检测系统是否自带安装mysql
yum list installed | grep mysql

Step2: 删除系统自带的mysql及其依赖
yum -y remove mysql-libs.x86_64

Step3: 给CentOS添加rpm源,并且选择较新的源

在MySQL官网中下载YUM源rpm安装包:
https://dev.mysql.com/downloads/repo/yum/
centos7下载:Red Hat Enterprise Linux 7 / Oracle Linux 7 (Architecture Independent), RPM Package
centos6下载:Red Hat Enterprise Linux 6 / Oracle Linux 6 (Architecture Independent), RPM Package

下载yum源
wget https://repo.mysql.com//mysql80-community-release-el7-7.noarch.rpm
安装mysql源
yum localinstall mysql80-community-release-el7-7.noarch.rpm

修改源
vim /etc/yum.repos.d/mysql-community.repo
改变默认安装的mysql版本。比如要安装5.7版本,将5.7源的enabled=0改成enabled=1。然后再将其他版本如8.0版本的enabled=1改成enabled=0即可

开始安装
yum install mysql-community-server

Linux-CentOS6.9安装PHP7.3多版本php并存

安装依赖,已安装的忽略


yum groupinstall "Development Tools"
yum install libxml2-devel openssl-devel libcurl-devel gd-devel gmp-devel libicu-devel recode recode-devel libxslt libxslt-devel

cmake安装


wget https://cmake.org/files/v3.18/cmake-3.18.0-Linux-x86_64.sh
chmod 777 cmake-3.18.0-Linux-x86_64.sh
./cmake-3.18.0-Linux-x86_64.sh
ln -s /work/soft/cmake-3.18.0/bin/cmake /usr/bin/cmake  

安装libzip


wget https://libzip.org/download/libzip-1.5.2.tar.gz
tar -zxvf libzip-1.5.2.tar.gz
cd libzip-1.5.2
mkdir build 
cd build 
cmake ..        (#注意:cmake后面有两个小数点)
make && make install

安装PHP-7.3


官网手动下载php源码包 https://www.php.net/downloads.php
tar -zxvf php-7.3.27.tar.gz
cd php-7.3.27/

./configure \
--prefix=/usr/local/php73 \
--with-config-file-path=/usr/local/php73/etc \
--disable-debug \
--disable-phpdbg \
--enable-mysqlnd \
--enable-bcmath \
--with-bz2=/usr \
--enable-calendar \
--with-curl \
--enable-exif \
--enable-fpm \
--with-fpm-user=nginx \
--with-fpm-group=nginx \
--with-freetype-dir \
--enable-ftp \
--with-gd \
--with-gettext=/usr \
--with-gmp \
--with-iconv \
--with-iconv-dir=/usr/local/libiconv \
--enable-intl \
--with-jpeg-dir \
--enable-mbstring \
--with-openssl \
--with-mhash \
--enable-pcntl \
--with-pdo-mysql=mysqlnd \
--with-png-dir \
--with-recode \
--enable-shmop \
--enable-soap \
--enable-sockets \
--enable-sysvmsg \
--enable-sysvsem \
--enable-sysvshm \
--enable-wddx \
--with-xmlrpc \
--with-xsl \
--with-zlib=/usr \
--enable-zip \
--with-libxml-dir=/usr \
--enable-xml \
--with-mysqli=mysqlnd \
--enable-fileinfo \
--enable-opcache \
--with-libdir=lib64

make && make install
cp php.ini-development /usr/local/php73/etc/php.ini

修改php.ini配置


vim /usr/local/php73/etc/php.ini
# 找到 ;cgi.fix_pathinfo=0 修改为
cgi.fix_pathinfo=1

# 找到 ;date.timezone = 修改为
date.timezone = PRC

查看版本


/usr/local/php73/bin/php -v

配置php-fpm


cp /usr/local/php73/etc/php-fpm.conf.default /usr/local/php73/etc/php-fpm.conf
vi /usr/local/php73/etc/php-fpm.conf
# 找到 ;pid = run/php-fpm.pid 修改为
pid = /usr/local/php73/var/run/php-fpm.pid

修改www.conf


cp /usr/local/php73/etc/php-fpm.d/www.conf.default /usr/local/php73/etc/php-fpm.d/www.conf
vi /usr/local/php73/etc/php-fpm.d/www.conf

user = nginx
group = nginx

# 找到 listen = 127.0.0.1:9000 如果启了php5 或其它 9000端口被占用的话 修改为
listen = 127.0.0.1:9002

配置 php-fpm 启动脚本 注册为系统服务 设置开机启动


cp /work/soft/php-7.3.27/sapi/fpm/php-fpm /usr/local/php73/bin
cp /work/soft/php-7.3.27/sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm73

将/etc/init.d/php-fpm73开始的注释部分php-fpm改为php-fpm73 虽然是注释,但在Linux下注册系统服务要求的格式;如果你不注册为系统服务则不需要改;


vi /etc/init.d/php-fpm73

#! /bin/sh

### BEGIN INIT INFO
# Provides:          php-fpm73
# Required-Start:    $remote_fs $network
# Required-Stop:     $remote_fs $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts php-fpm73
# Description:       starts the PHP FastCGI Process Manager daemon
### END INIT INFO

授予可执行权限


chmod +x /etc/init.d/php-fpm73

添加php-fpm73至服务列表并设置开机自启


chkconfig --add php-fpm73
chkconfig --list php-fpm73
chkconfig php-fpm73 on

创建php73命令的软连接

ln -s /usr/local/php73/bin/php /usr/bin/php73

php-fpm73 几个参数 start|stop|force-quit|restart|reload|status|configtest


service php-fpm73 start       # 启动服务
service php-fpm73 stop        # 停止服务
service php-fpm73 restart     # 重启服务
service php-fpm73 reload      # 平滑重启服务
service php-fpm73 force-quit  # 强制退出服务
service php-fpm73 status      # 查看服务状态
service php-fpm73 configtest  # 检查配置

启动php-fpm73服务


service php-fpm73 configtest   # 检查配置 显示如下 说明配置没有问题
# [07-Jun-2018 11:53:45] NOTICE: configuration file /usr/local/php73/etc/php-fpm.conf test is 

successful
service php-fpm73 start        # 启动

# Starting php-fpm  done 启动成功

查看php-frm进程 显示php-fpm73版本 和之前的php-fpm56版本


ps -ef |grep php

root      1805     1  0 09:48 ?        00:00:00 php-fpm: master process (/usr/local/php56/etc/php-fpm.conf)                                                                                        
nobody    1806  1805  0 09:48 ?        00:00:00 php-fpm: pool www                                                                                                                                          
nobody    1807  1805  0 09:48 ?        00:00:00 php-fpm: pool www                                                                                                                                          
root     31580     1  0 11:54 ?        00:00:00 php-fpm: master process (/usr/local/php72/etc/php-fpm.conf)                                                                        
www      31581 31580  0 11:54 ?        00:00:00 php-fpm: pool www                                                                                                                  
www      31582 31580  0 11:54 ?        00:00:00 php-fpm: pool www                                                                                                                  
root     31585  3311  0 11:54 pts/1    00:00:00 grep php

php中流行的RPC框架?

什么是RPC框架?

如果用一句话概括RPC就是:远程调用框架(Remote Procedure Call)那什么是远程调用?通常我们调用一个php中的方法,比如这样一个函数方法: localAdd(10, 20),localAdd方法的具体实现要么是用户自己定义的,要么是php库函数中自带的,也就说在localAdd方法的代码实现在本地,它是一个本地调用!远程调用意思就是:被调用方法的具体实现不在程序运行本地,而是在别的某个远程地方。

远程调用原理

比如 A (client) 调用 B (server) 提供的remoteAdd方法:

首先A与B之间建立一个TCP连接;
然后A把需要调用的方法名(这里是remoteAdd)以及方法参数(10, 20)序列化成字节流发送出去;
B接受A发送过来的字节流,然后反序列化得到目标方法名,方法参数,接着执行相应的方法调用(可能是localAdd)并把结果30返回;
A接受远程调用结果,输出30。
RPC框架就是把我刚才说的这几点些细节给封装起来,给用户暴露简单友好的API使用。

远程调用的好处

解耦:当server需要对方法内实现修改时,client完全感知不到,不用做任何变更;这种方式在跨部门,跨公司合作的时候经常用到,并且方法的提供者我们通常称为:服务的暴露。

RPC与Socket有什么区别?

通过上面的简单阐述,好像RPC与Socket 好像啊。都是调用远程的方法,都是client/server模式,我之前也写了一篇文章: 细说socket 那他们有啥区别呢?
RPC(远程过程调用)采用客户机/服务器模式实现两个进程之间相互通信。socket是RPC经常采用的通信手段之一,RPC是在Socket的基础上实现的,它比socket需要更多的网络和系统资源。除了Socket,RPC还有其他的通信方法,比如:http、操作系统自带的管道等技术来实现对于远程程序的调用。微软的Windows系统中,RPC就是采用命名管道进行通信。

RPC与REST有什么区别?

通过了解RPC后,我们知道是RPC是client/server模式的,调用远程的方法,REST也是我们熟悉的一套API调用协议方法,它也是基于client/server模式的,调用远程的方法的,那他俩又有啥区别呢?

REST API 和 RPC 都是在 Server端 把一个个函数封装成接口暴露出去,以供 Client端 调用,不过 REST API 是基于HTTP协议的,REST致力于通过http协议中的POST/GET/PUT/DELETE等方法和一个可读性强的URL来提供一个http请求。而 RPC 则可以不基于 HTTP协议
因此,如果是后端两种语言互相调用,用 RPC 可以获得更好的性能(省去了 HTTP 报头等一系列东西),应该也更容易配置。如果是前端通过 AJAX 调用后端,那么用 REST API 的形式比较好(因为无论如何也避不开 HTTP 这道坎)。

php中流行的rpc框架有哪些

既然php是世界上最好的语言,那php中流行的RPC框架有哪些呢?

先列举下: phprpc,yar, thrift, gRPC, swoole, hprose

因为时间和精力有限,不可能一个一个的去学习和使用,我选几个世面上用的最多的几个用下吧。因为RPC原理是一样的,都是Client/Server模式,只是每个框架的使用方式不一样而已。

Linux-CentOS6.9安装PHP7.2 多版本php并存

安装依赖

yum groupinstall "Development Tools"
yum install libxml2-devel openssl-devel libcurl-devel gd-devel gmp-devel libicu-devel recode recode-devel libxslt libxslt-devel

安装PHP-7.2

官网手动下载php源码包 https://www.php.net/downloads.php


tar zxvf php-7.2.6.tar.gz
cd php-7.2.6/

./configure \
--prefix=/usr/local/php72 \
--with-config-file-path=/usr/local/php72/etc \
--disable-debug \
--disable-phpdbg \
--enable-mysqlnd \
--enable-bcmath \
--with-bz2=/usr \
--enable-calendar \
--with-curl \
--enable-exif \
--enable-fpm \
--with-fpm-user=nginx \
--with-fpm-group=nginx \
--with-freetype-dir \
--enable-ftp \
--with-gd \
--with-gettext=/usr \
--with-gmp \
--with-iconv \
--with-iconv-dir=/usr/local/libiconv \
--enable-intl \
--with-jpeg-dir \
--enable-mbstring \
--with-openssl \
--with-mhash \
--enable-pcntl \
--with-pdo-mysql=mysqlnd \
--with-png-dir \
--with-recode \
--enable-shmop \
--enable-soap \
--enable-sockets \
--enable-sysvmsg \
--enable-sysvsem \
--enable-sysvshm \
--enable-wddx \
--with-xmlrpc \
--with-xsl \
--with-zlib=/usr \
--enable-zip \
--with-libxml-dir=/usr \
--enable-xml \
--with-mysqli=mysqlnd \
--enable-fileinfo \
--enable-opcache \
--with-libdir=lib64


# --enable-opcache            # PHP7.2 相对于 PHP7.1 主要优化 opcache
# --with-libdir=lib64         # 64位系统需指定 lib64 32位系统去掉此项
# --enable-maintainer-zts     # ZTS 线程安全,默认 NTS 非线程安全

# make clean   # 如果之前编译过 需要清除上次的编辑结果

make
make install

cp php.ini-development /usr/local/php72/etc/php.ini

修改php.ini配置

vim /usr/local/php72/etc/php.ini
# 找到 ;cgi.fix_pathinfo=0 修改为
cgi.fix_pathinfo=1

# 找到 ;date.timezone = 修改为
date.timezone = PRC

查看版本

/usr/local/php72/bin/php -v
PHP 7.2.6 (cli) (built: Jun  7 2018 11:08:13) ( ZTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

配置php-fpm

cp /usr/local/php72/etc/php-fpm.conf.default /usr/local/php72/etc/php-fpm.conf
vi /usr/local/php72/etc/php-fpm.conf
# 找到 ;pid = run/php-fpm.pid 修改为
pid = /usr/local/php72/var/run/php-fpm.pid

修改www.conf

cp /usr/local/php72/etc/php-fpm.d/www.conf.default /usr/local/php72/etc/php-fpm.d/www.conf
vi /usr/local/php72/etc/php-fpm.d/www.conf

user = www
group = www

# 找到 listen = 127.0.0.1:9000 如果启了php5 或其它 9000端口被占用的话 修改为
listen = 127.0.0.1:9001

配置 php-fpm 启动脚本 注册为系统服务 设置开机启动


cp /work/soft/php-7.2.6/sapi/fpm/php-fpm /usr/local/php72/bin
cp /work/soft/php-7.2.6/sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm72

将/etc/init.d/php-fpm72开始的注释部分php-fpm改为php-fpm72 虽然是注释,但在Linux下注册系统服务要求的格式;如果你不注册为系统服务则不需要改;


vi /etc/init.d/php-fpm72

#! /bin/sh

### BEGIN INIT INFO
# Provides:          php-fpm72
# Required-Start:    $remote_fs $network
# Required-Stop:     $remote_fs $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts php-fpm72
# Description:       starts the PHP FastCGI Process Manager daemon
### END INIT INFO

授予可执行权限


chmod +x /etc/init.d/php-fpm72

添加php-fpm72至服务列表并设置开机自启


chkconfig --add php-fpm72
chkconfig --list php-fpm72
chkconfig php-fpm72 on

创建php73命令的软连接


ln -s /usr/local/php72/bin/php /usr/bin/php72

php-fpm72 几个参数 start|stop|force-quit|restart|reload|status|configtest


service php-fpm72 start       # 启动服务
service php-fpm72 stop        # 停止服务
service php-fpm72 restart     # 重启服务
service php-fpm72 reload      # 平滑重启服务
service php-fpm72 force-quit  # 强制退出服务
service php-fpm72 status      # 查看服务状态
service php-fpm72 configtest  # 检查配置

启动php-fpm72服务


service php-fpm72 configtest   # 检查配置 显示如下 说明配置没有问题
# [07-Jun-2018 11:53:45] NOTICE: configuration file /usr/local/php72/etc/php-fpm.conf test is 

successful
service php-fpm72 start        # 启动

# Starting php-fpm  done 启动成功

查看php-frm进程 显示php-fpm72版本 和之前的php-fpm56版本


ps -ef |grep php

root      1805     1  0 09:48 ?        00:00:00 php-fpm: master process (/usr/local/php56/etc/php-fpm.conf)                                                                                        
nobody    1806  1805  0 09:48 ?        00:00:00 php-fpm: pool www                                                                                                                                          
nobody    1807  1805  0 09:48 ?        00:00:00 php-fpm: pool www                                                                                                                                          
root     31580     1  0 11:54 ?        00:00:00 php-fpm: master process (/usr/local/php72/etc/php-fpm.conf)                                                                        
www      31581 31580  0 11:54 ?        00:00:00 php-fpm: pool www                                                                                                                  
www      31582 31580  0 11:54 ?        00:00:00 php-fpm: pool www                                                                                                                  
root     31585  3311  0 11:54 pts/1    00:00:00 grep php

Nginx新建server


mkdir -p /data0/vhosts/php72
chmod +w /data0/vhosts/php72
chown -R www:www /data0/vhosts/php72

修改nginx.conf配置 新增server


server
{
    listen       80;
    server_name  dev.php72.gocuber.com;
    index index.html index.htm index.php;
    root  /data0/vhosts/php72;

    location ~* .php$ {
        fastcgi_index   index.php;
        fastcgi_pass    127.0.0.1:9001;
        include         fastcgi_params;
        fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
    }

    access_log  /data0/logs/wwwlogs.log  wwwlogs;
}

检查nginx配置 平滑重启nginx


/usr/local/nginx/sbin/nginx -t
/usr/local/nginx/sbin/nginx -s reload

/data0/vhosts/php72新建phpinfo.php index.php

phpinfo();

官方安装说明

页面最下方列出了常见问题及解决办法
http://php.net/manual/zh/install.unix.nginx.php

官方 configure 选项列表说明
http://php.net/manual/zh/configure.about.php

转载 thinkphp-queue 笔记

[TOC]

thinkphp-queue 笔记

前言

当前笔记中的内容针对的是 thinkphp-queue 的 v2.0 版本

传统的程序执行流程一般是 即时|同步|串行的,在某些场景下,会存在并发低,吞吐量低,响应时间长等问题。在大型系统中,一般会引入消息队列的组件,将流程中部分任务抽离出来放入消息队列,并由专门的消费者作针对性的处理,从而降低系统耦合度,提高系统性能和可用性。

一般来说,可以抽离的任务具有以下的特点:

  • 允许延后|异步|并行处理 (相对于传统的 即时|同步|串行 的执行方式)

    • 允许延后

    抢购活动时,先快速缓冲有限的参与人数到消息队列,后续再排队处理实际的抢购业务;

    • 允许异步

    业务处理过程中的邮件,短信等通知

    • 允许并行

    用户支付成功之后,邮件通知,微信通知,短信通知可以由多个不同的消费者并行执行,通知到达的时间不要求先后顺序。

  • 允许失败和重试

    • 强一致性的业务放入核心流程处理

    • 无一致性要求或最终一致即可的业务放入队列处理

thinkphp-queue 是thinkphp 官方提供的一个消息队列服务,它支持消息队列的一些基本特性:

  • 消息的发布获取执行删除重发失败处理延迟执行超时控制

  • 队列的多队列内存限制启动停止守护

  • 消息队列可降级为同步执行

thinkphp-queue 内置了 RedisDatabaseTopthinkSync这四种驱动。本文主要介绍 thinkphp-queue 结合其内置的 redis 驱动的使用方式和基本原理。

注1:如无特殊说明,下文中的 ‘消息’ 和 ‘任务’两个词指代的是同一个概念,即队列中的一个成员。该成员对消息队列而言是其内部保存的消息; 对业务应用而言是一个待执行的任务。请根据语境区分。

注2:本文编写时(2019-01-30)使用的 thinkphp-queue 的版本号是 v2.0 。如有变更,请以官方最新版为准。

一 代码示例

先通过一段代码,了解一下 thinkphp-queue 的基本使用流程。

目标:

在业务控制器中推送一个新消息到一个名为 ‘helloJobQueue’ 的队列中,该消息中包含我们自定义的业务数据,然后,编写一个名为 Hello 的消费者类,并通过命令行去调用该消费者类获取这个消息,拿到定义的数据。

1.1 安装 thinkphp-queue

composer install topthink/think-queue

1.2 搭建消息队列的存储环境

  • 使用 Redis [推荐]

    安装并启动 Redis 服务
  • 使用数据库 [不推荐]

    CREATE TABLE `prefix_jobs` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `queue` varchar(255) NOT NULL,
      `payload` longtext NOT NULL,
      `attempts` tinyint(3) unsigned NOT NULL,
      `reserved` tinyint(3) unsigned NOT NULL,
      `reserved_at` int(10) unsigned DEFAULT NULL,
      `available_at` int(10) unsigned NOT NULL,
      `created_at` int(10) unsigned NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.3 配置消息队列的驱动

根据选择的存储方式,在 \application\config\queue.php 这个配置文件中,添加消息队列对应的驱动配置

   return [
       'connector'  => 'Redis',        // Redis 驱动
       'expire'     => 60,        // 任务的过期时间,默认为60秒; 若要禁用,则设置为 null 
       'default'    => 'default',        // 默认的队列名称
       'host'       => '127.0.0.1',    // redis 主机ip
       'port'       => 6379,        // redis 端口
       'password'   => '',        // redis 密码
       'select'     => 0,        // 使用哪一个 db,默认为 db0
       'timeout'    => 0,        // redis连接的超时时间
       'persistent' => false,        // 是否是长连接
     
   //    'connector' => 'Database',   // 数据库驱动
   //    'expire'    => 60,           // 任务的过期时间,默认为60秒; 若要禁用,则设置为 null
   //    'default'   => 'default',    // 默认的队列名称
   //    'table'     => 'jobs',       // 存储消息的表名,不带前缀
   //    'dsn'       => [],

   //    'connector'   => 'Topthink',    // ThinkPHP内部的队列通知服务平台 ,本文不作介绍
   //    'token'       => '',
   //    'project_id'  => '',
   //    'protocol'    => 'https',
   //    'host'        => 'qns.topthink.com',
   //    'port'        => 443,
   //    'api_version' => 1,
   //    'max_retries' => 3,
   //    'default'     => 'default',

   //    'connector'   => 'Sync',        // Sync 驱动,该驱动的实际作用是取消消息队列,还原为同步执行
   ];

1.3.1 配置文件中的 expire 参数说明

expire 参数指的是任务的过期时间, 单位为秒。 过期的任务,其准确的定义是

  1. 任务的状态为执行中

  2. 任务的开始执行的时刻 + expire > 当前时刻

expire 不为null 时 ,thinkphp-queue 会在每次获取下一个任务之前检查并重发过期(执行超时)的任务。

expire 为null 时,thinkphp-queue 不会检查过期的任务,性能相对较高一点。但是需要注意:

  • 这些执行超时的任务会一直留在消息队列中,需要开发者另行处理(删除或者重发)!

对expire 参数理解或者使用不当时,很容易产生一些bug,后面会举例提到。

1.4 消息的创建与推送

我们在业务控制器中创建一个新的消息,并推送到 helloJobQueue 队列

新增 \application\index\controller\JobTest.php 控制器,在该控制器中添加 actionWithHelloJob 方法

<?php
/**
* 文件路径: \application\index\controller\JobTest.php
* 该控制器的业务代码中借助了thinkphp-queue 库,将一个消息推送到消息队列
*/
namespace app\index\controller;
  use think\Exception;

  use think\Queue;

  class JobTest {
  /**
   * 一个使用了队列的 action
   */
  public function actionWithHelloJob(){
      
      // 1.当前任务将由哪个类来负责处理。 
      //   当轮到该任务时,系统将生成一个该类的实例,并调用其 fire 方法
      $jobHandlerClassName  = 'app\index\job\Hello'; 
      
      // 2.当前任务归属的队列名称,如果为新队列,会自动创建
      $jobQueueName        = "helloJobQueue"; 
      
      // 3.当前任务所需的业务数据 . 不能为 resource 类型,其他类型最终将转化为json形式的字符串
      //   ( jobData 为对象时,存储其public属性的键值对 )
      $jobData             = [ 'ts' => time(), 'bizId' => uniqid() , 'a' => 1 ] ;
      
      // 4.将该任务推送到消息队列,等待对应的消费者去执行
      $isPushed = Queue::push( $jobHandlerClassName , $jobData , $jobQueueName );    
      
      // database 驱动时,返回值为 1|false  ;   redis 驱动时,返回值为 随机字符串|false
      if( $isPushed !== false ){  
          echo date('Y-m-d H:i:s') . " a new Hello Job is Pushed to the MQ"."<br>";
      }else{
          echo 'Oops, something went wrong.';
      }
  }
 }

注意: 在这个例子当中,我们是手动指定的 $jobHandlerClassName ,更合理的做法是先定义好消息名称与消费者类名的映射关系,然后由某个可以获取该映射关系的类来推送这个消息。这样,生产者只需要知道消息的名称,而无需指定哪个消费者类来处理。

除了 Queue::push( $jobHandlerClassName , $jobData , $jobQueueName ); 这种方式之外,还可以直接传入 Queue::push( $jobHandlerObject ,null , $jobQueueName ); 这时,需要在 $jobHandlerObject 中定义一个 handle() 方法,消息队列在执行到该任务时会自动反序列化该对象,并调用其 handle()方法。 该方式中, 数据需要提前挂载在 $jobHandlerObject 对象上。

1.5 消息的消费与删除

编写 Hello 消费者类,用于处理 helloJobQueue 队列中的任务

新增 \application\index\job\Hello.php 消费者类,并编写其 fire() 方法

 <?php
  /**
   * 文件路径: \application\index\job\Hello.php
   * 这是一个消费者类,用于处理 helloJobQueue 队列中的任务
   */
  namespace app\index\job;

  use think\queue\Job;

  class Hello {
      
      /**
       * fire方法是消息队列默认调用的方法
       * @param Job            $job      当前的任务对象
       * @param array|mixed    $data     发布任务时自定义的数据
       */
      public function fire(Job $job,$data)
      {
          // 有些消息在到达消费者时,可能已经不再需要执行了
          $isJobStillNeedToBeDone = $this->checkDatabaseToSeeIfJobNeedToBeDone($data);
          if(!$isJobStillNeedToBeDone){
              $job->delete();
              return;
          }
        
          $isJobDone = $this->doHelloJob($data);
        
          if ($isJobDone) {
              // 如果任务执行成功, 记得删除任务
              $job->delete();
              print("<info>Hello Job has been done and deleted"."</info>\n");
          }else{
              if ($job->attempts() > 3) {
                  //通过这个方法可以检查这个任务已经重试了几次了
                  print("<warn>Hello Job has been retried more than 3 times!"."</warn>\n");
                  
                          $job->delete();
                  
                  // 也可以重新发布这个任务
                  //print("<info>Hello Job will be availabe again after 2s."."</info>\n");
                  //$job->release(2); //$delay为延迟时间,表示该任务延迟2秒后再执行
              }
          }
      }
      
      /**
       * 有些消息在到达消费者时,可能已经不再需要执行了
       * @param array|mixed    $data     发布任务时自定义的数据
       * @return boolean                 任务执行的结果
       */
      private function checkDatabaseToSeeIfJobNeedToBeDone($data){
          return true;
      }

      /**
       * 根据消息中的数据进行实际的业务处理...
       */
      private function doHelloJob($data) 
      {
          print("<info>Hello Job Started. job Data is: ".var_export($data,true)."</info> \n");
          print("<info>Hello Job is Fired at " . date('Y-m-d H:i:s') ."</info> \n");
          print("<info>Hello Job is Done!"."</info> \n");
          
          return true;
      }
  }

至此,所有的代码都已准备完毕,在运行消息队列之前,我们先看一下现在的目录结构:

目录结构-代码示例

1.6 发布任务

在浏览器中访问 http://your.project.domain/index/job_test/actionWithHelloJob ,可以看到消息推送成功。

浏览器提示消息推送结果

1.7 处理任务

切换当前终端窗口的目录到项目根目录下,执行

php think queue:work --queue helloJobQueue

可以看到执行的结果类似如下:

命令行执行结果

至此,我们成功地经历了一个消息的 创建 -> 推送 -> 消费 -> 删除 的基本流程

下文,将介绍 thinkphp-queue 的详细使用方法。如配置介绍,基本原理,各种特殊情况的处理等

二 详细介绍

2.1 命令模式

  • queue:subscribe 命令 [官方未提供示例, 略过]

  • queue:work 命令

work 命令: 该命令将启动一个 work 进程来处理消息队列。

php think queue:work --queue helloJobQueue
  • queue:listen 命令

listen 命令: 该命令将会启动一个 listen 进程 ,然后由 listen 进程通过 proc_open(‘php think queue:work --queue="%s" --delay=%s --memory=%s --sleep=%s --tries=%s’) 的方式来周期性地创建一次性的 work 进程来消费消息队列, 并且限制该 work 进程的执行时间, 同时通过管道来监听 work 进程的输出,。

php think queue:listen --queue helloJobQueue

2.2 命令行参数

  • Work 模式

    php think queue:work \
    --daemon            //是否循环执行,如果不加该参数,则该命令处理完下一个消息就退出
    --queue  helloJobQueue  //要处理的队列的名称
    --delay  0 \        //如果本次任务执行抛出异常且任务未被删除时,设置其下次执行前延迟多少秒,默认为0
    --force  \          //系统处于维护状态时是否仍然处理任务,并未找到相关说明
    --memory 128 \      //该进程允许使用的内存上限,以 M 为单位
    --sleep  3 \        //如果队列中无任务,则sleep多少秒后重新检查(work+daemon模式)或者退出(listen或非daemon模式)
    --tries  2          //如果任务已经超过尝试次数上限,则触发‘任务尝试次数超限’事件,默认为0
  • Listen 模式

    php think queue:listen \
    --queue  helloJobQueue \   //监听的队列的名称
    --delay  0 \         //如果本次任务执行抛出异常且任务未被删除时,设置其下次执行前延迟多少秒,默认为0
    --memory 128 \       //该进程允许使用的内存上限,以 M 为单位
    --sleep  3 \         //如果队列中无任务,则多长时间后重新检查
    --tries  0 \         //如果任务已经超过重发次数上限,则进入失败处理逻辑,默认为0
    --timeout 60         // work 进程允许执行的最长时间,以秒为单位

可以看到 listen 模式下,不包含 --deamon 参数,原因下面会说明

2.3 work 模式和 listen 模式的区别

两者都可以用于处理消息队列中的任务

区别在于:

  • 2.3.1 执行原理不同

    • work 命令是单进程的处理模式。

    按照是否设置了 --daemon 参数,work命令又可分为单次执行和循环执行两种模式。

    • 单次执行:不添加 --daemon参数,该模式下,work进程在处理完下一个消息后直接结束当前进程。当队列为空时,会sleep一段时间然后退出。

    • 循环执行:添加了 --daemon参数,该模式下,work进程会循环地处理队列中的消息,直到内存超出参数配置才结束进程。当队列为空时,会在每次循环中sleep一段时间。

    • listen 命令是 双进程 + 管道 的处理模式。

    listen命令所在的进程会循环地创建 单次执行模式的 work 进程,每次创建的 work 进程只消费一个消息就会结束, 然后 listen 进程再创建一个新的 work 进程,

    • listen 进程会定时检查当前的 work 进程执行时间是否超过了 --timeout 参数的值, 如果已超时, 则 listen 进程会 kill 掉 work 进程, 然后抛出异常

    • listen 进程会通过管道来监听当前的 work 进程的输出, 当 work 进程有输出时, listen 进程会将输出写入到 stdout / stderr

    • listen 进程会定时通过 proc_get_status() 来监控当前的 work 进程是否仍在运行, work 进程消费完一个任务之后, work 进程就结束了,其状态会变成 terminated, 此时 listen 进程就会重新创建一个新的 work 进程并对其计时, 新的 work 进程开始消费下一个任务

  • 2.3.2 结束时机不同

    • work 命令的结束时机在上面的执行原理部分已叙述,此处不再重复

    • listen 命令中,listen 进程和 work 进程会在以下情况下结束:

      • listen 进程会定时检查当前的 work 进程的执行时间是否超过了 --timeout 参数的值,如果已超时, 此时 listen 进程会先 kill 掉当前的 work 进程, 然后抛出一个 ProcessTimeoutException 异常并结束 listen 进程

      • listen 进程会定时检查自身使用的内存是否超过了 --memory 参数的值,如果已超过, 此时 listen 进程会直接 die 掉, work 进程也会自动结束.

  • 2.3.3 性能不同

    • work 命令是在脚本内部做循环,框架脚本在命令执行的初期就已加载完毕;

    • 而listen模式则是处理完一个任务之后新开一个work进程,此时会重新加载框架脚本。

    因此: work 模式的性能会比listen模式高

    注意:当代码有更新时,work 模式下需要手动去执行 php think queue:restart 命令重启队列来使改动生效;而listen 模式会自动生效,无需其他操作。

  • 2.3.4 超时控制能力

    • work 模式本质上既不能控制进程自身的运行时间,也无法限制执行中的任务的执行时间。

    举例来说,假如你在某次上线之后,在上文中的 \application\index\job\Hello.php 消费者的fire方法中添加了一段死循环 :

    public function fire(){
       while(true){ //死循环
           $consoleOutPut->writeln("<info>I am looping forever inside a job.</info> \n");
           sleep(1);
       }
    }  
那么这个循环将永远不能停止,直到任务所在的进程超过内存限制或者由管理员手动结束。这个过程不会有任何的告警。更严重的是,如果你配置了expire ,那么这个死循环的任务可能会污染到同样处理 `helloJobQueue` 队列的其他work进程,最后好几个work进程将被卡死在这段死循环中。详情后文会说明。

**work 模式下的超时控制能力,实际上应该理解为 多个work 进程配合下的过期任务重发能力。**
  • 而 listen 命令可以限制 listen 进程创建的 work 进程的最大执行时间。

    listen 命令可通过 --timeout 参数限制 work 进程允许运行的最长时间,超过该时间限制后, work 进程会被强制 kill 掉, listen 进程本身也会抛出异常并结束;

  • 这里有必要补充一下 expire 和 timeout 之间的区别:

    • expire 在配置文件中设置,timeout 在 listen命令 的命令行参数中设置,而且,expire 和 timeout 是两个不同层次上的概念:

- expire 是指任务的过期时间。这个时间是全局的,影响到所有的work进程。(不管是独立的work命令还是 listen 模式下创建的的 work 进程) 。expire 针对的对象是 **任务**。
- timeout 是指 work 进程的超时时间。这个时间只对当前执行的 listen 命令有效。timeout 针对的对象是 **work 进程**。
  • 2.3.5 使用场景不同

根据上面的介绍,可以看到,

work 命令的适用场景是:

  • 任务数量较多

  • 性能要求较高

  • 任务的执行时间较短

  • 消费者类中不存在死循环,sleep() ,exit() ,die() 等容易导致bug的逻辑

listen命令的适用场景是:

  • 任务数量较少

  • 任务的执行时间较长(如生成大型的excel报表等),

  • 任务的执行时间需要有严格限制

2.4 消息队列的开始,停止与重启

  • 开始一个消息队列:

    php think queue:work
  • 停止所有的消息队列:

    php think queue:restart
  • 重启所有的消息队列:

    php think queue:restart 
    php think queue:work 

2.5 多模块,多任务的处理

  • 多模块

单模块项目推荐使用 app\job 作为任务类的命名空间

多模块项目可用使用 app\module\job 作为任务类的命名空间 也可以放在任意可以自动加载到的地方

  • 多任务

如果一个任务类里有多个小任务的话,在发布任务时,需要用 任务的类名@方法名app\lib\job\Job2@task1app\lib\job\Job2@task2

注意:命令行中的 --queue 参数不支持@解析

多任务例子:

  • \application\index\controller\JobTest.php 控制器中,添加 actionWithMultiTask()方法:

  1. function actionWithMultiTask(){

    $taskType = $_GET['taskType'];
    switch ($whichTask) {

      case 'taskA':
          $jobHandlerClassName  = 'application\index\job\MultiTask@taskA';
          $jobDataArr = ['a'    => '1'];
          $jobQueueName = "multiTaskJobQueue";    
          break;
      case 'taskB':
          $jobHandlerClassName  = 'application\index\job\MultiTask@taskB';
          $jobDataArr = ['b'    => '2'];
          $jobQueueName = "multiTaskJobQueue";        
          break;
       default:
          break;

    }

    $isPushed = Queue::push($jobHandlerClassName, $jobDataArr, $jobQueueName);
    if ($isPushed !== false) {
    echo("the $taskType of MultiTask Job has been Pushed to ".$jobQueueName ."
    ");
    }else{
    throw new Exception("push a new $taskType of MultiTask Job Failed!");
    }
    }

  • 新增 \application\index\job\MultiTask.php 消费者类,并编写其 taskA()taskB()方法

    <?php
    /**
    * 文件路径: \application\index\job\MultiTask.php
    * 这是一个消费者类,用于处理 multiTaskJobQueue 队列中的任务
  1. appindexjob;

  2. thinkqueueJob;

  3. MultiTask {

    public function taskA(Job $job,$data){

       
       $isJobDone = $this->_doTaskA($data);
    
       if ($isJobDone) {
           $job->delete();
           print("Info: TaskA of Job MultiTask has been done and deleted"."\n");
       }else{
           if ($job->attempts() > 3) {
               $job->delete();     
           }
       }

    }

    public function taskB(Job $job,$data){

       
       $isJobDone = $this->_doTaskA($data);
    
       if ($isJobDone) {
           $job->delete();
           print("Info: TaskB of Job MultiTask has been done and deleted"."\n");
       }else{
           if ($job->attempts() > 2) {
               $job->release();     
           }
       }

    }

    private function _doTaskA($data) {

       print("Info: doing TaskA of Job MultiTask "."\n");
       return true;

    }

    private function _doTaskB($data) {

       print("Info: doing TaskB of Job MultiTask "."\n");
       return true;

    }

2.6 消息的延迟执行与定时执行

延迟执行,相对于即时执行,是用来限制某个任务的最早可执行时刻。在到达该时刻之前,该任务会被跳过。

可以利用该功能实现定时任务

使用方式:

  • 在生产者业务代码中:

// 即时执行
$isPushed = Queue::push($jobHandlerClassName, $jobDataArr, $jobQueueName);

// 延迟 2 秒执行
$isPushed = Queue::later( 2, $jobHandlerClassName, $jobDataArr, $jobQueueName);

// 延迟到 2017-02-18 01:01:01 时刻执行
$time2wait = strtotime('2017-02-18 01:01:01') - strtotime('now');    
$isPushed = Queue::later($time2wait,$jobHandlerClassName, $jobDataArr, $jobQueueName);
  • 在消费者类中:

// 重发,即时执行
$job->release();

// 重发,延迟 2 秒执行
$job->release(2);

// 延迟到 2017-02-18 01:01:01 时刻执行
$time2wait = strtotime('2017-02-18 01:01:01') - strtotime('now');
$job->release($time2wait);
  • 在命令行中:

//如果消费者类的fire()方法抛出了异常且任务未被删除时,将自动重发该任务,重发时,会设置其下次执行前延迟多少秒,默认为0
php think queue:work --delay 3  

2.7 消息的重发

thinkphp-queue 中,消息的重发时机有3种:

  • 2.7.1 在消费者类中手动重发:

if( $isJobDone === false){
    $job->release();
}
  • 2.7.2 work进程自动重发,需同时满足以下两个条件

    • 消费者类的 fire() 方法抛出了异常

    • 任务未被删除

  • 2.7.3 当配置了 expire 不为 null 时,work 进程内部每次查询可用任务之前,会先自动重发已过期的任务。

补充:

在database 模式下,2.7.1 和 2.7.2 中的重发逻辑是先删除原来的任务,然后插入一个新的任务。2.7.3 中的重发时机是直接更新原任务。

而在redis 模式下,3种重发都是先删除再插入。

不管是哪种重发方式,重发之后,任务的已尝试次数会在原来的基础上 +1 。

此外,消费者类中需要注意,如果 fire() 方法中可能抛出异常,那么

  • 如果不需要自动重发的话, 请在抛出异常之前将任务删除 $job->delete() ,否则会被框架自动重发。

  • 如果需要自动重发的话,请直接抛出异常,不要在 fire() 方法中又手动使用 $job->release() , 这样会导致该任务被重发两次,产生两个一样的新任务。

2.8 任务的失败回调及告警

当同时满足以下条件时,将触发任务失败回调:

  • 命令行的 --tries 参数的值大于0

  • 任务的已尝试次数大于 命令行的 --tries 参数

  • 开发者添加了 queue_failed 事件标签及其对应的回调代码

  • 消费者类中定义了 failed() 方法,用于接收任务失败的通知

注意, queue_failed 标签需要在安装了 thinkphp-queue 之后 手动\application\tags.php 文件中添加。

首先,我们添加 queue_failed 事件标签, 及其对应的回调方法

// 文件路径: \application\tags.php
// 应用行为扩展定义文件
return [
    // 应用初始化
    'app_init'     => [],
    // 应用开始
    'app_begin'    => [],
    // 模块初始化
    'module_init'  => [],
    // 操作开始执行
    'action_begin' => [],
    // 视图内容过滤
    'view_filter'  => [],
    // 日志写入
    'log_write'    => [],
    // 应用结束
    'app_end'      => [],

    // 任务失败统一回调,有四种定义方式
    'queue_failed'=> [
      
         // 数组形式,[ 'ClassName' , 'methodName']
        ['application\\behavior\\MyQueueFailedLogger', 'logAllFailedQueues']
         
         // 字符串(静态方法),'StaicClassName::methodName'
         // 'MyQueueFailedLogger::logAllFailedQueues'   
      
         // 字符串(对象方法),'ClassName',此时需在对应的ClassName类中添加一个名为 queueFailed 的方法
         // 'application\\behavior\\MyQueueFailedLogger'
         
         // 闭包形式
         /*
         function( &$jobObject , $extra){
             // var_dump($jobObject);
             return true;
         }
         */
    ]
];

这里,我们选择数组形式的回调方式,新增 \application\behavior\MyQueueFailedLogger 类,添加一个 logAllFailedQueues() 方法

<?php
/**
 * 文件路径: \application\behavior\MyQueueFailedLogger.php
 * 这是一个行为类,用于处理所有的消息队列中的任务失败回调
 */

namespace app\behavior;


class MyQueueFailedLogger {
       
    const should_run_hook_callback = true;
  
    /**
     * @param $jobObject   \think\queue\Job   //任务对象,保存了该任务的执行情况和业务数据
     * @return bool     true                  //是否需要删除任务并触发其failed() 方法
     */
    public function logAllFailedQueues(&$jobObject){
        
        $failedJobLog = [
            'jobHandlerClassName'   => $jobObject->getName(), // 'application\index\job\Hello'
            'queueName' => $jobObject->getQueue(),               // 'helloJobQueue'     
            'jobData'   => $jobObject->getRawBody()['data'],  // '{'a': 1 }'
            'attempts'  => $jobObject->attempts(),            // 3
        ];
        var_export(json_encode($failedJobLog,true));
        
           // $jobObject->release();     //重发任务
          //$jobObject->delete();         //删除任务
          //$jobObject->failed();      //通知消费者类任务执行失败
        
        return self::should_run_hook_callback;         
    }
}

需要注意该回调方法的返回值:

  • 返回 true 时,系统会自动删除该任务,并且自动调用消费者类中的 failed() 方法

  • 返回 false 时,系统不会自动删除该任务,也不会自动调用消费者类中的 failed() 方法,需要开发者另行处理失败任务的删除和通知。

最后,在消费者类中,添加 failed() 方法

/**
 * 文件路径: \application\index\job\HelloJob.php
 */

/**
 * 该方法用于接收任务执行失败的通知,你可以发送邮件给相应的负责人员
 * @param $jobData  string|array|...      //发布任务时传递的 jobData 数据
 */
public function failed($jobData){
    send_mail_to_somebody() ; 
    
    print("Warning: Job failed after max retries. job data is :".var_export($data,true)."\n"; 
}

这样,就可以做到任务失败的记录告警

2.9 处理过期的任务

过期这个概念用文字比较难描述清楚,建议先看一下 深入理解3.4 消息处理的详细流程图

三 深入理解

3.1 thinkphp-queue 中消息与队列的保存方式

  • Redis

在 Redis 中,每一个 队列 都三个key 与之对应 ,以 helloJobQueue 队列举例,其在redis 中的保存方式为:

| key名 | 类型 | 说明 |
| ----------------------------- | --------------- | -------------- |
| queues:helloJobQueue | List , 列表 | 待执行的任务列表 |
| queues:helloJobQueue:delayed | Sorted Set,有序集合 | 延迟执行和定时执行的任务集合 |
| queues:helloJobQueue:reserved | Sorted Set,有序集合 | 执行中的任务集合 |

使用的:分隔符, 只是用来表示相关key的关联性。本身没有特殊含义。使用分隔符是一种常见的组织key的方式。

其中,在queues:helloJobQueue 列表中,每个元素的形式如下:

redis中的队列-queue

queues:helloJobQueue:delayedqueues:helloJobQueue:delayed 有序集合中,每个元素的形式如下:

redis中的队列-queue-reserved

可以看到,在有序集合中,每个元素代表一个任务,该元素的 Score 为该任务的入队时间戳,任务的 value 为json 格式,保存了任务的执行情况和业务数据。将value decode 为数组后形式如下:

[
  'job'  => 'application\\index\\job\\Hello' ,  // jobHandlerClassName,消费者类的类名 
  'data' => [                      // 生产者传入的业务数据
     'time' => '2017-02-18 16:20:10',
     'data' => 'I have 648 apples'
  ],
  'id'   => '77IasdasadIasdadadadKL8t',    // 一个随机的32位字符串
  'attempts' => 2                // 任务的已尝试次数
]

redis驱动下,为了实现任务的延迟执行和过期重发,任务将在这三个key中来回转移,详情可见 3.5

  • Database

在 Database 中,每个任务对应到表中的一行,queue 字段用来区分不同的队列。

表的字段结构如下:

数据库字段说明

其中,payLoad 字段保存了消息的执行者和业务数据,payLoad 字段采用 json 格式的字符串来保存消息,将其 decode 为数组后形式如下:

[
 'job'   => 'application\\index\\job\\Hello', // jobHandlerClassName,消费者类的类名 
 'data'  => string|array|integer|object       // 生产者传入的业务数据
]

3.2 thinkphp-queue 的目录结构和类关系图

thinkphp-queue的文件目录

这些类构成了消息队列中的几个角色:

角色类名说明
命令行Command + Worker负责解析命令行参数,控制队列的启动,重启
驱动Queue + Connector负责队列的创建,以及消息的入队,出队等操作
任务Job用于将消息转化为一个任务对象,供消费者使用
生产者业务代码负责消息的创建与发布
消费者业务代码负责任务的接收与执行

各个类之间的关系图如下:

thinkphp-queue类关系图

3.3 Deamon模式的执行流程

Daemon模式与非daemon模式状态图

3.4 Database模式下消息处理的详细流程

下图中,展示了database 模式下消息处理的详细流程,redis 驱动下大体类似

Database模式下消息获取和执行的具体流程

3.5 redis 驱动下的任务重发细节

在redis驱动下,为了实现任务的延迟执行和过期重发,任务将在这三个key中来回转移。

在3.4 Database模式下消息处理的消息流程中,我们知道,如果配置的expire 不是null ,那么 thinkphp-queue的work进程每次在获取下一个可执行任务之前,会先尝试重发所有过期的任务。而在redis驱动下,这个步骤则做了更多的事情,详情如下:

  1. queue:xxx:delayed 的key中查询出有哪些任务在当前时刻已经可以开始执行,然后将这些任务转移到 queue:xxx 的key的尾部。

  2. queue:xxx:reserved 的key中查询出有哪些任务在当前时刻已经过期,然后将这些任务转移到 queue:xxx的key的尾部。

  3. 尝试从 queue:xxx 的key的头部取出一个任务,如果取出成功,那么,将这个任务转移到 queue:xxx:reserved 的key 的头部,同时将这个任务实例化成任务对象,交给消费者去执行。

用图来表示这个步骤的具体过程如下:

redis队列中的过期任务重发步骤--执行前:

redis队列中的过期任务重发步骤-执行前

redis队列中的过期任务重发步骤--执行后:

redis队列中的过期任务重发步骤--执行后

3.6 thinkphp-queue的性能

  • 测试环境 :

虚拟机 Ubuntu 16.04 , PHP 7.1 ,TP5,Redis 3.2 , 双核 I5 6400,3G 内存

  • 测试方式 :

使用 Redis 驱动,在一个控制器中循环推送 40000 条消息到消息队列;

使用php think queue:work --daemon去消费这些消息,计算推送和消费各自所耗的时间。

  • 测试结果 :

在最简单的逻辑下,平均每秒中可推送8000个消息,平均每秒可消费200个消息。

注意:由于在测试时,Host 机本身的cpu和内存长期100%,并且虚拟机中的各项服务并未专门调优,因此该测试结果并不具备参考性

3.7 thinkphp-queue 的注意事项

  • 3.7.1 使用建议

    • 任务完成后, 使用 $job->delete() 删除任务

    • 在消费者类的 fire() 方法中,使用 $job->attempt() 检查任务已执行次数,对于次数异常的,作相应的处理。

    • 在消费者类的 fire() 方法中,根据业务数据来判断该任务是否已经执行过,以避免该任务被重复执行。

    • 编写失败回调事件,将事件中失败的任务及时通知给开发人员。

  • 3.7.2 使用了 queue:work --daemon ,但更新代码后没有使用 queue:restart 重启 work 进程, 使得 work 进程中的代码与最新的代码不同,出现各种问题。

  • 3.7.3 使用了 queue:work --daemon ,但是消费者类的 fire() 方法中存在死循环,或 sleep(n) 等逻辑,导致消息队列被堵塞;或者使用了 exit() , die() 这样的逻辑,导致work进程直接终止 。

  • 3.7.4 配置的 expire 为null ,但并没有自行处理过期的任务,导致过期的任务得不到处理,且一直占用消息队列的空间。

  • 3.7.5 配置的 expire 不为null ,但配置的 expire 时间太短,以至于 expire 时间 < 消费者的 fire() 方法所需时间 + 删除该任务所需的时间 ,那么任务将被误认为执行超时,从而被消息队列还原为待执行状态。

  • 3.7.6 queue:work 属于 cli 模式, 其入口文件是根目录下的 think 文件, 网页访问属于 sapi 模式, 其入口是 public 目录下的 index.php 文件. 如果需要在入口文件中定义自定义常量, 记得去修改对应的入口文件. 具体可参考 chichoyi 的说明 https://github.com/coolseven/notes/issues/7

四 拓展

4.1 队列的稳定性和拓展性

  • 稳定性:不管是 listen 模式还是 work 模式,都建议使用 supervisor 或者 自定义的cron 脚本,去定时检查 work 进程是否正常

  • 拓展性: 当某个队列的消费者不足时,再给这个队列添加 work进程即可。

4.2 消息队列的可视化管理工具

  • 队列管理,队列的列表,队列的 work 进程数量控制,队列的任务数量变化趋势 //TBD

  • 任务管理,任务的列表,添加/撤回/查询任务,修改任务的 执行者/执行时间/优先级/数据 等 //TBD

4.2 编写自定义的 thinkphp-queue 驱动

//TBD

4.3 编写消息队列的单元测试

//TBD

4.4 与其他PHP消息队列库的对比

TP5的消息队列与Laravel的消息队列比较相似,下面是与laravel 中的消息队列的一些对比:

thinkphp-queue (v1.1.2)laravel-queue (v5.3)
内置的驱动Database,Redis,Sync,TopThinkDatabase,Redis, Sync(在laravel中称为 null)。
Redis驱动要求安装redis的C扩展安装 predis 包 + LUA脚本
推送任务允许推送 消费者类名,消费者对象允许推送消费者类名,消费者对象,闭包
失败任务处理触发失败回调事件 (有Bug)触发失败回调事件 + 移动任务到 failed_jobs表?
消息订阅subscribe 命令+ Topthink驱动(注:未实现/未提供)subscribe 命令 + 安装IronMQ 驱动
删除任务消费者类中手动删除任务完成后自动删除
推送到多个队列需自己实现原生支持
延迟执行支持 (有Bug)支持
消息重发支持支持
检查已执行次数原生支持需在消费者类中显式 use 相关的 trait
执行方式work 模式 + listen 模式work 模式 + listen 模式
进程命令开启,停止,重启开启,停止,重启
任务命令展示失败任务列表,重试某个失败任务,删除某个失败任务
支持的事件失败回调事件失败回调事件,支持消费前事件,消费后事件