深入Memcache的Session数据的多服务器共享详解


Posted in PHP onJune 13, 2013

一相关介绍
1.memcache + memcache的多服务器数据共享的介绍
,请参见http://www.guigui8.com/index.php/archives/206.html
2.session机制:
session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。
当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识- 称为sessionid,如果已包含一个sessionid则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含sessionid,则为此客户端创建一个session并且生成一个与此session相关联的sessionid,sessionid的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个sessionid将被在本次响应中返回给客户端保存。

保存这个sessionid的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。

一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,PHPSESSID=ByOK3vjFD75aPnrF3K2HmdnV6QZcEbzWoWiBYEnLerj,它的名字就是PHPSESSID。

二动机
在实际web生产环境中,一个应用系统,往往将不同的业务应用分布到不同服务器上进行处理。
当跟踪当前在线用户信息时,如果是同一个主域名时,可以用全域cookie处理相关数据的共享问题;如果是在不同主域下,则可以通过观察者模式的中心话概念解决相应问题,通过这种概念延伸出的解决方案有很多,而今天我所要讲的,是前一种,通过memcache的多服务器数据共享技术来模拟session,以进行对当前在线用户数据的多服务器共享。
关于多服务器统一session信息,要求如下:
1.能够在memcached规定的几台服务器上,保存session信息(通过前面介绍的memcache的多服务器数据共享);
2.能够象zend定义的session_start()前,通过session_id($sessid)那样,自定义session_id的值。
3.能方便的在系统运行时,切换memcached存储的session信息和 用文件存储的session信息的操作。

三代码
实现方式很简单,通过memcache来模拟session机制,只是利用memcache将存储媒介换成共享服务器的内存,以达到多台分布式部署的服务器共享session信息的目的。而调用的接口,与zend提供的session操作函数相区别,所以可以方便的在memcache和文件的session信息操作建切换。
以下代码,已经过多次实际测试,能达到以上功能需求。先贴下面了:

/**
 *=---------------------------------------------------------------------------=
 *                         MemcacheSession.class.php
 *=---------------------------------------------------------------------------=
 * 
 * 实现基于Memcache存储的 Session 功能
 *  (模拟session机制,只是利用memcache将存储媒介换成共享服务器的内存)
 *
 * 缺点:暂时没有引入不同主域的session共享机制的实现策略。即只支持同主域下的实现。
 * 
 * Copyright(c) 2008 by guigui. All rights reserved.
 * @author guigui <evan_gui@163.com>
 * @version $Id: MemcacheSession.class.php, v 1.0 2008/12/22 $
 * @package systen
 * @link http://www.guigui8.com
 */ 
/**
 * class MemcacheSession
 * 
 * 1. 设置客户端的Cookie来保存SessionID
 * 2. 把用户的数据保存在服务器端,通过Cookie中的Session Id来确定一个数据是否是用户的
 */
class MemcacheSession
{
   // {{{ 类成员属性定义
   public  $memObject          = null;      //memcache操作对象句柄
   private $_sessId            = '';
   private $_sessKeyPrefix     = 'sess_';
   private $_sessExpireTime    = 86400;   
   private $_cookieDomain      = '.guigui8.com';   //全域cookie域名
   private $_cookieName        = '_PROJECT_MEMCACHE_SESS';
   private $_cookieExpireTime  = '';    
   private $_memServers        = array('192.168.0.3' => 11211, '192.168.0.4' => 11211);
   private $_sessContainer     = array();     //当前用户的session信息
   private static $_instance   = null;      //本类单例对象
   // }}}
 
