1. 在数据库中设置一个标志
在数据库中设置一个标志可能就是P H P用户使用的de facto的标准方法:连接到一个数据库,然后在数据库中留下一些数据,将其留给其他的进程去处理。这个方法非常容易实现,而且在所有的系统中都是可利用的,但是它也有一个缺点。你可以说出缺点是什么吗?
这个缺点不是来自于数据库的填充器(插入用户信息的进程),而是来自于数据库的读取器(从数据库中取出所有用户信息的主进程)。为了实现一个很好的“聊天感觉”,需要尽量的减少延迟—而且这样也是一个非常好的反应时间。反应时间对于基于互联网的聊天来说是非常关键的,因为这恰好就是用户真正的感觉是自己加入到这个行动当中去了。当发送信息的速度变得越来越慢,用户很快就变得非常沮丧,马上就会退出。我们的测试显示多于一秒钟的延迟就非常地过分了。为了能够保证低于这个值,主进程从数据库读取信息的查询时间必须非常短。p h p C h a t缺省值是0 . 5秒(在一秒钟之内检查两次)。现在,只要有许多用户需要聊天系统来处理,那么数据库就非常的忙,这样就占用越来越多的资源。在每秒钟4 0~5 0次的查询条件下,我们的测试服务器花掉了三分之一的过程时间仅仅是用来执行数据库查询。就算是这能作为数据库系统的基准(它应该能够处理更多的查询),一些优化选择很显然也是必要的,况且这非最佳设置。
2. 创建文件锁
我们的下一个想法就是当处理内部过程信息交换的时候,如果数据库占用了太多的内存,一个文件系统可能还需要更加有效率的工作。但是很明显文件系统会在内存的竞争中失败。同样的,填充器并不是问题—文件锁的创建就会很轻松的解决这个问题。然而为了侦测文件锁是否已经被安装了,必须做很多关于c l e a r s t a t c a c h e()的调用,这样是为了正确的侦测文件锁是否已经被删掉了或者还依然存在。c l e a r s t a t c a c h e()对系统性能有如此强烈的效果以至于我们不需要再尽力去研究这一项了。它的聊天程序占用的系统资源只占使用数据库支持的方法所占用的四分之一。创建我们自己的标准。做一个脚本程序,使它能够高频率的存取数据库和文件系统。记录下运行结果,然后比较它们。当评估数据交换方法的时候,这通常是一个非常好的办法—不要相信理论上关于系统可以胜任的那些话。在实际生活中,许多事情都不一样了。
3. 使用信号量
当然,前述方法不佳的原因可以很容易找到。那么原因是什么呢?试着把它们找出来然后记录下来。试着找出关键点—当以后必须作出优化选择的时候它们也是非常关键的。“一条链子只和它最脆弱的连接点一样长,”同样地,软件也只和它内部最慢的循环一样快。这个寻找瓶颈的过程就叫做剖视,它是非常重要的。
当使用一个数据库时,瓶颈就是数据库:调用数据库、执行查询、取出结果和下一步要干什么(叫做内部操作)所耗费的时间和我们取得结果的时间相比是很长的。换句话说,我们现在用一个大型的为复杂数据的存储而设计的软件系统去交换简单的布尔值—如果说数据库的设计中还有没有涉及到的东西的话,那就是布尔值。毫无疑问它执行起来并不理想。它的瓶颈就是内部操作,设置和取消初始化所需的时间。
文件系统运行得不好是因为它不是为这个用途而设计的,也因为其他的一些限制: P H P没有包含理想的文件系统存取方法。决定一个文件存在的因素是需要不断的使缓冲区失效和重新分配缓冲区。这使得我们再次在琐碎的任务上使用了大量的内部操作。
那么为什么不使用完全不同的东西呢?我们肯定不是第一个处理内部过程信息交流的人。其他人肯定已经找到了好的解决方案。于是我们实现了另外一个可能的解决方案:信号量。信号量(S e m a p h o r e s)能够很完好地达到我们的要求:它们像信号一样工作。信号量是存储在共享内存中的计数器。你可以获得一个信号量并且增加它的计数器的值。你也可以释放这个信号量并且减少其计数器的值。另外,也有等待某个信号量空闲的可能性,这意味着计数器的值将重新回到零。然而这个选择也有缺点:信号量倾向于锁定资源,创建某种类型的有时序安排的机制,允许许多进程等待某个装置上可利用的时间,或者相似的东西。无论什么时候你等待一个信号量变得空闲,这个等待的进程就被设置为睡眠状态,不能去做其他的事情。如果一个主进程正在等待用户输入区域发送一个新信息,那么它将进入睡眠状态,不能够处理输入的网络信息流。