PHP消息队列实现及应用详解【队列处理订单系统和配送系统】


Posted in PHP onMay 20, 2019

本文实例讲述了PHP消息队列实现及应用。分享给大家供大家参考,具体如下:

在互联网项目开发者经常会遇到『给用户群发短信』、『订单系统有大量的日志需要记录』或者在秒杀业务的时候服务器无法承受瞬间并发的压力。

这种情况下,我们怎么保证系统正常有效的运行呢?

这个时候,我们可以引入一个叫『消息队列』的概念来解决上面的需求。

消息队列的概念、原理和场景

在高并发的时候,程序往往无法做到及时的处理。我们引入一个中间的系统,来进行分流和减压。

所以从本质上讲:消息队列就是一个队列结构的中间件。也就是说,你把消息和内容放入这个容器之后就可以直接返回,不用等它后期处理的结果。另外会有一个程序,读取这些数据并按照顺序处理。

1、队列结构的中间件
2、消息放入后,不必立即处理
3、由订阅者/消费者按顺序处理

也就是说:当遇到一个比较大或者耗时比较长的环节的时候,而同时你的业务又不需要立即知道这个环节的结果,使用消息队列是好的选择。

核心结构如下面:

PHP消息队列实现及应用详解【队列处理订单系统和配送系统】

消息队列 适用场景

一、数据需要冗余的时候
比如订单系统中,后续需要进行数据的转换和记录。消息队列可以把这些数据持久化的存储在队列中,然后由订单后期处理程序进行处理,处理完成之后再把这条记录从队列中删除。

二、系统的解耦
消息队列解决了2套系统之间深度耦合的问题。
使用消息队列后,入队的系统和出队的系统没有直接的关系。
入队系统和出队系统,其中一个崩溃之后不会影响另外一个的正常运行。

三、流量削峰
就是秒杀和抢购的时候,会出现明显的流量剧增,对服务器的压力非常大。
实际项目开发中,配合缓存来使用消息队列,一种很好的方案。

四、异步通信
消息队列本身就实现了程序的异步操作,因此只要适合于异步的场景都可以使用消息队列

五、扩展性
比如订单系统,订单入队之后,后期或许还有财务系统处理,但是如果还要加一个配货系统。
只需要让这个配货系统 订阅这个 消息队列 即可。

六、排序保证
在有些场景下,数据的处理顺序是非常重要的,队列本身就可以做成单线程的单进单出的系统。
从而有效的保证数据按照顺序进行处理。

常见 队列实现 的优缺点

队列介质:

Mysql:可靠性高、易实现、速度慢
Redis:速度快,单条大消息包时效率低
消息系统:专业性强、可靠,学习成本高(比如:RabbtiMQ)

消息处理的触发机制:

死循环方式读取:易实现,故障时无法及时恢复;
定时任务:压力均分,有处理量上限。(最大的缺陷:定位任务时间的间隔和处理的数据需要精准把握,不能上一个任务还没有处理完成,下一个认为就已经启动了)
守护进程:类似于PHP-FPM和PHP-CGI,需要shell知识

解耦案列:队列处理 订单系统和配送系统

我们在前面了解过消息队列的使用场景

这里,我们要来处理其中一个场景:系统的解耦。

在电商项目中,当客户提交了一个订单之后,客户在个人中心可以看到订单处于配送中。
这个时候就要参与进来一个系统,叫做『配送系统』。如果我们在做架构的时候,把订单系统和配送系统设计在一起的话就会出现一些问题:订单系统的压力比较大,但是配送系统没有必要对这些压力做及时的反应;我们不需要订单系统出现故障之后导致配送系统故障。

所以我们需要把这2个系统分开,通过一个中间的队列表来实现这2个系统的沟通。

如下图架构:

 PHP消息队列实现及应用详解【队列处理订单系统和配送系统】

具体到我们的程序代码大致逻辑如下图:

 PHP消息队列实现及应用详解【队列处理订单系统和配送系统】

大致流程:order.php来接收用户订单,生成订单号并对订单进行处理(订单系统);在订单系统会把配送系统所需要的数据放入队列表中;我们的配送系统goods.php会有个定时脚本每分钟执行一次,处理队列表中的数据。

简单设计队列表order_queue:

CREATE TABLE `order_queue` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `order_id` int(11) unsigned NOT NULL COMMENT '订单ID(从订单系统来的)',
 `user_info` varchar(255) NOT NULL DEFAULT '' COMMENT '可以是用户手机号/用户id等(这里只是演示)',
 `created_at` datetime NOT NULL COMMENT '订单创建时间',
 `updated_at` datetime NOT NULL COMMENT '本记录最后处理完成时间',
 `status` tinyint(2) NOT NULL COMMENT '0未处理,1已处理,2处理中',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

mysql订单队列

前面我们已经分析清楚了逻辑,剩下的就是代码实现了。

注意:我这里只是演示代码,单纯为了展示实现过程。

1、接收订单,处理订单order.php

<?php
// 这个文件是用来接收用户的订单信息 并写入队列的一个文件
if(!empty($_GET['user_info'])){
  // 验证 过滤 接收的数据
  // todo...
  // 这里是应该首先是订单中心的处理流程
  // 因为订单系统是一套单独的系统 这里就不编写这个系统了
  // todo...
  $order_id = rand(100000,99999); // 正常的订单号从 订单系统来,我们这里只是演示
  // 把配送系统需要的订单数据存入队列表中
  $insert_data = array(
    'order_id'=>$order_id,
    'user_info'=>$_GET['user_info'],
    'created_at'=>date('Y-m-d H:i:s',time()),
    'status'=>0
  );
  // 把上面的数据 插入到order_queue表中
  // insert into order_queue
}

2、配送系统goods.php

