PHP+memcache实现消息队列案例分享


Posted in PHP onMay 21, 2014

memche消息队列的原理就是在key上做文章,用以做一个连续的数字加上前缀记录序列化以后消息或者日志。然后通过定时程序将内容落地到文件或者数据库。

php实现消息队列的用处比如在做发送邮件时发送大量邮件很费时间的问题,那么可以采取队列。
方便实现队列的轻量级队列服务器是:
starling支持memcache协议的轻量级持久化服务器
https://github.com/starling/starling
Beanstalkd轻量、高效,支持持久化,每秒可处理3000左右的队列
http://kr.github.com/beanstalkd/
php中也可以使用memcache/memcached来实现消息队列。

    <?php  
    /** 
    * Memcache 消息队列类 
    */  
    class QMC {  
    const PREFIX = 'ASDFASDFFWQKE';  
    /** 
    * 初始化mc 
    * @staticvar string $mc 
    * @return Memcache 
    */  
    static private function mc_init() {  
    static $mc = null;  
    if (is_null($mc)) {  
    $mc = new Memcache;  
    $mc->connect('127.0.0.1', 11211);  
    }  
    return $mc;  
    }  
    /** 
    * mc 计数器,增加计数并返回新的计数 
    * @param string $key   计数器 
    * @param int $offset   计数增量,可为负数.0为不改变计数 
    * @param int $time     时间 
    * @return int/false    失败是返回false,成功时返回更新计数器后的计数 
    */  
    static public function set_counter( $key, $offset, $time=0 ){  
    $mc = self::mc_init();  
    $val = $mc->get($key);  
    if( !is_numeric($val) || $val < 0 ){  
    $ret = $mc->set( $key, 0, $time );  
    if( !$ret ) return false;  
    $val = 0;  
    }  
    $offset = intval( $offset );  
    if( $offset > 0 ){  
    return $mc->increment( $key, $offset );  
    }elseif( $offset < 0 ){  
    return $mc->decrement( $key, -$offset );  
    }  
    return $val;  
    }  
    /** 
    * 写入队列 
    * @param string $key 
    * @param mixed $value 
    * @return bool 
    */  
    static public function input( $key, $value ){  
    $mc = self::mc_init();  
    $w_key = self::PREFIX.$key.'W';  
    $v_key = self::PREFIX.$key.self::set_counter($w_key, 1);  
    return $mc->set( $v_key, $value );  
    }  
    /** 
    * 读取队列里的数据 
    * @param string $key 
    * @param int $max  最多读取条数 
    * @return array 
    */  
    static public function output( $key, $max=100 ){  
    $out = array();  
    $mc = self::mc_init();  
    $r_key = self::PREFIX.$key.'R';  
    $w_key = self::PREFIX.$key.'W';  
    $r_p   = self::set_counter( $r_key, 0 );//读指针  
    $w_p   = self::set_counter( $w_key, 0 );//写指针  
    if( $r_p == 0 ) $r_p = 1;  
    while( $w_p >= $r_p ){  
    if( --$max < 0 ) break;  
    $v_key = self::PREFIX.$key.$r_p;  
    $r_p = self::set_counter( $r_key, 1 );  
    $out[] = $mc->get( $v_key );  
    $mc->delete($v_key);  
    }  
    return $out;  
    }  
    }  
    /** 
    使用方法: 
    QMC::input($key, $value );//写入队列 
    $list = QMC::output($key);//读取队列 
    */  
    ?> 

基于PHP共享内存实现的消息队列:

<?php  
/** 
* 使用共享内存的PHP循环内存队列实现 
* 支持多进程, 支持各种数据类型的存储 
* 注: 完成入队或出队操作,尽快使用unset(), 以释放临界区 
* 
* @author wangbinandi@gmail.com 
* @created 2009-12-23 
*/  
class ShmQueue  
{  
private $maxQSize = 0; // 队列最大长度  
private $front = 0; // 队头指针  
private $rear = 0;  // 队尾指针  
private $blockSize = 256;  // 块的大小(byte)  
private $memSize = 25600;  // 最大共享内存(byte)  
private $shmId = 0;  
private $filePtr = './shmq.ptr';  
private $semId = 0;  
public function __construct()  
{  
$shmkey = ftok(__FILE__, 't');  
$this->shmId = shmop_open($shmkey, "c", 0644, $this->memSize );  
$this->maxQSize = $this->memSize / $this->blockSize;  
// 申?一个信号量  
$this->semId = sem_get($shmkey, 1);  
sem_acquire($this->semId); // 申请进入临界区  
$this->init();  
}  
private function init()  
{  
if ( file_exists($this->filePtr) ){  
$contents = file_get_contents($this->filePtr);  
$data = explode( '|', $contents );  
if ( isset($data[0]) && isset($data[1])){  
$this->front = (int)$data[0];  
$this->rear  = (int)$data[1];  
}  
}  
}  
public function getLength()  
{  
return (($this->rear - $this->front + $this->memSize) % ($this->memSize) )/$this->blockSize;  
}  
public function enQueue( $value )  
{  
if ( $this->ptrInc($this->rear) == $this->front ){ // 队满  
return false;  
}  
$data = $this->encode($value);  
shmop_write($this->shmId, $data, $this->rear );  
$this->rear = $this->ptrInc($this->rear);  
return true;  
}  
public function deQueue()  
{  
if ( $this->front == $this->rear ){ // 队空  
return false;  
}  
$value = shmop_read($this->shmId, $this->front, $this->blockSize-1);  
$this->front = $this->ptrInc($this->front);  
return $this->decode($value);  
}  
private function ptrInc( $ptr )  
{  
return ($ptr + $this->blockSize) % ($this->memSize);  
}  
private function encode( $value )  
{  
$data = serialize($value) . "__eof";  
echo '';  
echo strlen($data);  
echo '';  
echo $this->blockSize -1;  
echo '';  
if ( strlen($data) > $this->blockSize -1 ){  
throw new Exception(strlen($data)." is overload block size!");  
}  
return $data;  
}  
private function decode( $value )  
{  
$data = explode("__eof", $value);  
return unserialize($data[0]);  
}  
public function __destruct()  
{  
$data = $this->front . '|' . $this->rear;  
file_put_contents($this->filePtr, $data);  
sem_release($this->semId); // 出临界区, 释放信号量  
}  
}  
/* 
// 进队操作 
$shmq = new ShmQueue(); 
$data = 'test data'; 
$shmq->enQueue($data); 
unset($shmq); 
// 出队操作 
$shmq = new ShmQueue(); 
$data = $shmq->deQueue(); 
unset($shmq); 
*/  
?>

