对 PHP SESSION 的深入理解(2)

本篇文章是以我之前一页文章 《对 PHP SESSION 的深入理解(一)》 为基础的,如果你没有阅读该篇文章,建议你回头看看。

在 《对 PHP SESSION 的深入理解(一)》 的最后,我们提到在应用 session 时可能会遇到的问题:

session 多服务器共享的问题,假如有多台 php 服务器进行负载均衡的时候,用户登录时访问的是第一台服务器,没准下一个页面访问的是第二台服务器,但是 session 数据是存储在第一台服务器上的,因此在访问下一个页面的时候由于没有 session 数据(第二台服务器上)导致用户必须重新登陆。

从前面的分析我们也知道,php 中 session 默认通过文件的方式实现,但是如果访问量大,可能产生的 SESSION 文件会比较多,从众多的文件中选择其中一个文件不是一件轻松的事情,而且每次都以打开文件、读取文件的方式,也会产生大量的 I/O 操作,严重影响服务器的性能。

在这篇文章中将讨论解决的方法。

问题一讨论:

针对第一个问题,我在这里引入 《session多服务器共享的方案梳理 》 中的例子:

假设这种情况:多台 PHP 服务器进行负载均衡的时候,比如有三台 php 服务器,为了实现负载均衡,那么三台服务器上面的php代码都是一样(拷贝一份)。如下图:

对 PHP SESSION 的深入理解

上面的图是nginx+fpm部署图。可以看到多台php服务器进行负载均衡。

根据之前对 session 原理的理解,session 的数据默认是保存在磁盘文件中,而且 session 是 php 引擎生成的,那么生成 session 数据文件都是存储在本地了(a,b,c各自的服务器磁盘上)。负载均衡的目的本来就是要为了平均分配请求,所以没有固定第一次访问和第二次访问是同一台服务器,实际上无法确定的。第一秒访问可能是a服务器,第二秒访问的可能是c服务器。

所以同一个登录会员,实际上就会出现:第一秒访问第一台 php 服务器,第二秒访问的是第三台服务器。登录的信息一般是保存在 session 中的。这样子登录保存的 session 数据就需要进行共享了。不然的话会出现,访问第一台服务器生成了一个session数据。第二秒负载请求到第三台服务器,结果获取不到刚才生成的session 数据,就会提示用户重新登陆以生成 session 数据。但是假如下一秒负载请求到第二台服务器呢?

因此,这里我们要谈论的问题其实是 session 数据的共享问题,即多台后端服务器(php,Java等处理服务器,并不是 nginx、apache等的WEB服务器)共用一份 session 数据的问题。

针对多服务器的 session 共享问题,大概有以下几种解决方案:

第一种方案:

通过复制或同步的方式使得 a、b、c 服务器上都具有相同的 session 数据。

这种方案是,使用一些文件同步工具 ( Linux 下的 rsync ),当 a 服务器中的 session 数据有更改的时候,就会把这些更改也同步到 b,c 服务器上去。通过复制的方式,最终a,b,c各个服务器上都拷贝了一份 session 数据。

这种方式的弊端是,速度慢。复制数据会出现延迟。比如第一秒访问是 a 服务器,修改了 session 数据,负载均衡,可能下一秒访问是 b 服务器,session 数据如果没有被复制到 b 服务器,则是读取不到 session 数据的,出现时间上的延迟。这种复制数据要消耗很多网络带宽的。在实际中业界用得比较少。机器的数量越多,复制数据的性能损耗越大。不具备高度扩展性。

复制 session 的方式,无论是网络带宽成本还是硬件开销上都很大的。

第二种方案:

把原来存储在服务器磁盘上的session数据存储到客户端的cookie中去。

这样子,就不需要涉及到数据共享了。当客户端请求的时候,原来生成在服务器的数据生成到浏览器的 cookie 中,根据 cookie 中的数据识别用户。php 由原来的”从本地(也就是服务器)磁盘上读取 session 数据”转变为”浏览器的 cookie 中读取数据”。

这样子,在多台 php 服务器负载均衡的情况下,即便第一秒请求是 a 服务器,第二秒请求是 b 服务器,都不需要管哪台服务器了。反正都是读取客户端上的cookie数据。

一般是把 session 数据按照自己定义的加密规则,加密后后存在 cookie 中。

数据保存在 cookie 中这种做法有好处,也有坏处

好处是服务器的压力减小了,因为session数据不存在服务器磁盘上。根本就不会出现 session 读取不到的问题。

带来的弊端是:

网络请求占用很多。每次请求时,客户端都要通过 cookie 发送 session 数据给服务器。

另外,浏览器对 cookie 的大小存在限制。每个浏览器限制是不同的。

所以第一种方案不适合高访问量的情况下,因为高访问量的情况下,每次请求浏览器都要发送session数据给服务器。一般一个cookie大小 2k 的样子。会占用很多带宽(服务器购买带宽是一个很大费用),成本增高。归纳为带宽性能,速度问题。

存储到 cookie 中去,第二方面是安全问题:把session数据放到客户端,一般session中存的都是重要性数据(帐号、昵称、用户id等),会存在安全问题。

了解到,淘宝以前用过这种方式,把 session 数据存储到 cookie 中,根据 cookie 来识别用户。

第三种方案:

用一种算法(简单理解为规则),什么机制下 session 是保存在哪台服务器下,那么读取的时候就按照这种规则去读取,就能定位到原来的服务器。叫做分发请求,分发到特定的服务器上去,我理解其原理是存session 和读 session 数据保证都在一台服务器操作,就不会需要涉及到共享,具体实现方式是通过约定一种分发机制来实现。

