在负载均衡集群中如何处理PHP会话?

53

好的,我有一个非常罕见且独特的负载均衡PHP网站方案。不幸的是-它以前没有进行负载均衡。现在我们开始出现问题...

目前唯一的问题是PHP会话。自然而然,一开始没有人想到这个问题,所以PHP会话配置保持默认设置。因此,两个服务器都有自己的一小堆会话文件,下一个请求被投向另一个服务器的用户就很倒霉了,因为那里没有他在第一个服务器上创建的会话。

现在,我一直在阅读PHP手册,试图解决这种情况。我发现了一个好用的函数session_set_save_handler()。(巧合的是,在SO上的这个主题中)挺不错的。除了我得在网站的所有页面中调用此函数。未来页面的开发人员也必须记住始终调用它。感觉有点笨拙,更何况可能违反十几项最佳编码实践。如果我能够翻转某些全局配置选项,会话将全部神奇地存储在DB或内存缓存中等,那该多好啊。

有什么想法吗?


添加:为了澄清-我希望这是一个标准的情况并具有标准的解决方案。FYI-我有一个MySQL DB可用。肯定有一些现成的代码可以解决这个问题吧?当然,我可以编写自己的会话保存内容,并且Greg指出的auto_prepend选项似乎很有前途-但那感觉像重新发明轮子。:P

添加2:负载均衡是基于DNS的。我不确定它是如何工作的,但我想应该类似于这个添加3:好的,我看到一个解决方案是使用auto_prepend选项在每个脚本中插入对session_set_save_handler()的调用,然后编写自己的DB持久化程序,可能还要加上对memcached的调用以提高性能。很好。

还有没有某种方式可以避免自己编写所有这些代码呢?像一些著名且经过充分测试的PHP插件一样?

后来添加:最终我选择了这种方式:如何在PHP + MySQL中正确实现自定义会话持久化器?

此外,我只需在所有页面中手动包含会话处理程序即可。

10个回答

36
你可以设置PHP来处理数据库中的会话,这样所有的服务器都可以共享相同的会话信息,因为所有的服务器都使用同一个数据库。
这里有一个关于这方面的好教程。

6
这不是正确的方法。问题出在PHP的会话上,与数据库无关,我们正在谈论PHP。这个解决方案只是一个权宜之计。 - Daniel
12
我不同意。将 PHP 会话存储在数据库中是解决这个问题的完美方案。 - Matt Fletcher
3
这是一种不太理想的解决问题的方法。应该使用内存存储,比如redis、memcached或ramfs等。 - r3wt
2
@r3wt 在内存中吗?如果会话存储(如memcache等)由于某种原因必须重新启动,您是否愿意接受所有会话都将消失的事实? - sbrattla
2
你可以随时使用Redis。 - DOfficial
显示剩余3条评论

23

我们处理这个的方式是通过使用memcached。只需要类似以下更改php.ini文件:

session.save_handler = memcache
session.save_path = "tcp://path.to.memcached.server:11211"

我们使用 AWS ElastiCache,因此服务器路径是一个域名,但我相信对于本地的 Memcached 来说也是类似的。

这种方法不需要进行任何应用程序代码更改。


4
Memcached 对于存储会话来说是一把双刃剑。它快速、易于设置,但当内存用尽时,会销毁旧会话,因此永久会话存储将无法工作。在使用之前,应该认真考虑这种数据丢失是否对自己的应用程序可以接受。 - Ivan Hušnjak
1
更多信息请参见:http://php.net/manual/en/memcached.sessions.php 注意:大部分文档实际上在那里的注释中! - Darren Cook

10
您没有提到负载均衡使用的技术(软件、硬件等); 但无论如何,解决您问题的方法是在负载均衡器上使用“黏性会话(sticky sessions)”。简而言之,这意味着当来自“新”访问者的第一个请求进入时,他们将被分配到群集中的特定服务器:随后在其会话的生命周期内,所有未来的请求都将定向到该服务器。实际上,这意味着编写用于单个服务器的应用程序可以扩展到平衡环境,而不需要进行零/少量代码更改。
如果您使用的是硬件负载均衡器(例如Radware设备),则粘性会话将作为群集设置的一部分进行配置。硬件设备通常为您提供更细粒度的控制:例如分配新用户到哪个服务器(它们可以检查健康状态等,并选择最健康/最少利用的服务器),以及服务器失败并从群集中退出时发生的情况。硬件负载均衡器的缺点是成本高 - 但我认为它们是值得的。
至于软件负载均衡器,这取决于您正在使用什么。对于Apache,mod_proxy上有stickysession属性 - 并且有很多通过谷歌可以获得的文章,以使其与php会话一起使用(例如for example)。