   /**
    * 单例对象获取的静态方法。
 * (可以顺便提供memcache信息存储的服务器参数)
    *
    * @param string $host   - memcache数据存储的服务器ip
    * @param integer $port  - memcache数据存储的服务器端口号
    * @param bool $isInit   - 是否实例化对象的时候启动Session
    */
   public static function getInstance($host='', $port=11211, $isInit = true) {
     if (null === self::$_instance) {
      self::$_instance = new self($host, $port, $isInit);
     } 
     return self::$_instance;
   }
   /**
    * 构造函数
    *
    * @param bool $isInit - 是否实例化对象的时候启动Session
    */
   private function __construct($host='', $port=11211, $isInit = false){
       !empty($host) && $this->_memServers = array(trim($host) => $port);
       $isInit && $this->start();
   }
   /**
  *=-----------------------------------------------------------------------=
  *=-----------------------------------------------------------------------=
 *      Public Methods
  *=-----------------------------------------------------------------------=
  *=-----------------------------------------------------------------------=
  */
   /**
    * 启动Session操作
    *
    * @param int $expireTime - Session失效时间,缺省是0,当浏览器关闭的时候失效, 该值单位是秒
    */
   public function start($expireTime = 0){
       $_sessId = $_COOKIE[$this->_cookieName];
       if (!$_sessId){
           $this->_sessId = $this->_getId();
           $this->_cookieExpireTime = ($expireTime > 0) ? time() + $expireTime : 0;
           setcookie($this->_cookieName, $this->_sessId, $this->_cookieExpireTime, "/", $this->_cookieDomain);
           $this->_initMemcacheObj();
           $this->_sessContainer = array();
           $this->_saveSession();
       } else {
           $this->_sessId = $_sessId;
           $this->_sessContainer = $this->_getSession($_sessId);
       }        
   }
   /**
    * setSessId
    * 
    * 自定义的session id,通常没有必要经过cookie操作处理的(所以省略了cookie记录session_id)
    *
    * @param string $sess_id
    * @return boolean 
    */
   public function setSessId($sess_id){
       $_sessId = trim($sess_id);
       if (!$_sessId){
           return false;
       } else {
           $this->_sessId = $_sessId;
           $this->_sessContainer = $this->_getSession($_sessId);
       }        
   }
   /**
    * 判断某个Session变量是否注册
    *
    * @param string $varName - 
    * @return bool 存在返回true, 不存在返回false
    */
   public function isRegistered($varName){
       if (!isset($this->_sessContainer[$varName])){
           return false;
       }
       return true;
   }    
   /**
    * 注册一个Session变量
    *
    * @param string $varName - 需要注册成Session的变量名
    * @param mixed $varValue - 注册成Session变量的值
    * @return bool - 该变量名已经存在返回false, 注册成功返回true
    */
   public function set($varName, $varValue){
       $this->_sessContainer[$varName] = $varValue;
       $this->_saveSession();
       return true;
   }
   /**
    * 获取一个已注册的Session变量值
    *
    * @param string $varName - Session变量的名称
    * @return mixed - 不存在的变量返回false, 存在变量返回变量值
    */
   public function get($varName){
       if (!isset($this->_sessContainer[$varName])){
           return false;
       }
       return $this->_sessContainer[$varName];
   }    
   /**
    * 销毁一个已注册的Session变量
    *
    * @param string $varName - 需要销毁的Session变量名
    * @return bool 销毁成功返回true
    */
   public function delete($varName){
       unset($this->_sessContainer[$varName]);
       $this->_saveSession();
       return true;
   }
   /**
    * 销毁所有已经注册的Session变量
    *
    * @return 销毁成功返回true
    */
   public function destroy(){
       $this->_sessContainer = array();
       $this->_saveSession();
       return true;    
   }
 