例如 Nginx 的 ip_hash, 每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器。

假设是同一个用户 user1,每次访问都路由到同一台服务器上,这样即便是在负载均衡的情况下,也能保证每次访问都能读取到 session,不需要做 session 数据共享了。

这种方式的弊端是:

1、如果这台机子挂掉了,那么后续的请求按照 session 的规则还是会分发到这台服务器上去,但是现在不可用了,就会导致归属到这台服务器上的所有用户登录失败。

2、本来负载均衡有一个目的就是:当其中一台机子不可用的时候,会自动分发到可用的机子上去(自动判断现在要请求的机子是否可用)。

第四种方案:

做一个中间层,专门来存储所有访问涉及到的 session 。也就是所有的 session 都存储在这里。
服务器端统一从这里读取 session 数据。

比如以下的模式:

对 PHP SESSION 的深入理解

对比于前面的几种方式,第四种方式才算是真正的实现了 session 数据的共享。

利用这种中间层的方式共享 session 数据,我们可以通过 NFS 文件共享的方式,多台 php 服务器共享保存session 文件的磁盘。

通过 nfs 的方式,各个 php 服务器操作 session 数据的时候,是读取本地磁盘目录,但实际上是一个共享网络文件。各个 php 服务器实际上操作的都是同一个目录的文件。

对 PHP SESSION 的深入理解

采用中间层的方式来共享 session 数据是不错的,但是正如前面问题二所说的,采用文件的方式来存储 session 数据确实不是一个好办法,我们在问题二的讨论中再说比较好的解决方案。

问题二的讨论:

由于使用文件的方式来存储 session 数据带来的大量 I/O 操作而使服务器的性能下降,我们必须找到解决方案:

方案一:数据库

保存在数据库中,这种方式的扩展性很强,可以随意增加WEB而不受影响。放在数据库里面安全方面好。

要使用数据库来存储 session 数据的话,其实就是我们自己按照 session 的机制来模拟实现 session 的存储,我们可以通过 session_set_save_handler() 函数来实现这个想法。具体为,把以前存储在文件中的session数据存储到数据库中去,那么这样做,其实就不用到 php 内置的session机制了(像 session_start() 之类的函数都不需要去用了)。

写程序要模拟的是,从数据库拿session数据,约定什么情况下数据过期了然后自动清理,这里是指删除数据库中的行。保存在文件中的时候,php 有垃圾回收机制会去自动清理过期的 session 文件。

放在数据库里面,访问量小没有问题。大流量网站这么做,只会拖慢速度。因为得查询数据库,造成数据库压力大。假如你的整个 web 程序所使用的数据库和存储 session 数据所使用的数据库是同一个的话,那数据库真的是苦不堪言。但是单独为 session 而分出一台服务器的话,好像又有点浪费资源的感觉。

有些做法跟这种思想是类似的:比如ecshop、phpcms是把session数据都存储在数据库中去。服务端就是从数据库中拿session的数据。

放到数据库存储后,就可以实现:多台web服务器统一操作数据库,因为数据都在数据库,web服务器都能从数据库进行读取,那么session数据就能实现共享。

存储在数据库的做法,在线人数决定了其瓶颈,主要问题是影响性能。在线人数,因为登录的session数据存储在数据库中,只要是登录的用户就会涉及到频繁操作数据库。

访问量大的话,一个用户访问了n多个页面,哪怕是刷新页面,都需要去数据库取session数据。数据库的承受压力,确实很恐怖。pv是多少,就要请求多少次数据库服务器。访问每个页面都会去数据库查询是否登录,或者添加数据进数据库的sessions表

而保存在文件中的时候,则交给了操作系统去控制。一个用户怎么刷新页面,查看其他页面,都只需要读取单个session文件(sess_74dd7807n2mfml49a1i12hkc45)。

方案二:缓存实现

可以将 session 数据保存在 memcached,Redis 之类内存数据库中,memcached 是基���内存存储数据的,性能很高,用户并发量很大的时候尤其合适。

主要是利用内存的数据读取速度是很快的,与磁盘读取的速度不是一个数量级的。

使用内存存储:方便统计在线人数,内存的速度比磁盘访问快、内存数据库系统能够控制内存中的过期数据自动失效(刚好符合 session 过期需要)。

存储在 redis 比较理想的选择,存储在数据库中方便存储统计在线人数,那么存储在 redis 中也实现了这个要求。

也可以存储在 memcache 中。但 redis 支持的数据类型多。所以用它好点。

总结:

1、针对负载均衡来说,使用中间层的方式是最为理想的

2、关于取缔使用文件存储 session 的问题,是使用数据库存储还是使用缓存存储是要根据 web 程序或者开发者自身的情况有关的,如果web程序的规模不是很大,出于经济成本考虑而使用虚拟主机或共享主机的话,由于不具备对服务器的完全控制权限,比如还要安个memcache之类的,修改php.ini之类的都需要自己拥有独立服务器才能操控的,推荐使用数据库来存储。相反,如果你的web程序规模不小,网站很活跃,而且能够拥有独立的服务器的话,使用缓存方式将会得到理想的效果。

3、在后续的文章中,我将实现使用数据库存储 session 数据和使用缓存存储 session 数据。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/f958e0b0d2b05a80533906fd83f9d570.html