对于一个很大的消息队列,频繁进行进行大数据库的序列化 和 反序列化,有太耗费。下面是我用PHP 实现的一个消息队列,只需要在尾部插入一个数据,就操作尾部,不用操作整个消息队列进行读取,与操作。但是,这个消息队列不是线程安全的,我只是尽量的避免了冲突的可能性。如果消息不是非常的密集,比如几秒钟才一个,还是可以考虑这样使用的。
如果你要实现线程安全的,一个建议是通过文件进行锁定,然后进行操作。下面是代码:
代码如下:

    class Memcache_Queue   
    {   
    private $memcache;   
    private $name;   
    private $prefix;   
    function __construct($maxSize, $name, $memcache, $prefix = "__memcache_queue__")   
    {   
    if ($memcache == null) {   
    throw new Exception("memcache object is null, new the object first.");   
    }   
    $this->memcache = $memcache;   
    $this->name = $name;   
    $this->prefix = $prefix;   
    $this->maxSize = $maxSize;   
    $this->front = 0;   
    $this->real = 0;   
    $this->size = 0;   
    }   
    function __get($name)   
    {   
    return $this->get($name);   
    }   
    function __set($name, $value)   
    {   
    $this->add($name, $value);   
    return $this;   
    }   
    function isEmpty()   
    {   
    return $this->size == 0;   
    }   
    function isFull()   
    {   
    return $this->size == $this->maxSize;   
    }   
    function enQueue($data)   
    {   
    if ($this->isFull()) {   
    throw new Exception("Queue is Full");   
    }   
    $this->increment("size");   
    $this->set($this->real, $data);   
    $this->set("real", ($this->real + 1) % $this->maxSize);   
    return $this;   
    }   
    function deQueue()   
    {   
    if ($this->isEmpty()) {   
    throw new Exception("Queue is Empty");   
    }   
    $this->decrement("size");   
    $this->delete($this->front);   
    $this->set("front", ($this->front + 1) % $this->maxSize);   
    return $this;   
    }   
    function getTop()   
    {   
    return $this->get($this->front);   
    }   
    function getAll()   
    {   
    return $this->getPage();   
    }   
    function getPage($offset = 0, $limit = 0)   
    {   
    if ($this->isEmpty() || $this->size < $offset) {   
    return null;   
    }   
    $keys[] = $this->getKeyByPos(($this->front + $offset) % $this->maxSize);   
    $num = 1;   
    for ($pos = ($this->front + $offset + 1) % $this->maxSize; $pos != $this->real; $pos = ($pos + 1) % $this->maxSize)   
    {   
    $keys[] = $this->getKeyByPos($pos);   
    $num++;   
    if ($limit > 0 && $limit == $num) {   
    break;   
    }   
    }   
    return array_values($this->memcache->get($keys));   
    }   
    function makeEmpty()   
    {   
    $keys = $this->getAllKeys();   
    foreach ($keys as $value) {   
    $this->delete($value);   
    }   
    $this->delete("real");   
    $this->delete("front");   
    $this->delete("size");   
    $this->delete("maxSize");   
    }   
    private function getAllKeys()   
    {   
    if ($this->isEmpty())   
    {   
    return array();   
    }   
    $keys[] = $this->getKeyByPos($this->front);   
    for ($pos = ($this->front + 1) % $this->maxSize; $pos != $this->real; $pos = ($pos + 1) % $this->maxSize)   
    {   
    $keys[] = $this->getKeyByPos($pos);   
    }   
    return $keys;   
    }   
    private function add($pos, $data)   
    {   
    $this->memcache->add($this->getKeyByPos($pos), $data);   
    return $this;   
    }   
    private function increment($pos)   
    {   
    return $this->memcache->increment($this->getKeyByPos($pos));   
    }   
    private function decrement($pos)   
    {   
    $this->memcache->decrement($this->getKeyByPos($pos));   
    }   
    private function set($pos, $data)   
    {   
    $this->memcache->set($this->getKeyByPos($pos), $data);   
    return $this;   
    }   
    private function get($pos)   
    {   
    return $this->memcache->get($this->getKeyByPos($pos));   
    }   
    private function delete($pos)   
    {   
    return $this->memcache->delete($this->getKeyByPos($pos));   
    }   
    private function getKeyByPos($pos)   
    {   
    return $this->prefix . $this->name . $pos;   
    }   
    }  
