本文是非常初级的,大牛勿喷。
一、概要
一般开发网站项目中大部分会带有用户系统,简单的来说就是用户注册、登录的模块。这年头字符串作为账户名已不再流行,而改为更具有独立性、安全性的电子邮箱地址或手机号作为账户名。但这种利用邮箱地址和手机号码作为账号的业务逻辑也带来了额外的验证操作---需要确认该邮箱地址是注册人拥有(或手机号是注册人拥有)。
验证邮箱地址或手机号是注册人拥有,最直接的方法就是给该邮箱发送一封注册验证邮件(或该手机号发送一条验证码短信),注册页面进行验证码效验或邮件正文中的激活url进行点击操作。
作为PHP开发者,利用PHP发送邮件是很容易实现的(比如:《PHP下利用PHPMailer配合QQ邮箱下的域名邮箱发送邮件》),而PHP发送短信则更容易实现,买个短信验证码api接口,调用接口即可实现。无论是发送邮件还是调用短信api接口发送短信,都有一个很棘手的问题:请求发送邮件或短信时服务器端都有连接到第三方的网络延迟和开销,而用户在浏览器端注册时是零容忍网络延迟的,更为棘手的是你无法控制通过用户注册的http请求触发发送邮件或短信的加载时间(服务器端与邮件服务器或短信api服务器的交互的延迟时间无法控制)。
这时候解耦中间件就派上用处了。所谓解耦,就是用户注册动作中的http请求并不直接触发发送邮件或手机短信的动作;而是传递一个发送邮件或短信的信号给队列,由中间件去不间断的读取队列并真正执行发送邮件或短信的任务。至于什么是队列就不多吧唧了。
二、一种简单的解耦中间件原理
概要里已经提出了解决用户注册过程直接通过http请求触发发送邮件或短信的网络延迟问题的基本流程。代码层面,用户注册界面相关的php代码并不负责处理发送邮件或短信的执行;而是负责将相关格式化的信号数据写入队列,解耦中间件则专门执行发送邮件或短信的任务,借助队列也将调用发送邮件的接口或短信接口的网络延迟从用户层面拿走,提升了用户体验。
这种简单的中间件也是由php代码实现,只不过是通过php-cli模式运行的,而不需要由用户的http请求触发执行。这就要求得有服务器的ssh权限,普通的虚拟主机恐怕是无法实现了。
上图所示:白色方框区域为web服务器过程处理的示意结构。
用户注册时发送邮件或短信的业务逻辑,传统处理过程就是走流程1(用户注册动作的http请求触发)经过php处理直接走流程2请求发送邮件或短信,那么用户在注册过程中势必会在执行流程2的过程中耗费较多的时间---网络延迟不可控。
现在添加的简单中间件流程则是走流程1,再经流程4将发送邮件、短信的任务数据提交至队列数据库;对注册用户而言,执行完毕流程4一个http请求就结束,这个任务执行的时间是很短且可控的。
接下来“简单中间件”就默默的在服务器端读取队列数据库,执行发送邮件或短信的任务,这个时候就与流程1也就是用户注册动作触发的http请求无关了。至于发送邮件、短信是否成功就是“简单中间件”的事了。
三、“简单中间件”业务逻辑的PHP代码实现
说了这么多轻理论型的东西,啥是队列数据库、啥是简单中间件都快搞迷糊了。既然是队列,就是先进先出的数据结构,可以写入也可以读出,只不过可以写入速度大于读出速度,这样就起到了一个“排队等候”的作用。那有人说了,为什么不用堆栈?这个就看具体的业务逻辑了。
PHP中可以拿来当做队列使的最简单的就是redis,更具体点的就是redis的list结构体或有序集合体。既然是简单中间件,就用字符串类型的list结构好了。先服务器要安装redis并安装相应的php操纵redis的扩展(不装也行,自己拼包呗),这个不是本文的关键,建议安装phpredis扩展;附上一篇文章:https://blog.jjonline.cn/linux/111.html
PHP处理用户注册层面的代码,也就是上图对流程1的响应代码:处理用户注册后,接下来要发送邮件、短信的任务了,这个时候将需要发送的邮件数据备好(短信数据备好),用redis的list结构写入redis即可。示例代码如下:
#用户注册数据效验代码 ...code... # ...code.... #用户注册数据写入数据代码 ...code... # ...code.... #写入发送邮件或短信任务数据进入redis---list结构体 #假设这里的发送邮件或短信的任务数据是一个数组 $MailData = array('mailAddr'=>'这里是接受邮件的email地址','mailContetn'=>'这里自定邮件正文');/* 还可以定义其他字段用于标记 这个就得自由发挥了 */ $RedisModel = new Redis; $RedisModel->connect('127.0.0.1',6379); #这里不再进行连接是否完成的检测 请自主完成 # ...code.... #对redis进行list结构的数据写入 $value = json_encode($MailData); /* 成功返回队列长度 失败返回false */ return $RedisModel->lPush('LB',$value);/*redis-list链表的key字段需要固定 这里LB可以任意 只是需要统一*/
这个时候这套简单中间件的用户注册端的代码完成,当然实现用户账号注册数据效验与数据库写入并不是本文的核心所在。
===
接下来就是“简单中间件”的实现了,依然使用php代码,只不过这些php代码是在服务器端用php命令执行的。具体实现发送邮件、短信的php代码就不写了,请自主实现,主要是这段代码里的读取redis里的任务数据代码段如下:
#利用死循环达到守护进程的效果 while(true) { #从redis---list结构体读出发送邮件或短信任务数据 $RedisModel = new Redis; $RedisModel->connect('127.0.0.1',6379); #这里不再进行连接是否完成的检测 请自主完成 # ...code.... #对redis进行list结构的数据逐出读取 $result = $RedisModel->rPop('LB');/*直接操作list链表从底部去除一条==redis会自动处理读出一条其就不会存在于该链表中了*/ if($result === false) { #倘若没有数据就意味着没有发送邮件、短信的任务 直接sleep休眠3秒 sleep(3); #本次循环结束 continue; } # 存在发送邮件或短信的任务 还原任务数据为数组 $MailData = json_decode($result,true); # 进行发送邮件或短信任务的代码 此处不再完成 # ...code..... }
主要的一点核心就是死循环并配合休眠,当然还设置php执行时间为永久,这就不写了,一个简单set_ini函数。
===
接下来就要让这个“简单中间件”的php代码在服务器端一刻也不停的运行。linux的nohup命令配合php命令即可。
nohup php -f file.php >/dev/null 2>&1 &
四、中间件存在的问题解决
用nohup执行的中间件php文件在死循环过程中会因内存问题宕掉---php本身内存回收的问题导致。就要服务器端有自主检测并重启的定时bash脚本进行配合。
基本思路就是服务器端利用bash脚本定时检测这个文件是否还在运行,没有运行了就自动重启;用到crontab任务,就不细说了,仅列个思路代码。
#!/bin/bash #author: JJonline.CN #auto check and reRun PHP cli Script sh #ps、grep、wc三个命令配合起来检测中间件php文件是否还在运行 #留意这里取巧仅检测了该php脚本的名称 也就是Qeue result=`ps -ef | grep "Qeue" | grep -v "grep" | wc -l` #这句代码是判断该cli脚本是否还运行的关键 if [ 0 == $result ];then #reRun PHP-Cli scrip now #这个非常重要 先进入到该cli脚本所在的目录 不然倘若Qeue.php文件中包含了其他php文件时 该命令会跑不起来 cd /Server/PHPCliRoot/ #再次执行nohup、php命令 nohup /usr/local/php/bin/php -f /Server/PHPCliRoot/Qeue.php >/dev/null 2>&1 & #写入重启日志 便于管理 echo $(date -d "today")":Try reStarting php-cli">>/Server/PHPCliRoot/RunAuto.log fi
假设该bash脚本保存为/Server/auto.sh,接下来就是设定定时任务cronteb,如下:
#检查是否存在crond守护进程(是否安装了定时任务)就不再本次示例范围了 #添加定时任务 crontab -e #写法如下 * * * * * /bin/bash /Server/auto.sh #保存退出
楼主这网站挺好。