Perl语言再学习(9): FCGI进程的内存共享

FCGI进程模型

跨请求的进程生命周期

长寿的进程和残存的变量

因为FCGI进程独立且持续。 所以上一次处理请求时用到过的数据结构和一些变量没有被清空,仍然驻留在这个进程的当前使用的内存空间里。 其实也是挺显然的一件事情:既然进程没有结束,其中在全局数据区的变量自然会维持上一次执行过后的状态。

但是注意

package Model::Example;

my $result = 100;

sub get_result {
	my $a = 3;
	return $a * $result++;
}

在上边的例子中$result的值会被缓存,而$a的值则不会。 这样会导致每次执行get_result得到的结果都不一样。

内存膨胀问题

FCGI这种进程模型当然有其好处:

  1. 可以免掉每次fork进程/线程的系统开销。 一般的做法是维护一个进程/线程池而不是在接到请求之后再去创建进程/线程。
  2. 因为可以保存上一次操作的结果。甚至可以把一些进程直接当做Memcached用。 效率上可能会差一点,然而易用性和自由度上会有很大的提升。

至于坏处:

  1. 这些进程占用的内存会一点点的增大。 引用的库及相关数据结构占用的内存越多,这个现象就越明显。
  2. 因为各个进程的内存空间相互独立,20个相同的进程把一份变量缓存了20次造成很大浪费。

定期重启

比较普遍的解决方案一般是定期结束进程并且重启。 这个方案最简单,且没有什么额外的操作负担。 缺点是如果把使用了进程内变量Cache,那么定期重启会导致Cache的Compulsory Miss以及重新加载。 虽然一个进程的生命周期中只有一次Compulsory Miss,但是无谓的损失应该尽量避免。 而且这个方法并不能够解决过度使用进程Cache的问题。

COW与预加载

对于过度使用Cache的问题,既然一份数据被缓存了20份,那么能不能通过共享内存来减少浪费呢? 通过一些库(比如IPC::SharedMem)当然是选择之一,但是这种方法引入了额外的操作成本,且对于应用程序编写者而言不够透明。

什么是Copy-on-write(COW)?

简而言之就是:

参考资料:

Linux内存管理是基于COW的。所以也就是说:

所以对于上边问题的解决方案也就呼之欲出了:

  1. 首先创立一个父进程,在父进程完成所有想要Cache内容的预加载。
  2. 由这个父进程负责创建所有的子进程

实际上运行的效果而言,20个fcgi进程可以节省1G以上的内存空间。

缺陷:

总结