最近学习了Web架构方面的知识,包括数据库读写分离、Redis缓存与队列、集群、负载均衡(LVS)。今天,我就来学习一下我的负载均衡过程中遇到的问题,就是session共享的问题。1.负载均衡负载均衡:将很多访问分散到其他服务器上,从而减轻各服务器的压力。通俗的解释是:将任务分配给开发者,处理能力总是有上限的。这时候可以考虑加入开发人员一起处理这个任务。当多人处理同一个任务时,就会涉及到调度问题。即任务分配,这与多线程的概念是一致的。nginx在这里的作用相当于任务分配者。比如我们第一次访问域名www.baidu.com时,可能对应到IP为111.13.101.208的服务器,第二次访问时,IP可能会变为111.13.101.209的服务器。这就是百度使用负载均衡的原因。一个域名对应多台服务器,流量分散到其他服务器上,大大减少了每台服务器上的流量。但是,这里有一个问题。如果我们登录一个百??度的账号,比如网页的百度网盘,但是每次有可能请求到不同的服务器,我们知道每个服务器都会有自己的session会话,所以会导致是用户每次刷新网页都要重新登录,体验很差。因此,针对以上问题,希望能够实现session的共享,从而在负载均衡上解决同一个域名不同服务器对应不同session的问题。2、redis介绍目前多服务器共享session,redis用的最多。关于Redis的基础知识可以看我之前的博文Redis开发与学习。简单梳理一下:redis是key-value存储系统,属于非关系型数据库特点:支持数据持久化,允许数据在内存中保存到磁盘(memcached:数据存在于内存中,如果服务重启,数据会丢失)支持5种数据类型:string、hash、list、set、zset两种文件格式(即数据持久化)(1)RDB(全量数据):多久/多久,数据在内存被刷到磁盘,以便下次读取文件时可以加载它。(2)AOF(incrementalrequest):类似于mysql的二进制日志,在日志中不断记录对数据库的变更语句。下次重启服务时,数据会根据二进制日志重写一次,加载到内存中。实现持久化数据存储(1)内存存储(2)磁盘存储(RDB)(3)日志文件(AOF)3.实现的核心思想首先要弄清楚session和cookie的区别。浏览器存储cookie。每次浏览器向服务器发送请求时,HTTP头都会自动添加你的cookie信息。服务器以用户的cookie为key,在存储中找到对应的值(session)。同一域名下网站的cookies是相同的。所以无论有多少台服务器,无论请求分配到哪台服务器,同一个用户的cookie都是不变的。也就是说,cookie对应的session也是唯一的。所以这里只需要保证多台业务服务器访问同一个redis服务器(或集群)即可。4.PHPsession会话配置改为Redis。我们可以看到PHP默认的session配置是以文件的形式保存在服务器的临时目录中的。我们需要Redis作为保存会话的驱动程序。所以这里需要修改配置文件。PHP的自定义session机制改为Redis。这里有三种修改方法:1、修改配置文件php.ini找到配置文件php.ini,修改为如下内容,保存重启服务session.save_handler=redissession.save_path="tcp://127.0.0.1:6379"2.代码中动态配置修改直接在代码中添加如下内容:ini_set("session.save_handler","redis");ini_set("session.save_path","tcp://127.0.0.1:6379");注意:如果在配置文件redis.conf中设置了连接密码requirepass,save_path需要写tcp://127.0.0.1:6379?auth=authpwd,否则保存时会报错会议。测试:'toefl','num'=>8);//连接redis$redis=newredis();$redis->connect('127.0.0.0.1',6379);//查看session_idecho'session_id:'.session_id().'
';//redis中存储的session(redis以session_id为key,以字符串形式存储)echo'redis_session:'。$redis->get('PHPREDIS_SESSION:'.session_id()).'
';//php获取会话值echo'php_session:'.json_encode($_SESSION['class']);3.自定义会话机制使用session_set_save_handle方法自定义会话机制。我们在网上找了一个封装得很好的类。我们可以直接使用这个类来实现我们的共享会话操作。null,//数据库连接句柄'host'=>null,'port'=>null,'lifeTime'=>null,'prefix'=>'PHPREDIS_SESSION:');/***constructor*@param$options设置信息数组*/publicfunction__construct($options=array()){if(!class_exists("redis",false)){die("必须安装redis扩展");}if(!isset($options['lifeTime'])||$options['lifeTime']<=0){$options['lifeTime']=ini_get('session.gc_maxlifetime');}$this->_options=array_merge($this->_options,$options);}/***开始使用驱动程序的会话*/publicfunctionbegin(){if($this->_options['host']===null||$this->_options['port']===null||$this->_options['lifeTime']===null){returnfalse;}//设置会话处理函数session_set_save_handler(array($this,'open'),array($this,'close'),array($this,'read'),array($this,'write'),数组($this,'destory'),数组($this,'gc'));}/***session自动启动或session_start()启动session后调用的第一个函数*类似构造函数的功能*@param$savePath默认保存路径*@param$sessionName默认参数名,PHPSESSID*/publicfunctionopen($savePath,$sessionName){if(is_resource($this->_options['handler']))返回真;//连接redis$redisHandle=newRedis();$redisHandle->connect($this->_options['host'],$this->_options['port']);如果(!$redisHandle){返回假;}$this->_options['handler']=$redisHandle;//$this->gc(null);返回真;}/***类似于析构函数,在write之后或者session_write_close()函数之后调用*/publicfunctionclose(){return$this->_options['handler']->关闭();}/***读取session信息*@param$sessionId通过这个Id唯一确定对应的session数据*@returnsession信息/空字符串*/publicfunctionread($sessionId){$sessionId=$this->_options['前缀'].$sessionId;返回$this->_options['handler']->get($sessionId);}/***写入或修改session数据*@param$sessionId写入数据的session对应的id*@param$sessionData写入的数据已经序列化*/publicfunctionwrite($sessionId,$sessionData){$sessionId=$this->_options['prefix'].$sessionId;返回$this->_options['handler']->setex($sessionId,$this->_options['lifeTime'],$sessionData);}/***主动销毁会话session*@param$sessionId要销毁的会话的唯一id*/publicfunctiondestroy($sessionId){$sessionId=$this->_options['prefix'].$sessionId;//$array=$this->print_stack_trace();//日志::write($array);返回$this->_options['handler']->delete($sessionId)>=1?真假;}/***清理画中的过期数据*@paramvalidityperiod*/publicfunctiongc($lifeTime){//获取所有sessionid,让过期的释放//$this->_options['handler']->钥匙(“*”);返回真;}//打印堆栈信息publicfunctionprint_stack_trace(){$array=debug_backtrace();//截取用户信息$var=$this->read(session_id());$s=strpos($var,"index_dk_user|");$e=strpos($var,"}authId|");$user=substr($var,$s+14,$e-13);$user=unserialize($user);//print_r($array);//信息完整unset($array[0]);if(!empty($user)){$traceInfo=$user['id'].'|'.$user['user_name'].'|'.$user['user_phone'].'|'.$user['presona_name'].'+++++++++++++++\n';}else{$traceInfo='++++++++++++++++\n';}$time=date("y-m-dH:i:m");foreach($arrayas$t){$traceInfo.='['.$时间。']'。$t['文件'].'('.$t['线']。')';$traceInfo.=$t['类'].$t['类型'].$t['函数'].'(';$traceInfo.=implode(',',$t['args']);$traceInfo.=")\n";}$traceInfo.='++++++++++++++++';返回$traceInfo;}}在你的项目入口调用上面的类:上面的方法相当于重写了将session写入文件的方法,将数据写入Redis初始化文件init.php"127.0.0.1",'port'=>"6379"));$handler->begin();//这个也是必须的,要开启session,必须在session_set_save_handler之后执行session_start();testtest.phpCorwien[isex]=>Hello)在Redis客户端使用命令检查我们的数据存在:27.0.0.1:6379>keys*1)"first_key"2)"mylist"3)"language"4)"mytest"5)"pragmmer"6)"good"7)"PHPREDIS_SESSION:29a111bcs120sv48ibmmjqdag4"8)"user:1"9)"counter:__rand_int__"10)"key:__rand_int__"11)"tutorial-list"12)"id:1"13)"name"127.0.0.1:6379>getPHPREDIS_SESSION:29a111bcs120sv48ibmmjqdag4"sex|s:7:\"Corwien\";isex|s:5:\"Hello\";"127.0.0.1:6379>可以看到我们的数据是保存在Redis端的,关键是:PHPREDIS_SESSION:29a111bcs120sv48ibmmjqdag4.相关文章通过redis-phpRedis分布式缓存实现session共享,如何实现多台服务器SESSION实时共享redis实现session共享,sentinelnginx+iis实现负载均衡session_set_save_handler的执行顺序机制我理解
