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高级OOP技术演示
Aug 27 PHP
php使用百度ping服务代码实例
Jun 19 PHP
php中最简单的字符串匹配算法
Dec 16 PHP
smarty内置函数config_load用法实例
Jan 22 PHP
教你php如何实现验证码
Jan 20 PHP
Symfony2学习笔记之系统路由详解
Mar 17 PHP
Yii框架数据模型的验证规则rules()被执行的方法
Dec 02 PHP
利用ajax和PHP实现简单的流程管理
Mar 23 PHP
php 调用ffmpeg获取视频信息的简单实现
Apr 03 PHP
asp.net和php的区别点总结
Oct 10 PHP
使用 PHP Masked Package 屏蔽敏感数据的实现方法
Oct 15 PHP
PHP中SESSION过期设置
Mar 09 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
模板引擎Smarty深入浅出介绍
2006/12/06 PHP
php安全配置 如何配置使其更安全
2011/12/16 PHP
laravel 5.3中自定义加密服务的方案详解
2017/05/09 PHP
Yii2.0使用阿里云OSS的SDK上传图片、下载、删除图片示例
2017/09/20 PHP
PHP+Apache环境中如何隐藏Apache版本
2017/11/24 PHP
使用Json比用string返回数据更友好,也更面向对象一些
2011/09/13 Javascript
jQuery中的jQuery()方法用法分析
2014/12/27 Javascript
jQuery焦点图切换特效代码分享
2015/09/15 Javascript
Ext JS框架程序中阻止键盘触发回退或者刷新页面的代码分享
2016/06/07 Javascript
JavaScript控制浏览器全屏显示简单示例
2018/07/05 Javascript
解决layer弹出层自适应页面大小的问题
2019/09/16 Javascript
js 解析 JSON 数据简单示例
2020/04/21 Javascript
Element InputNumber计数器的使用方法
2020/07/27 Javascript
js实现贪吃蛇小游戏(加墙)
2020/07/31 Javascript
Antd的table组件表格的序号自增操作
2020/10/27 Javascript
react antd表格中渲染一张或多张图片的实例
2020/10/28 Javascript
新手该如何学python怎么学好python?
2008/10/07 Python
Django添加favicon.ico图标的示例代码
2018/08/07 Python
PyQt5实现QLineEdit添加clicked信号的方法
2019/06/25 Python
python字符串Intern机制详解
2019/07/01 Python
Numpy中对向量、矩阵的使用详解
2019/10/29 Python
Python实现序列化及csv文件读取
2020/01/19 Python
Anaconda配置pytorch-gpu虚拟环境的图文教程
2020/04/16 Python
python实现Oracle查询分组的方法示例
2020/04/30 Python
纯CSS3制作页面切换效果的实例代码
2019/05/30 HTML / CSS
甜品店的创业计划书范文
2014/01/02 职场文书
可口可乐广告词
2014/03/20 职场文书
给校长的建议书100字
2014/05/16 职场文书
2014教师教育实践活动对照检查材料思想汇报
2014/09/21 职场文书
小学趣味运动会加油稿
2014/09/25 职场文书
新学期红领巾广播稿
2014/10/04 职场文书
甲乙双方合作协议书
2014/10/13 职场文书
网络营销计划
2015/01/17 职场文书
中标通知书格式
2015/04/17 职场文书
学雷锋主题班会教案
2015/08/13 职场文书
oracle DGMGRL ORA-16603报错的解决方法(DG Broker)
2021/04/06 Oracle