   /**
    * 获取所有Session变量
    *
    * @return array - 返回所有已注册的Session变量值
    */
   public function getAll(){
       return $this->_sessContainer;
   }
   /**
    * 获取当前的Session ID
    *
    * @return string 获取的SessionID
    */
   public function getSid(){
       return $this->_sessId;
   }
   /**
    * 获取Memcache的服务器信息
    *
    * @return array Memcache配置数组信息
    */
   public function getMemServers(){
       return $this->_memServers;
   }
   /**
    * 设置Memcache的服务器信息
    *
    * @param string $host - Memcache服务器的IP
    * @param int $port - Memcache服务器的端口
    */
   public function setMemServers($arr){
       $this->_memServers = $arr;
   }    
   /**
    * 添加Memcache服务器
    *
    * @param string $host - Memcache服务器的IP
    * @param int $port - Memcache服务器的端口
    */
   public function addMemServer($host, $port){
       $this->_memServers[trim($host)] = trim($port);
       $this->memObject->addServer($host, $port);
   }   
   /**
    * 移除Memcache服务器(注意,这个只是移除配置,并不能实际从memcached的连接池移除)
    *
    * @param string $host - Memcache服务器的IP
    * @param int $port - Memcache服务器的端口
    */
   public function removeMemServer($host){
  unset($this->_memServers[trim($host)]);
   }   
   /**
  *=-----------------------------------------------------------------------=
  *=-----------------------------------------------------------------------=
 *      Private Methods
  *=-----------------------------------------------------------------------=
  *=-----------------------------------------------------------------------=
  */
   /**
    * 生成一个Session ID
    *
    * @return string 返回一个32位的Session ID
    */
   private function _getId(){
       return md5(uniqid(microtime()));
   }
   /**
    * 获取一个保存在Memcache的Session Key
    *
    * @param string $_sessId - 是否指定Session ID
    * @return string 获取到的Session Key
    */
   private function _getSessKey($_sessId = ''){
       $sessKey = ($_sessId == '') ? $this->_sessKeyPrefix.$this->_sessId : $this->_sessKeyPrefix.$_sessId;
       return $sessKey;
   }    
   /**
    * 检查保存Session数据的路径是否存在
    *
    * @return bool 成功返回true
    */
   private function _initMemcacheObj(){
       if (!class_exists('Memcache') || !function_exists('memcache_connect')){
           $this->_showMessage('Failed: Memcache extension not install, please from http://pecl.php.net download and install');
       }        
       if ($this->memObject && is_object($this->memObject)){
           return true;
       }
       $this->memObject = new Memcache;
       if (!empty($this->_memServers)) {
          foreach ($this->_memServers as $_host => $_port) {
            $this->memObject->addServer($_host, $_port);
        }
       }
       return true;
   }
   /**
    * 获取Session文件中的数据
    *
    * @param string $_sessId - 需要获取Session数据的SessionId
    * @return unknown
    */
   private function _getSession($_sessId = ''){
       $this->_initMemcacheObj();
       $sessKey = $this->_getSessKey($_sessId);
       $sessData = $this->memObject->get($sessKey);
       if (!is_array($sessData) || empty($sessData)){
         //this must be $_COOKIE['__SessHandler'] error!
         return array();
       }
       return $sessData;
   }
   /**
    * 把当前的Session数据保存到Memcache
    *
    * @param string $_sessId - Session ID
    * @return 成功返回true
    */
   private function _saveSession($_sessId = ''){
       $this->_initMemcacheObj();
       $sessKey = $this->_getSessKey($_sessId);
       if (empty($this->_sessContainer)){
           $ret = @$this->memObject->set($sessKey, $this->_sessContainer, false, $this->_sessExpireTime);
       }else{
           $ret = @$this->memObject->replace($sessKey, $this->_sessContainer, false, $this->_sessExpireTime);
       }
       if (!$ret){
           $this->_showMessage('Failed: Save sessiont data failed, please check memcache server');
       }
       return true;
   }
   /**
    * 显示提示信息
    *
    * @param string $strMessage - 需要显示的信息内容
    * @param bool $isFailed - 是否是失败信息, 缺省是true
    */
   private function _showMessage($strMessage, $isFailed = true){
     return;
       if ($isFailed){
           echo ($strMessage);
       }
       echo $strMessage;
   }   

四应用
1.本地session存储,与原始session操作方式一样,没有任何改变。如:
session_start();
$_SESSION['file_session_info']= '本地文件保存的session信息'; //本地文件保存的session

2.memcache共享服务器的session存储
$mem= MemcacheSession::getInstance('192.168.0.4', 11211);
$mem->addMemServer('192.168.0.4',11211);
$mem->addMemServer('192.168.0.5',11211);
//如果cookie功能不可用,则根据其他参数传递的唯一信息,设置映射为session_id
if(1) {
$sn= '838ece1033bf7c7468e873e79ba2a3ec';
$mem->setSessId($sn);
}
$mem->set('name','guigui');  //多台memcache服务器共享的session
$mem->set('addr','wuhan');  //多台memcache服务器共享的session
//$mem->destroy();

3.分别获取本地和memcache存储的session信息
$addr= $mem->get('addr');
$_MEM_SESSION= $mem->getAll();
echo"<hr>localhost file session:";
var_dump($_SESSION);
echo"<hr>memcache session:";
var_dump($_MEM_SESSION);
//$res= $mem->delete('name');

PHP 相关文章推荐
PHP利用COM对象访问SQLServer、Access
Oct 09 PHP
phpmyadmin导入(import)文件限制的解决办法
Dec 11 PHP
php下尝试使用GraphicsMagick的缩略图功能
Jan 01 PHP
php文件上传表单摘自drupal的代码
Feb 15 PHP
解析PHP缓存函数的使用说明
May 10 PHP
解析PHP实现多进程并行执行脚本
Jun 18 PHP
Thinkphp使用mongodb数据库实现多条件查询方法
Jun 26 PHP
jQuery+PHP发布的内容进行无刷新分页(Fckeditor)
Oct 22 PHP
PHP基于mssql扩展远程连接MSSQL的简单实现方法
Oct 08 PHP
PHP自动补全表单的两种方法
Mar 06 PHP
[原创]PHP正则匹配中英文、数字及下划线的方法【用户名验证】
Aug 01 PHP
PHP实现将几张照片拼接到一起的合成图片功能【便于整体打印输出】
Nov 14 PHP
探讨:如何使用PHP实现计算两个日期间隔的年、月、周、日数
Jun 13 #PHP
判断php数组是否为索引数组的实现方法
Jun 13 #PHP
深入解析yii权限分级式访问控制的实现(非RBAC法)
Jun 13 #PHP
PHP 基于Yii框架中使用smarty模板的方法详解
Jun 13 #PHP
关于PHP二进制流 逐bit的低位在前算法(详解)
Jun 13 #PHP
php读取二进制流(C语言结构体struct数据文件)的深入解析
Jun 13 #PHP
基于PHP Socket配置以及实例的详细介绍
Jun 13 #PHP
You might like
什么是调频(FM)、调幅(AM)、短波(SW)、长波(LW)
2021/03/01 无线电
php文字水印和php图片水印实现代码(二种加水印方法)
2013/12/25 PHP
PHP+Apache环境中如何隐藏Apache版本
2017/11/24 PHP
PHP程序员必须知道的两种日志实例分析
2020/05/14 PHP
jQuery实现切换页面布局使用介绍
2011/10/09 Javascript
Javascript弹出窗口的各种方法总结
2013/11/11 Javascript
js事件监听机制(事件捕获)总结
2014/08/08 Javascript
JavaScript弹出窗口方法汇总
2014/08/12 Javascript
JavaScript避免内存泄露及内存管理技巧
2014/09/05 Javascript
解决Jquery向页面append新元素之后事件的绑定问题
2015/03/16 Javascript
js实现适用于素材网站的黑色多级菜单导航条效果
2015/08/24 Javascript
Vue.js快速入门实例教程
2016/10/15 Javascript
详解堆的javascript实现方法
2016/11/29 Javascript
JS实现课堂随机点名和顺序点名
2017/03/09 Javascript
jquery.rotate.js实现可选抽奖次数和中奖内容的转盘抽奖代码
2017/08/23 jQuery
React 组件转 Vue 组件的命令写法
2018/02/28 Javascript
[33:15]2018DOTA2亚洲邀请赛3月30日 小组赛B组 VP VS Mineski
2018/03/31 DOTA
用Python将动态GIF图片倒放播放的方法
2016/11/02 Python
css3闪亮进度条效果实现思路及代码
2013/04/17 HTML / CSS
html5的自定义data-*属性与jquery的data()方法的使用
2014/07/02 HTML / CSS
美国内衣第一品牌:Hanes(恒适)
2016/07/29 全球购物
唤醒头发毛囊的秘密武器:Grow Gorgeous
2016/08/28 全球购物
意大利奢侈品购物网站:Deliberti
2019/10/08 全球购物
OSPF有什么优点?为什么OSPF比RIP收敛快?
2013/02/13 面试题
毕业生求职信的经典写法
2014/01/31 职场文书
辅导员评语
2014/05/04 职场文书
幼儿园中班区域活动总结
2014/07/09 职场文书
兽医医药专业求职信
2014/07/27 职场文书
学校施工安全责任书
2015/01/29 职场文书
汽车销售助理岗位职责
2015/04/14 职场文书
致我们终将逝去的青春观后感
2015/06/10 职场文书
聘任书范文大全
2015/09/21 职场文书
导游词之无锡古运河
2019/11/14 职场文书
python中24小时制转换为12小时制的方法
2021/06/18 Python
Redis RDB技术底层原理详解
2021/09/04 Redis
MySQL数据库Innodb 引擎实现mvcc锁
2022/05/06 MySQL