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创建动态图像
Oct 09 PHP
IIS6+PHP5+MySQL5+Zend Optimizer+phpMyAdmin安装配置图文教程 2009年
Jun 08 PHP
php MYSQL 数据备份类
Jun 19 PHP
微信支付开发教程(一)微信支付URL配置
May 28 PHP
PHP中mysql_field_type()函数用法
Nov 24 PHP
在Mac OS的PHP环境下安装配置MemCache的全过程解析
Feb 15 PHP
php实现在线通讯录功能(附源码)
May 13 PHP
php实现等比例不失真缩放上传图片的方法
Nov 14 PHP
PHP中的empty、isset、isnull的区别与使用实例
Mar 22 PHP
laravel validate 设置为中文的例子(验证提示为中文)
Sep 29 PHP
浅谈laravel框架与thinkPHP框架的区别
Oct 23 PHP
php 使用 __call实现重载功能示例
Nov 18 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实现模仿socket请求返回页面的方法
2014/11/04 PHP
Django 中 cookie的使用
2017/08/17 PHP
PHP中str_split()函数的用法讲解
2019/04/11 PHP
Js之软键盘实现(js源码)
2007/01/30 Javascript
用js实现下载远程文件并保存在本地的脚本
2008/05/06 Javascript
javascript正则表达式中参数g(全局)的作用
2010/11/11 Javascript
extjs中grid中嵌入动态combobox的应用
2011/01/01 Javascript
JavaScript高级程序设计 事件学习笔记
2011/09/10 Javascript
jquery post方式传递多个参数值后台以数组的方式进行接收
2013/01/11 Javascript
js单例模式的两种方案
2013/10/22 Javascript
jquery实现手机发送验证码的倒计时代码
2014/02/12 Javascript
如何防止回车(enter)键提交表单
2014/05/11 Javascript
javascript实现浏览器窗口传递参数的方法
2014/09/03 Javascript
js监听鼠标事件控制textarea输入字符串的个数
2014/09/29 Javascript
HTML+CSS+JS实现完美兼容各大浏览器的TABLE固定列
2015/04/26 Javascript
javascript自定义滚动条实现代码
2020/04/20 Javascript
使用jQuery制作Web页面遮罩层插件的实例教程
2016/05/26 Javascript
Angular2 路由问题修复详解
2017/03/01 Javascript
HTML5+jQuery实现搜索智能匹配功能
2017/03/24 jQuery
vue.js单页面应用实例的简单实现
2017/04/10 Javascript
vue使用swiper实现中间大两边小的轮播图效果
2019/11/24 Javascript
封装 axios+promise通用请求函数操作
2020/08/11 Javascript
微信小程序实现单个或多个倒计时功能
2020/11/01 Javascript
[41:56]Spirit vs Liquid Supermajor小组赛A组 BO3 第一场 6.2
2018/06/03 DOTA
python实现大文件分割与合并
2019/07/22 Python
修改 CentOS 6.x 上默认Python的方法
2019/09/06 Python
python GUI库图形界面开发之PyQt5美化窗体与控件(异形窗体)实例
2020/02/25 Python
爱游人:Travelliker
2017/09/05 全球购物
IdealFit官方网站:女性蛋白质、补充剂和运动服装
2019/03/24 全球购物
管理学专业个人求职信范文
2013/09/21 职场文书
2014年教师政治学习材料
2014/06/02 职场文书
重大事项社会稳定风险评估方案
2014/06/15 职场文书
中学生思想品德评语
2014/12/31 职场文书
义诊活动通知
2015/04/24 职场文书
OpenCV实现常见的四种图像几何变换
2022/04/01 Python
《乙女游戏世界对路人角色很不友好》OP主题曲无字幕动画MV公开
2022/04/05 日漫