<?php
// 这个文件主要是配送系统处理队列表中的订单并进行标记的文件
//分析:
//第一步:先把要处理的记录更新为『等待处理』
//第二步:选择刚刚标记为『等待处理』的记录,然后进行配送系统的处理
//第三步:把上面前面处理过的程序标记『已完成』
/////////////////////这里很重要,你一定要明白哦//////////////////////////////////////////////
//疑问:为什么不直接处理最后更新为『已完成』,多了先标记为『等待处理』?
//这是因为配送系统很可能不是及时完成的,它中间会有一段处理的时间,如果还在处理中有其他程序来进行读取和操作,就冲突了。
//这样设计其实也是一个锁的机制
//1、
$waiting = array('status'=>0);
$lock = array('status'=>2);
//把状态为0的记录标记为2,每次更新3条(具体每次几条看情况)
$sql = "update order_queue set status=2 where status=0 limit 3";
//2、
if(上面update成功){
  // 选择出要处理订单内容
  // select * from order_queue where status = 2;
  // 然后由配货系统进行处理
  // todo...
  //3、处理完成把订单状态更新为已完成
  $success = array(
    'status'=>1,
    'updated_at'=>date('Y-m-d H:i:s',time())
  );
}else{
  echo 'All Finished';
}

3、linux服务器 定时任务

写个shell脚本:goods.sh

#!/bin/bash
date "+%G-%m-%d %H:%M:%S"
cd /var/www/
php goods.php

这个脚本就是去执行orders.php这个程序的。

在linux服务器部署定时任务:

crontab -e

*/1 * * * * /var/www/goods.sh >> /var/www/goods_shell.log 2>$1

每分钟执行一次goods.sh文件,并记录日志到goods_shell.log文件(在对应目录新建该文件)

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
让PHP COOKIE立即生效,不用刷新就可以使用
Mar 09 PHP
PHP array操作10个小技巧分享
Jun 23 PHP
php设计模式 Factory(工厂模式)
Jun 26 PHP
php中实现简单的ACL 完结篇
Sep 07 PHP
php从右向左/从左向右截取字符串的实现方法
Nov 28 PHP
php中通过curl模拟登陆discuz论坛的实现代码
Feb 16 PHP
显示程序执行时间php函数代码
Aug 29 PHP
php实现以只读方式打开文件的方法
Mar 16 PHP
YII Framework框架教程之安全方案详解
Mar 14 PHP
Yii2 如何在modules中添加验证码的方法
Jun 19 PHP
PHP实现防止表单重复提交功能【基于token验证】
May 24 PHP
浅谈PHP5.6 与 PHP7.0 区别
Oct 09 PHP
PHP常量define和const的区别详解
May 18 #PHP
thinkphp5框架实现的自定义扩展类操作示例
May 16 #PHP
java解析json方法总结
May 16 #PHP
微信支付之JSAPI公众号支付详解
May 15 #PHP
php获取目录下所有文件及目录(多种方法)(推荐)
May 14 #PHP
yii2 开发api接口时优雅的处理全局异常的方法
May 14 #PHP
yii2的restful api路由实例详解
May 14 #PHP
You might like
PHP 程序员也要学会使用“异常”
2009/06/16 PHP
比较discuz和ecshop的截取字符串函数php版
2012/09/03 PHP
PHP不使用递归的无限级分类简单实例
2016/11/05 PHP
PHP生成随机数的方法总结
2018/03/01 PHP
jquery 防止表单重复提交代码
2010/01/21 Javascript
Jquery截取中文字符串的实现代码
2010/12/22 Javascript
jQuery学习总结之jQuery事件
2014/06/30 Javascript
CSS+JS实现点击文字弹出定时自动关闭DIV层菜单的方法
2015/05/12 Javascript
Centos7 中 Node.js安装简单方法
2016/11/02 Javascript
Javascript Function.prototype.bind详细分析
2016/12/29 Javascript
微信小程序 http请求的session管理
2017/06/07 Javascript
浅谈JS对html标签的属性的干预以及对CSS样式表属性的干预
2017/06/25 Javascript
使用Vue开发一个实时性时间转换指令
2018/01/17 Javascript
JS实现的杨辉三角【帕斯卡三角形】算法示例
2019/02/26 Javascript
python获取文件版本信息、公司名和产品名的方法
2014/10/05 Python
python多进程操作实例
2014/11/21 Python
Python实现的使用telnet登陆聊天室实例
2015/06/17 Python
在Django中创建第一个静态视图
2015/07/15 Python
Python实现全角半角字符互转的方法
2016/11/28 Python
python 实现删除文件或文件夹实例详解
2016/12/04 Python
django轻松使用富文本编辑器CKEditor的方法
2017/03/30 Python
基于Python中random.sample()的替代方案
2020/05/23 Python
深入了解python列表(LIST)
2020/06/08 Python
美国求婚钻戒网站:Super Jeweler
2016/08/27 全球购物
Merrell迈乐澳大利亚网站:购买户外登山鞋
2017/05/28 全球购物
跟单文员的岗位职责
2013/11/14 职场文书
大学生自助营养快餐店创业计划书
2014/01/13 职场文书
小学教师师德反思
2014/02/03 职场文书
党委书记个人检查对照材料思想汇报
2014/10/11 职场文书
湖南省召开党的群众路线教育实践活动总结大会报告
2014/10/21 职场文书
临时工聘用合同协议书
2014/10/29 职场文书
幼儿园大班教师个人工作总结
2015/02/05 职场文书
2015年公务员试用期工作总结
2015/05/28 职场文书
防溺水主题班会教案
2015/08/12 职场文书
django注册用邮箱发送验证码的实现
2021/04/18 Python
css 边框添加四个角的实现代码
2021/10/16 HTML / CSS