session 机制

1问题背景

php配置redis作为会话(session)存储机制示例:

session.save_handler = redis

session.save_path =“tcp://127.0.0.1:6379;tcp://192.168.0.2:6379″

 

以上两行的php配置功能是设置 php程序中 $_SESSION 全局变量内容存储到Redis中。

 

 

php 配置文件(php.ini),设置两个redis服务入口保存会话信息,当其中一个redis服务宕机,部分使用sessionweb请求无法正常返回(这里的部分是指部分使用了$_SESSION取数据或者写数据的请求,同时会打印一条 PHP Warning: session_write_close(): Failed to write session data (redis). …日志到php log)

 

注意:这里的两个redis地址完全是平等的,本质上它门不是redis服务,只是负责redis存取服务分发功能的一个代理服务, (其主要功能是根据用户提交的redis读写请求做主从分发和负载均衡,各个redis之间通过主从复制实现信息同步),在讲解本文内容时,为方便理解,将其看成实际服务讲解。

 

疑问:既然php.ini中配置了两个redis服务地址,为何其中一个服务宕机,部分请求无法正常工作(即php会话信息存储时,不能自动选择一个正常的服务?.

 

学习php底层session代码总结原因如下(详细代码可见附录)

php session初始化的时候会给每个php.ini中指定的地址分配一个权重weights(默认权重为1,如果不为1,会复杂些例如tcp://192.168.0.2:6379?weight=..)

php session 存储到redis中的 key值为:”PHPREDISSESSION:session_id()”, val serialize($_SESSION)

 

pos =substr(key,0,32)%sum(weights);

sum(weights)=count(hosts);(权重为1

因此权重为1pos=substr(key,0,32)%count(hosts)

pos 最终等于0或者 1即表示选择哪个redis服务,

通过以上分析可得出一个结论,php session选择redis服务的依据是session_id(依据PHPREDISSESSION:session_id的前32位),换句话说一个用户的session_id固定后,之后每次存取session信息的redis服务的地址也随之固定。

当然实际上由于每个redis服务的权重不等,可能选择的会更加复杂些(详细可见具体源码),但是选择哪个redis服务都是和session_id相关(如果session_id不变则选择对应的  redis地址始终不变即使该redis服务宕机)

 

 

总结一下: php.ini中配置多个redis地址作为session服务地址,当其中一个redis服务宕机,则部分用户web请求时使用$_SESSION存储信息时,不能如猜想会自动使用另一个正常的redis服务。

2 php session底层存储代码

php session 底层写session信息到 redis方法

代码中黄色部分以及注释是本人自己添加,方便大家理解

PS_WRITE_FUNC(redis)

{

        char *cmd, *response, *session;

        int cmd_len, response_len, session_len;

 

        redis_pool *pool = PS_GET_MOD_DATA();

    /* 选择redis分发服务 */

    redis_pool_member *rpm = redis_pool_get_sock(pool,key TSRMLS_CC);

        RedisSock *redis_sock = rpm?rpm->redis_sock:NULL;

        if(!rpm || !redis_sock){

                  return FAILURE;

         }

 

        /* send SET command */

        session = redis_session_key(rpm, key, strlen(key), &session_len);

        cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds",session, session_len, INI_INT("session.gc_maxlifetime"), val,vallen);

        efree(session);

        if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {

                  efree(cmd);

                  return FAILURE;

        }

        efree(cmd);

 

        /* read response */

        if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) ==NULL) {

                  return FAILURE;

        }

 

        if(response_len == 3 && strncmp(response, "+OK", 3) == 0) {

                  efree(response);

                  return SUCCESS;

        } else {

                  efree(response);

                  return FAILURE;

        }

}

 

 

PHPAPI redis_pool_member *

redis_pool_get_sock(redis_pool *pool, constchar *key TSRMLS_DC) {

 

/*

totalWeight 为多个redis服务的权重之后,默认每个redis服务权重为1

      key  示例:PHPREDISSESSION:ps6dtfnkdf926d2r1q7kj1sop1的字符串(PHPSESSION+session_id组合)

      由于pos只受 session_id影响,因此如果某天配置文件(php.ini)中的redis入库宕机了,

     由于session_idtotalWeight不变,其pos不变,实际写入session数据时会始终无法

     正常存储session信息,从而导致请求不正常返回。

   */

        unsigned int pos, i;

         memcpy(&pos, key,sizeof(pos));

         pos %=pool->totalWeight;

 

        redis_pool_member *rpm = pool->head;

 

        for(i = 0; i < pool->totalWeight;) {

                  if(pos >= i && pos < i + rpm->weight) {

                           int needs_auth = 0;

           if(rpm->auth && rpm->auth_len &&rpm->redis_sock->status != REDIS_SOCK_STATUS_CONNECTED) {

                   needs_auth = 1;

           }

/*

此函数如果连接失败则返回-1这里并不处理

导致实际写入操作时才能反映出服务不可用户错误

*/

           redis_sock_server_open(rpm->redis_sock,0 TSRMLS_CC);

           if(needs_auth) {

               redis_pool_member_auth(rpm TSRMLS_CC);

           }

           if(rpm->database >= 0) { /* default is -1 which leaves the choice toredis. */

               redis_pool_member_select(rpm TSRMLS_CC);

           }

 

                           return rpm;

                  }

                  i += rpm->weight;

       rpm = rpm->next;

        }

 

        return NULL;

}

3 网址汇总

http://www.tuicool.com/articles/yeeyume自定义会话处理机制

http://my.oschina.net/u/1466553/blog/332830redis

http://www.ueffort.com/php-yong-redis-cun-chu-session/

http://blog.csdn.net/ohmygirl/article/details/43152683php内核session实现机制

http://blog.snsgou.com/post-944.htmlsession数据什么时候被删除

http://www.laruence.com/2012/01/10/2469.htmlsession过期时间设置

http://blog.csdn.net/lijing198997/article/details/9378047http cookie

http://www.php.net/session_startphp session官网

https://github.com/phpredis/phpredis#closegithub redis学习扩展

 

3 自定义session存储机制

 

(对于最小圈中使用session的部分代码 可以选择直接使用session存储相应数据)

 

附件是经过测试 自定义的session服务php代码。原理是使用php5.3+ 提供的 SessionHandlerInterface 接口自定义了session的存储机制

 

其他方案:修改redis扩展库中 选择redis连接地址(技术上修改,下期出版)

         直接操作redis缓冲存现信息(业务上避免使用$_SESSION)

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值