题目:
如何实现多台服务器共用session?
回答:
方案将session保存到数据库,不同主机可以去读session数据库来判断是否登录在线
反问:
如果并发访问很大的情况下,如何保证session正常读取?
这是一个面试题,回答了使用数据库来保存session的时候,面试官不满意,我自己的思路是将这个题目思考为多个分站如何公用session,比如顶级域和多子域。所以为了同步方便,想到的是数据库存放,但是如何处理并发量很高的情况?(弃用db存放session换其他方案?)
回复内容: 题目:
如何实现多台服务器共用session?
回答:
方案将session保存到数据库,不同主机可以去读session数据库来判断是否登录在线
反问:
如果并发访问很大的情况下,如何保证session正常读取?
这是一个面试题,回答了使用数据库来保存session的时候,面试官不满意,我自己的思路是将这个题目思考为多个分站如何公用session,比如顶级域和多子域。所以为了同步方便,想到的是数据库存放,但是如何处理并发量很高的情况?(弃用db存放session换其他方案?)
你说存 mysql,然后面试官问并发很大怎么办,其实说明他不推荐存 mysql。这种情况你得回答:存 redis 或者 memcached。
如果我是面试官,我会再问,用户量很大,单台 redis 根本就放不下怎么办?这里也表明,把所有的 session 全量存放单台机器上是不可行的。有2种方式:方法一,服务器端不保存 session 了,将用户的 session(注意:不仅是 sessionid) 存在用户本地(用 cookie 或者 localdata),但是明显有几个严重的问题:安全性、http传输的数据量、本地存储的上限等。还有,这就不是 session 了,完全就是 cookie 的方案。
方法二,那就是服务器端分布式存储了(redis 集群、 memcached 集群),既然是分布式,那么就必须保证用户每次请求都得到达指定的服务器,因为他的 session 在那台指定分片上,标记的方式可以是在存在用户的 cookie 里面,用户请求时,服务器根据 cookie 里面的内容将请求 route 到指定的分片上。
如果你这样回答了,我再问,如果某个分片挂了怎么办,那这所有用户的 session 就丢了。这是就高可用了,对每个分片建多个复制集(从节点),主分片挂了,从节点就继续提供访问。
恩,分布式基本都是这个理。
用mysql存储用户会话信息
基于数据库实现会话:
登录用户:
online1(sessid,session,time,version); $_cookie['myid']内容为:用户id(作为sessid)+密码哈希(验证身份)
普通访客:
online2(sessid,session,time,version); $_cookie['myid']内容为:用户id(为0)+会话id(sessid)
sessid是用户id,访客则为普通访客则为online_id()生成的id.
session存储用户会话,内容是会话数组serialize序列化后的串.
time是记录的更新时间.
version是版本号,用于实现cas(check and set)乐观锁,保证session字段数据的一致性.
访客唯一id生成函数:
function online_id(){ $time = !empty($_server['request_time_float']) ? $_server['request_time_float'] : mt_rand(); $addr = !empty($_server['remote_addr']) ? $_server['remote_addr'] : mt_rand(); $port = !empty($_server['remote_port']) ? $_server['remote_port'] : mt_rand(); $ua = !empty($_server['http_user_agent']) ? $_server['http_user_agent'] : mt_rand(); return md5(uniqid(mt_rand(), true).$time.$addr.$port.$ua.mt_rand());}
表结构:
create table `online1` ( `sessid` int(10) unsigned not null default '0', `session` varchar(20000) not null default '', `time` int(10) unsigned not null default '0', `version` int(10) unsigned not null default '0', primary key (`sessid`), key (`time`), key (`version`)) engine=innodb default charset=utf8 collate=utf8_general_ci;create table `online2` ( `sessid` char(32) not null default '', `session` varchar(20000) not null default '', `time` int(10) unsigned not null default '0', `version` int(10) unsigned not null default '0', primary key (`sessid`), key (`time`), key (`version`)) engine=innodb default charset=utf8 collate=utf8_general_ci;
读写函数:
function cn_session_get($sessid) { crud($_cookie['myid'],online1,online2) + return unserialize($session); }function cn_session_set($sessid,$session) { serialize($session) + curd_cas_update }
函数调用:
$session = cn_session_get($sessid);print_r($session['cart']);$session['cart'] = array();cn_session_set($sessid,$session); 或者 register_shutdown_function(cn_session_set,$sessid,$session);
cnsessionset()时用版本号version进行冲突检测,保证session字段数据的一致性:
cn_session_get: select * from online where sessid=1;cn_session_set: update online set session=$session,version=last_version+1 where id=1 and version=last_version;
如果没有更新记录,则返回ajax操作失败的提示.
对比:php内置的会话机制使用的是排它锁(悲观锁).
sessionstart()开启的是一个对sessid会话文件的写保护锁,
其他页面操作同一个sessid会话文件的sessionstart()将会被阻塞,
直到请求完成或者用sessionwriteclose()显式关闭.
这样的session锁机制就避免了下面的情况:
a页面和b页面读取了相同的一份会话信息.
a页面修改并写入了会话变量a.
b页面随后也修改并写入了会话变量b.
这时b页面会覆盖了之前a页面写入的会话变量a.
注意:这种并发一般很少发生,所以基于数据库实现的会话机制使用乐观锁,也是合理的,并不会频繁遇到操作失败的情况.
会话应用场景:记录登录后的用户信息,购物车(读取/添加/删除),观看记录,验证码,csrf_token.
把购物车数据存以序列化或json串形式存到数据库字段中,也就丧失了sql查询功能.
如果是以数据库记录的形式来存储购物车,则方便进行sql查询分析.
既然,已经放弃使用sql,自然可以上nosql,比如memcached/redis,设计类似于上述mysql的方案.
@eechen 给出的是单节点或者同一主域名下的多节点之间使用自定义存储session的步骤,其实php本身就有使用外置存储保存的功能,可以看这一篇教程:http://www.sitepoint.com/saving-php-sessions-in-redis/ 。由于对于所有web站点来说,session的后台存储都是同一个redis,所以即使web站点被调度到任何一个节点,读取的session数据还是不变的。
不知道面试官问的是不是不同主域共享session,如果是这种情况是不能用这种方案的。
sessionid之类的客户端标志,做一致性哈希,保证每次访问到同一台机器,对n个客户端来说,祈祷分流作用,这样行不行.
要琢磨考官问的什么,然后给出正确答案,真挺难的
的确,redis,memcache是不二的选择,原因是他们是内存数据库,硬盘数据库的瓶颈就是硬盘io。两者之间我比较推荐redis,因为它支持的数据格式多,而且扩展强大,比如持久化。
用memcached。
session不是持久化存储,跟rdbs的差异还是挺大的,但跟memcached很像。
至于sessionid,可以用应用里的uid来代替,需要手动管理。
memcache 是最佳选择 用memcache实现session的功能。
redis可以存储啊。