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

  一相关介绍

  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 <[email protected]>

  * @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');