编辑: 根据原问题后面的其他评论,听起来你的“负载均衡”是通过轮询DNS完成的,所以上述内容可能不适用。我将避免进一步发表评论并对轮询DNS进行攻击。


1
我没有选择这个解决方案,也无法更改它。 - Vilx-

4

最简单的方法是配置负载均衡器,始终将相同的会话发送到相同的服务器。

如果您仍然想使用session_set_save_handler,那么可以看一下auto_prepend


负载均衡有些通过DNS条目完成。主机名解析为两个不同的IP地址,浏览器随机选择其中一个。或者类似那样-我不太清楚具体情况。但据我所知,没有负载均衡器。 "auto_prepend"看起来很有前途。 - Vilx-
使用auto_prepend将session_set_save_handler函数插入到每个脚本之前。 - Galen
2
听起来你正在使用“轮询DNS” - http://en.wikipedia.org/wiki/Round_robin_DNS。我会说,越少谈论这种负载平衡的“解决方案”越好。 - Ian
或许吧,不过这不是我选择的,我也无法改变它。 - Vilx-

3
如果您有时间并且仍然想查看更多解决方案,请查看http://redis4you.com/articles.php?id=01
使用Redis可以实现容错。在我看来,这种做法可能比memcache的解决方案更好,因为它更加健壮。

3
如果您正在使用php会话,您可以与NFS共享/tmp目录,在这里我认为会话存储在整个集群中的所有服务器之间。这样您就不需要数据库了。
编辑:您还可以使用像memcachedb(持久且快速)这样的外部服务,并将会话信息存储在memcachedb索引中,并使用内容的哈希或甚至会话ID进行标识。

这是目前的“快速解决方法”,但管理员不喜欢那样做。关于安全和其他方面的问题,我不知道具体情况。 - Vilx-
嗯,那么您可以使用像memcachedb这样的外部服务,使用密钥的哈希作为会话索引。 memcachedb服务非常快(特别是写入),易于使用,您可以从PHP使用特殊和简化的一组memcached函数。 - Khriz
2
NFS不适用于大量请求...请记住,PHP在session_start()处加载会话,并在脚本执行结束时保存它(即使没有更改任何会话数据!),因此每个PHP请求将生成两个NFS请求。 - Ivan Hušnjak
我不会以那种方式管理会话,但对于没有访问其他(更好的选项)键值存储引擎的人来说,这可能是一个选择。但是,是不是一个很好的选择... :) - Khriz
值得一提的是,我们浪费了一周的时间来弄清楚为什么我们在AWS上托管的Magento设置,有两个后端和NFS共享缓存目录,速度极慢。原因就是NFS。永远不要这样做! - wkw
好的,这是8年前的答案,但很抱歉听到这个消息。 - Khriz

2
可能已经有点晚了,但是看一下这个: http://www.pureftpd.org/project/sharedance Sharedance是一个高性能的服务器,用于集中管理远程主机上的临时密钥/数据对,而不需要SQL数据库的开销和复杂性。它主要设计用于在一组Web服务器之间共享缓存和会话。通过简单的PHP API可以轻松访问Sharedance服务器,并且与PHP 4和PHP 5会话处理程序的期望兼容。

2
当我们遇到这种情况时,我们实现了一些代码,这些代码存在于一个公共头文件中。
基本上,对于每个页面,我们都会检查是否知道会话ID。如果我们不知道,我们会检查是否处于您描述的情况下,通过检查我们是否已将会话数据存储在数据库中。否则,我们就开始一个新的会话。
显然,这需要将所有相关数据复制到数据库中,但是如果您将会话数据封装在一个单独的类中,那么它可以正常工作。

2

你也可以尝试将memcache用作会话处理程序。


1

在负载均衡集群中处理php会话时,最好使用粘性会话。为此,请向维护负载均衡器的数据中心网络请求启用粘性会话。一旦启用,您就不需要担心php端的会话。


好的,没有粘性会话你也可以获得故障转移。 :) 然而,在原始问题中有一个不同的问题 - 负载均衡是“DNS轮询”类型。因此,粘性会话无论如何都不在考虑范围内。 :P - Vilx-
@Vilx- 我们在使用PHP时遇到了与负载均衡器的会话问题,我们解决了这个问题。我们所做的唯一一件事就是要求我们的数据中心为配置的域和轮询请求启用粘性会话。 - harigorana

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接