PHP 相关文章推荐
php 字符转义 注意事项
May 27 PHP
浏览器关闭后,能继续执行的php函数(ignore_user_abort)
Aug 01 PHP
PHP中判断变量为空的几种方法小结
Nov 12 PHP
使用CodeIgniter的类库做图片上传
Jun 12 PHP
ThinkPHP实现ajax仿官网搜索功能实例
Dec 02 PHP
PHP实现中文圆形印章特效
Jun 19 PHP
PHP的命令行命令使用指南
Aug 18 PHP
PHP7引入的&quot;??&quot;和&quot;?:&quot;的区别讲解
Apr 08 PHP
YII框架http缓存操作示例
Apr 29 PHP
PHP读取XML文件的方法实例总结【DOMDocument及simplexml方法】
Sep 10 PHP
Laravel框架下载,安装及路由操作图文详解
Dec 04 PHP
用php实现分页效果的示例代码
Dec 10 PHP
php+highchats生成动态统计图
May 21 #PHP
PhpDocumentor 2安装以及生成API文档的方法
May 21 #PHP
Smarty模板学习笔记之Smarty简介
May 20 #PHP
php实例分享之mysql数据备份
May 19 #PHP
dedecms函数分享之获取某一栏目所有子栏目
May 19 #PHP
ECMall支持SSL连接邮件服务器的配置方法详解
May 19 #PHP
PHP小技巧之JS和CSS优化工具Minify的使用方法
May 19 #PHP
You might like
深入解析php之apc
2013/05/15 PHP
php实现源代码加密的方法
2015/07/11 PHP
Jquery中国地图热点效果-鼠标经过弹出提示层信息的简单实例
2014/02/12 Javascript
PHP+jQuery实现随意拖动层并即时保存拖动位置
2015/04/30 Javascript
JS实现可点击展开与关闭的左侧广告代码
2015/09/02 Javascript
jQuery实现可用于博客的动态滑动菜单完整实例
2015/09/17 Javascript
JavaScript File API文件上传预览
2016/02/02 Javascript
html5+javascript实现简单上传的注意细节
2016/04/18 Javascript
BootStrap 智能表单实战系列(十)自动完成组件的支持
2016/06/13 Javascript
JS实现不用中间变量temp 实现两个变量值得交换方法
2018/02/04 Javascript
Node.js Event Loop各阶段讲解
2019/03/08 Javascript
Vue移动端实现图片上传及超过1M压缩上传
2019/12/23 Javascript
小程序实现上下切换位置
2020/11/16 Javascript
[44:51]2018DOTA2亚洲邀请赛 4.4 淘汰赛 VP vs Liquid 第二场
2018/04/05 DOTA
[01:20]2018DOTA2亚洲邀请赛总决赛战队LGD晋级之路
2018/04/07 DOTA
[51:00]Secret vs VGJ.S 2018国际邀请赛淘汰赛BO3 第一场 8.24
2018/08/25 DOTA
Python tempfile模块学习笔记(临时文件)
2014/05/25 Python
跟老齐学Python之让人欢喜让人忧的迭代
2014/10/02 Python
python通过索引遍历列表的方法
2015/05/04 Python
Python 的类、继承和多态详解
2017/07/16 Python
centos6.4下python3.6.1安装教程
2017/07/21 Python
Python有序查找算法之二分法实例分析
2017/12/11 Python
PyCharm代码提示忽略大小写设置方法
2018/10/28 Python
对python 多个分隔符split 的实例详解
2018/12/20 Python
浅谈Python中的全局锁(GIL)问题
2019/01/11 Python
使用Python控制摄像头拍照并发邮件
2019/04/23 Python
Python脚本如何在bilibili中查找弹幕发送者
2020/06/04 Python
大学生自我评价怎样写好
2013/10/23 职场文书
学生党员的自我评价范文
2014/03/01 职场文书
竞选大学学委演讲稿
2014/09/13 职场文书
2014年学校领导班子对照检查材料
2014/09/19 职场文书
毕业纪念册寄语大全
2015/02/26 职场文书
办公室岗位职责范本
2015/04/11 职场文书
幼儿园教师读书笔记
2015/06/29 职场文书
吧主申请感言怎么写
2015/08/03 职场文书
ECharts transform数据转换和dataZoom在项目中使用
2022/12/24 Javascript