登录 |
  • 注册
  • [转]PHP沉思录之六 drupal

    2009年06月28日 上午 34:34 | 作者:pangyt

    Drupal是一个基于PHP的开源CMS系统,也是我认为技术上实现得最好的一个PHP应用。Drupal的架构非常优秀,通过微内核+plugin的方式,实现了极佳的扩展性,从而使Drupal远远超出一般的CMS这一范畴。从这个意义上来说,把Drupal称为Web OS似乎更加合适一些。关于Drupal,有太多的话可以说,也许我会在以后的时间里写一篇文章对它进行专门的讨论。但是在本文中,我想讨论的,是 Drupal社区中的每一个人都会面对,但不是每一个人都对其有清晰认识的问题,即Drupal的性能问题。

    因为客户需求,我曾经对Drupal做过比较全面的测试。当时的环境是双服务器(DB server+Web Server),硬件配置都是单CPU+4G。数据库里面有几千条Node记录。用JMeter对各种情况下(开/关各种cache模块,logged user/anonymous user)不同页面的读取和写入操作都进行过测试。

    测试的结果可能和很多人印象中不一样。两个主要的结果如下:

    1. Logged user和anonymous user的性能差距非常大。同一个页面,logged user的RPS(Requests per second)一般不超过20,而启用了cache的anonymous user的RPS在100多,当使用了file-based cache以后,甚至能超过300。

    2. 数据库压力相对较小。由于Drupal把大量可配置的内容都放在数据库中,因此往往容易产生这样一种印象,即Drupal对数据库要求应该是很高的。但事实上,无论是cache还是非cache模式,DB server的压力都相当小(CPU在10%以下),而Web Server的CPU在80%以上。跟踪所有的db query的执行时间后,也证明了这一点(全部db query的执行时间只占页面生成时间的一小部分)。

    经过反复的测试和思考,我得出了一些结论。很明显,Drupal在大量logged user并发情况下的瓶颈,在于执行Drupal代码的CPU时间,而不是在于数据库或者其他地方。之所以出现这样的情况,和PHP本身的执行机制和 Drupal的实现方式有关。Drupal在生成一个非cached的页面时,不管这个页面多么简单,都要执行一个完整的bootstrap过程,即使只启用了最少的模块,这个过程也要调用几十个PHP文件,执行成千上万行PHP代码。而PHP的机制又决定了没有任何PHP代码或者对象能够驻留内存,每次响应请求都必须执行完整的初始化工作。而anonymous user之所以快,是因为Drupal在执行cached page的时候,不会执行完整的bootstrap过程,它先检查是否cached page,是的话就读取缓存,然后结束工作。这样当然就快了。
    以这个结论为前提,可以解释一些事情:

    1. 为什么Drupal的性能在各种环境下相差并不多。无论是双服务器,单服务器,甚至内存非常小的虚拟机,logged user的RPS值往往总是在10~20之间。数据库里面有几百条,或者几十万条记录,影响也不大。因为瓶颈并不在于DB或者内存,而是在于执行代码的过程。

    2. 为什么使用APC/XCache这样的代码优化程序,能够得到极大的性能提升。在我自己的虚拟机环境上,RPS从3~4提升到了12。因为它提升的是PHP代码的执行时间。

    从这个结论出发,列出一些对优化Drupal的logged user性能有明显作用和没有明显作用的措施:

    I. 没有明显作用的:
    1. 加内存。在并发数只有10+的时候,即使每个请求占20M内存,也只有200M+内存而已。
    2. DB server和Web server分开,或者增强DB server的配置。一台中等性能的mysql服务器,应付200~300的并发是很轻松的事情,在并发数只有10+的时候,db server实际上是很空闲的。
    3. 基础软件的优化,例如从Windows转移到Linux,从apache转移到Lighttpd,从MySQL迁移到其他数据库,除了从Windows转移到 Linux会有比较明显的提升以外(因为PHP在Linux上的效率比在Windows上要好),其它的措施可能会快一些,但不会有大幅度的提高,因为瓶颈不在那里。

    II. 有明显作用的:

    1. 使用APC/XCache这样的代码优化程序,速度会有几倍的提升。估计大家都已经这样做过了。
    2. 增加web server的CPU数量。双核的肯定比单核的快,4个CPU肯定比2个CPU快得多。
    3. 使用多web server+单db server的配置,把代码执行的压力分散到不同的web server上。上文说到,单台db server可以轻松应付200+的并发,这意味着理论上可以支持10台以上的web server。
    4. 使用Quercus这样的引擎,把PHP代码编译成Java,再在Java VM中运行,理论上会有很大的提高。原因是,第一,Java的运行效率比PHP高,第二,Java代码是可以cache的,不需要每次都重新加载。这里有个测试结果:http://www.workhabit.org/resin-backed-php-drives-4x-performance- improvements-drupal 。Drupal在Quercus下有4倍的性能提高,但是这个数字跟Drupal在打开APC/eAccelerator下的提升差不多,所以可能没有太大的实用价值。

    另外一种思路是代码本身的优化。
    使用cache API基本上是没有意义的,因为对于logger user,Drupal不会调用cache API。Drupal.org上有人提出,即使是logged user,有很多页面也是不用定制化的,这意味着可以cache它们。但是Drupal没有提供这样一种机制。只要是logged user,Drupal就会执行完整的bootstrap过程,即使只打印出一个hello world,因此实际上你没有办法在logged user状态下cache单个页面。

    到目前最新版本的Drupal(Drupal 6.4)为止,对于logger user,Drupal只提供了一种cache功能,就是可以将部分block设置为可cache的。在block占用大量服务器时间的情况下,block cache能够有效地提高效率。但是,由于block cache对于bootstrap过程并无影响,因此当瓶颈在于bootstrap本身时,Block cache是无能为力的。
    在Drupal.org的社区,关于logger user的cache问题,一直处于热烈的讨论之中。基本的结论是,由于Drupal的架构就是这样,目前没有很好的解决方案,只能期待Drupal在以后的版本中进行改进了。

    我研究了一下Drupal的bootstrap过程,发现也许这样是可行的:实现hook_boot函数,这是bootstrap中执行最靠前的一个函数,它被调用时,bootstrap的大部分过程还没有执行。在hook_boot中,检查当前页面是否需要cache,如果是,直接读 cache生成页面,然后调用exit()强行结束。这在理论上是可行的,但太过hack了一点。
    Drupal的情况是这样,那么其他的PHP框架,尤其是半官方的Zend Framework,性能如何呢?通过搜索,我在网上找到了一份PHP framework comparison benchmarks,网址见:http://www.avnetlabs.com/php/php-framework-comparison- benchmarks。根据这份报告的数据,Zend Framework的性能只有原生PHP的10%,如果没有用APC,连3%都不到。当然,这份报告的数据不一定详尽,Zend Framework在不同环境下的表现应该也会有出入。但是,Zend Framework的性能大幅度落后于Baseline PHP,应该是确凿无疑的。

    为什么PHP主流框架的性能都存在着这样的问题呢?其实这也不难理解。回顾PHP沉思录系列第一篇中对于PHP工作模型的讨论,由于PHP没有驻留内存的进程,所以每一个request发生时,都必须初始化所有的对象,这导致大量的时间被耗费在进程代码的执行过程中。当PHP程序仅仅是简单的脚本时,这无关紧要,但是在结构复杂的架构中,由于每次处理request都要重复调用成千上万行代码,这一问题就变得非常突出了。而且,除非PHP以后的版本对这一机制进行改进,否则这个问题无法得到彻底的解决。

    那么,这是否意味着,PHP只能适用于小型的网站,而无法在高并发量的大型网站施展拳脚呢?当然不是这样。事实上,在Yahoo和其他许多知名的巨型网站上,都大量地使用了PHP。原因在于,PHP仅仅被用作一个内容生成器,生成的内容会被转化为静态文本,绝大多数用户浏览的都是被cache的静态文本。这就和PHP程序的性能毫无关系了。但是,当用户并不是仅仅进行浏览,而是需要频繁地和网站进行互动时,PHP的性能不但无法比拟C和Java,甚至无法与同为脚本语言的Python和Ruby相比。也就是说,PHP更适合于新闻门户这样的内容发布站点,而不是web 2.0应用的首选。

    在本系列文章告一段落的时候,我们看到的是PHP的局限性。热爱PHP的人们可能会对此觉得沮丧。但是,这并无损于PHP作为一门优秀语言的声誉。尺有所短,寸有所长,对于我们熟悉和喜爱的工具,我们更应该了解它们的局限,这也有利于我们更有效地使用它们。

    [转]PHP沉思录之三

    2009年06月28日 上午 32:33 | 作者:pangyt

    左轻候:
    在任何Web应用中,如何将程序代码和界面设计,或者说,将逻辑层和表现层分离开来,都会是一个问题。对于PHP这种类型的嵌入网页的脚本语言,这一问题尤其突出。在新手编写的代码中,把访问数据库的代码和操纵HTML元素的代码写在同一个页面里,是很常见的情况。为了避免这一问题,开发者倾向于将涉及业务逻辑的代码封装在某些单独的库文件中,再在负责显示界面的文件中将它们include进来。但是,这仍然无法避免在显示界面的文件中包含大量的PHP代码。究其所以然,是因为除了涉及业务逻辑的代码以外,即使仅仅在显示层,也往往涉及到复杂的显示逻辑。在一个典型的显示页面中,程序需要先包含所有必需的库文件,初始化上下文环境,创建相关业务逻辑对象(假如数据库访问代码已经被业务逻辑对象封装,可以节省数据库相关的代码),最后在HTML的空隙中把对象格式化为HTML元素进行显示。于是我们看到了无数这样的页面,在第一行HTML开始之前,就已经包含了数十行甚至更多的PHP代码,在HTML的内部,仍然充满了各种各样的PHP代码。因此,PHP代码和HTML代码搅和在一块的问题仍然无法解决,对HTML的修改仍然可能导致整个PHP程序崩溃。更加麻烦的是,这种不清晰的结构妨碍了PHP应用在规模上的进一步扩张。

    为了解决这一问题,模板(template)技术应运而生。模板技术的基本原理是,通过一个解析器(parser),读取指定的模板文件(包含了某些特定标签的HTML文件),将这些标签替换为相关的PHP变量,再输出为标准的HTML。通过这种方式,不但分离了业务逻辑层和表现层,而且也尽可能地分离了显示逻辑和HTML代码。通过替换不同的模板文件,可以方便地生成各种格式的输出,例如HTML,XML,WML,后期稍加处理甚至可以生成PDF和 Flash。早期较为著名的PHP模板引擎有PHPLib中的Template和FastTemplate。

    但是,模板技术也有其先天的缺陷。
      
    无法彻底分离逻辑。显示逻辑和HTML代码很难通过简单的标签替换,实现彻底的分离。例如,遍历并显示一个数组,在PHP中可以用简单的foreach语句实现,但是使用模板时,就需要进行对整个模板文件进行多次替换操作,造成效率的极大降低;或者根据不同的数据值显示不同的格式,如果模板文件完全不包含 PHP代码,那么将很难做到这一点。

    解析导致的性能损失。由于每次PHP页面被访问时,解析器都必须对模板文件进行替换操作,无疑会降低PHP应用的性能。尤其在多次的替换操作时更是如此。因此,不使用模板比使用模板往往更加快速,这也是许多PHP程序员摒弃模板技术的原因之一。
      
    在经过数年的发展之后,“编译型”的模板技术渐渐占据了主流。所谓“编译型”,是指解析器读取模板文件以后,并不直接生成静态HTML,而是 “编译”成一个新的PHP文件,并将它保存起来。以后访问该页面时,模板引擎会直接执行“编译”后的PHP文件。Smarty是这种模板引擎的代表。

    针对以上的两个问题,Smarty作了如下处理:
      
    独立语法。Smarty实现了一套自己的语法,这套语法不但支持变量替换和简单的判断,而且支持循环,修饰符(modifier),内置了很多功能强大的函数,而且还支持自定义函数。这套系统保证Smarty能够完全独立地处理显示输出,无须再和PHP有什么瓜葛。事实上,在Smarty模板中,是不可以直接使用PHP代码的(通过显式定义可以使用),这也是一种强制分离逻辑层和表现层的方式。(理论上来说,Smarty的模板文件也可以应用于其它语言。)但是,这种解决方式也受到了指责,因为Smarty的语法过于强大,几乎变成了一门新的语言,指责者认为,这反而增加了复杂性。但是,根据作者的实际经验,Smarty的语法不但非常简单直观,而且只需要掌握一些最初级的语法,就足可以应付绝大多数的应用。即使是不懂编程的网页设计师,也很容易就能够掌握。

    编译机制。Smarty的“编译”机制,节省了用于反复解析模板文件的时间,极大地提高了速度。由于“编译”后生成的是标准的PHP文件,因此从理论上来说,执行的速度不会低于没有模板的PHP应用的速度。在一些和解析型模板引擎进行的对比测试中,Smarty在第一次访问时落后,但是在以后的访问中速度远远超出竞争对手。而且,这种编译过程是智能的,在模板文件的内容被改变后,Smarty会自动重新编译,因此对于开发者来说,编译过程完全无需人工干预。另外,如果你愿意的话,生成的PHP文件还可以方便地应用于Zend Accelerator这样的工具,进行二次编译。
      
    除此之外,Smarty还拥有其他一些优秀的特性:
      
    缓存机制。由于实现了编译机制,在接收到对某个模板文件的访问请求时,Smarty会自动将它重定向到编译后的PHP文件。但是,这也意味着,Smarty也可以将它重定向到任何其他的文件——例如静态的HTML文件。在此基础之上,Smarty实现了自己的基于页面的缓存机制。 Smarty能够将编译后的PHP文件产生的结果——静态HTML——保存起来,将重复发送的请求直接重定向给它,这意味着对于第一次之后的请求,不需要执行任何PHP代码(Smarty本身的代码当然除外)。对于不需要频繁更新的页面(我们知道这种网页往往在整个网站中占大多数),通过这种缓存机制获取的性能提升是惊人的。而且,由于它是在页面级实现的,因此完全无须涉及到复杂的对象级缓存问题,保持了逻辑上的简单性。

    可配置性。Smarty在开发之初就将高度的可配置性作为自己的一个设计目标。它本身以100%的PHP编写,以源代码的方式发行,只需要将Smarty 简单地拷贝到你的文件路径中,就可以使用了。Smarty的各项配置变量,都可以通过修改config文件或者手动编码进行定制。例如,Smarty默认的定界符是花括号({}),但是这往往和Javascript以及CSS中的花括号冲突。为了解决这一问题,可以简单地将默认定界符修改为其他的字符(例如ASP风格的“<%”和“%>”)。

    可扩展性。Smart的实现基于面向对象的架构,并且提供了插件机制,非常便于用户修改和扩展其默认的认为。当然,你也可以直接修改它的源代码来达到目的。(对于基于脚本语言的开源应用来说,这是非常惬意的,因为你甚至不需要重新编译。)
      
    让我们来看一个最简单的Smarty应用。这个应用包括两个文件:
      
    TestSmarty.php 调用Smarty类库,初始化变量,并解析相应的模板文件
    TestSmarty.tpl 模板文件,其实就是包含了Smarty标签的HTML,放在指定的模板目录下,默认是./templates
      
    TestSmarty.php的内容如下:
    Html代码
      
         include_once("./smarty/Smarty.class.php");
      
      $Smarty = new Smarty();
      $Smarty->assign(“HelloStr”, “Hello, world”);
      $Smarty->display(“TestSmarty.tpl”);
      ?>

      
         include_once("./smarty/Smarty.class.php");
      
      $Smarty = new Smarty();
      $Smarty->assign(“HelloStr”, “Hello, world”);
      $Smarty->display(“TestSmarty.tpl”);
      ?>
      

    这个文件的内容非常简单,任何有过PHP经验的程序员都应该能够理解:首先将Smarty类库所在的文件include进来,然后创建一个新的Smarty对象,并对HelloStr变量进行赋值,最后解析TestSmarty.tpl文件。
    TestSmarty.php的内容如下:
      
    Html代码
    This is a string from Smarty: {$HelloStr}

    This is a string from Smarty: {$HelloStr}
      
       解析的结果为:
      
    Html代码
    This is a string from Smarty: Hello, world

    This is a string from Smarty: Hello, world
      
       此时检查存放编译后的PHP文件的子目录(默认是./templates_c),可以找到一个名叫%%65^650^65099D8B%%TestSmarty.tpl.php的文件,内容如下:
      
    compiled from TestSmarty.tpl */ ?>
    This is a string from Smarty:
    _tpl_vars['HelloStr']; ?>
      
    这就是Smarty引擎编译生成的结果。
    为了启用缓存,可以在TestSmarty.php文件中加入这么一行(当然必须在display方法之前):
      
    Html代码
    $Smarty->caching = 1;

    $Smarty->caching = 1;
      
    重新访问该页面,然后检查存放缓存文件的子目录(默认是./cache),可以找到一个名叫%%65^650^65099D8B%%TestSmarty.tpl的文件,内容如下:
      
    136
       a:4:{s:8:”template”;a:1:{s:14:”TestSmarty.tpl”;b:1;}s:9:”timestamp”;i:1186888266;s:7:”expires”;i:1186891866;s:13:”cache_serials”;a:0:{}}This is a string from Smarty: Hello, world
      
    这就是生成的缓存文件,在静态的HTML文件之前,包含了已经序列化的PHP信息。虽然这些信息无法被直接阅读,但是多少还是能够猜测出来:模板的子目录,模板文件名,时间戳,生存期(过期时间),等等。如果读者有兴趣研究它们的详细定义,可以阅读Smarty的源代码。
    注意,上述信息中包含了一项:生存期,即当前缓存在多长时间以后过期。Smarty默认的生存期是1小时,即3600秒。可以通过修改Smarty属性来设置生命期,代码如下:
      
    Html代码
    $Smarty->cache_lifetime = 1800;

    $Smarty->cache_lifetime = 1800;
      
    时间单位是秒,设置为1800表示当前缓存半小时后过期。
    Smarty还支持为同一个模板创建多个缓存实例,这在实际应用中是非常常见的。举例来说,假设某个博客系统中,显示article的页面为 Article.php,对应的模板文件为Article.tpl。但是,article页面的内容根据不同的article ID而不同,因此,必须为同一个页面创建不同的缓存实例。Smarty可以轻松做到这一点:
      
    Html代码
    $Smarty->display(“Article.tpl”, $ArticleId);

    $Smarty->display(“Article.tpl”, $ArticleId);
      
    只要将一个唯一标识符(在这个例子中是article的ID)作为第二个参数传给display方法,Smarty就会自动完成一切。
      
    Smarty出现的时间虽然较老牌的PHPLib Template和FastTemplate为晚,但是发展非常迅速,而且已经成为PHP的官方子项目,拥有二级域名 http://smarty.php.net/。正如它的官方站点上所说,与其说Smarty是一个模板引擎,不如说它是一个表现层的 Framework。这句话极为重要。

    作者个人认为,Smarty诞生和逐渐取得主流地位的意义,不仅仅是提供了一个优秀的模板引擎,而是表示PHP在解决更大规模的应用上迈出了坚实的一步。如果说Java是一开始就将解决大规模应用作为自己的设计目标的话,那么PHP就是在开源社区的推动下,在不断的改进中逐渐接近这一目标。当一个PHP应用采用了Smarty这样优秀和成熟的表现层解决方案,以及其他一些类似的技术(例如好的ORM解决方案)以后,它能够解决的问题的规模就比过去大得多了。通过这些现象,我们可以看到,PHP,或者说LAMP,正在以稳健而持续的步伐,向企业级应用迈进。

    PHP沉思录之二

    2009年06月28日 上午 20:18 | 作者:pangyt

    左轻候 : http://blog.csdn.net/phphot/archive/2008/02/19/2105635.aspx
    PME模型

    在大规模的程序设计中,组件(component)已经成为一种非常流行的技术。常见的组件技术都基于PME模型,即属性(Property)、方法(Method)和事件(Event)。

    旧电脑回收基于PME的组件技术可以方便地实现IoC(Inversion of Control,控制反转),是从IDE的plugin到应用服务器的“热发布”等许多技术的基础。
    PHP从版本5开始,大大完善了对OO的支持,以前不能被应用的许多pattern现在都可以在PHP5中实现。因此,是否能够实现基于PHP的组件技术,也就成了一个值得讨论的问题。
    下面对PHP对于PME模型的支持,逐一进行讨论:

     属性(Property)
    PHP并不支持类似Delphi或者C#的property语法,但这并不是问题。Java也不支持property语法,但是通过getXXX()和setXXX()的命名约定,同样可以支持属性。

    PHP也可以通过这一方式来支持属性。但是,PHP提供了另一种也许更好的方法,那就是__set()和__get()方法。
    在PHP中,每一个class都会自动继承__set()和__get()方法。它们的定义如下:

    void __set ( string name, mixed value )
    mixed __get ( string name )

    这两个方法将在下列情况下被触发:当程序访问一个当前类没有显式定义的属性时。在这个时候,被访问的属性名称作为参数被传入相应的方法。任何类都可以重载__set()和__get()方法,以实现自己的功能。
    如下例:

    class PropertyTester {

    public function __get($PropName) {
    echo “Getting Property $PropNamen”;
    }

    public function __set($PropName, $Value) {
    echo “Setting Property $PropName to ‘$Value’n”;
    }
    }

    $Prop = new PropertyTester();
    $Prop->Name;
    $Prop->Name = “some string”;

    类 PropertyTester重载了__set()和__get()方法,为了测试,仅仅将参数打印输出,没有做更多的工作。测试代码创建了 PropertyTester类的实例,并试图读写它并不存在的一个属性Name。此时,__set()和__get()相继被调用,并打印出相关参数。它的输出结果如下:

    Getting Property Name
    Setting Property Name to ’some string’
    旧电脑回收基于这种机制,我们可以将属性的值放在一个private的List中,在读写属性时,通过重载__set()和__get()方法,读写List中的属性值。
    但是,__set()和__get()方法的有趣之处远不止及。通过这两个方法,可以实现动态属性,也就是不在程序中显式定义,而是在运行时动态生成的属性。只要想想这种技术在OR Mapping中的作用就能够明白它的重要性了。配合__call()方法(用于实现动态方法,在下一节中详述),它能够取代丑陋的代码生成器(code generator)的大部分功能。

     方法(Method)
    PHP对方法的支持比较简单,没有太多可以讨论的。值得一提的是,PHP从版本5开始支持类的静态方法(static method),这使得程序员再也不用无谓地增加许多全局函数了。

     事件(Event)
    事件也许是PHP遇到的最复杂的问题。PHP并没有在语法层面提供对事件的支持,我们只能考虑通过别的途径来实现。因此,我们需要先对事件的概念和其他语言对事件的实现方式进行讨论。

    事件模型可以简述如下:充当事件触发者的代码本身并不处理事件,而仅仅是在事件发生时,把程序控制权转交给事件的处理者,在事件处理完成后,再收回控制权。事件触发者本身并不知道事件将会被如何处理,在大多数情况下,事件触发者的代码要先于事件处理者的代码被完成。

    在传统的面向过程的语言(例如C或者PASCAL)中,事件可以通过函数指针来实现。具体来说,事件触发者定义一个函数指针,这个函数指针可以在以后被指向某个处理事件的函数。在事件发生时,调用该函数指针指向的处理函数,并将事件的上下文作为参数传入。处理完成后,控制权再回到事件触发者。

    在面向对象的语言中,方法指针(指向某个类的方法的指针)取代了函数指针。以Delphi为例,事件处理的例子如下:

    type
    TNotifyEvent = procedure(Sender: TObject) of object;
    TMainForm = class(TForm)
    procedure ButtonClick(Sender: TObject);

    End;
    Var
    MainForm: TMainForm;
    OnClick: TNotifyEvent;

    可以看出,TNotifyEvent被定义为所谓的过程类型(Procedural Type),事实上就是一个方法指针。TMainForm的ButtonClick方法是一个事件处理者,符合TNotifyEvent的签名。 OnClick是一个事件触发者。在实际使用时,通过如下代码:

    OnClick := MainForm.ButtonClick;

    将MainForm.ButtonClick方法绑定到了OnClick事件。当OnClick事件触发时,MainForm.ButtonClick方法将被调用,并且将Sender(触发事件的组件对象)作为参数传入。
    回到PHP,由于PHP不支持指针,因此无法使用函数指针这一技术。但是,PHP支持所谓的“函数变量”,可以把函数赋予某个变量,其作用类似于函数指针。如下例:

    function EventHandler($Sender) {
    echo “Calling EventHandler(), arv = $Sendern”;
    }

    $Func = ‘EventHandler’;
    $Func(‘Sender Name’);

    由于PHP是一种动态语言,变量可以为任何类型,所以无须先定义函数指针的类型作为事件的签名。直接定义了一个函数EventHandler作为事件处理者,然后将它赋予变量$Func(注意直接使用了字符串形式的函数名),最后触发该事件,并将一个字符串“Sender Name”传给它作为参数。输出的结果是:

    Calling EventHandler(), arv = Sender Name

    同样地,PHP也提供了类似方法指针的机制。如下例:

    Class EventHandler {

    public function DoEvent($Sender) {
    echo “Calling EventHandler.DoEvent(), arg = $Sendern”;
    }
    }

    $EventHanler = new EventHandler();
    $HandlerObject = $EventHanler;
    $Method = ‘DoEvent’;
    $HandlerObject->$Method(‘Sender Name’);

    由于PHP中没有能够直接引用对象方法的变量,因此需要使用两个变量来间接实现:$HandlerObject指向对象,$Method指向对象方法。通过$HandlerObject->$Method方式的调用,可以动态地指向任何对象方法。
    为了使代码更加优雅和更适合复用,可以定义一个专门的类NotifyEvent,并使用一段新的调用代码:

    final class NotifyEvent {

    private $HandlerObject;
    private $Method;

    public function __construct($HandlerObject, $Method) {
    $this->HandlerObject = $HandlerObject;
    $this->Method = $Method;
    }

    public function Call($Sender) {
    $Method = $this->Method;
    $this->HandlerObject->$Method($Sender);
    }
    }

    $EventHanler = new EventHandler();
    $NotifyEvent = new NotifyEvent($EventHanler, ‘DoEvent’);
    $NotifyEvent->Call(‘Sender Name’);

    NotifyEvent类定义了两个私有变量$HandlerObject和$Method,分别指向事件处理者对象和处理方法。在构造函数中对这两个变量赋值,再通过Call方法来调用。

    熟悉C#的读者可以发现,NotifyEvent类与C#中的Delegate十分类似。Delegate超过NotifyEvent的地方在于支持多播(Multicast),也就是一个事件可以绑定多个事件处理者。只要事件触发者自己维护一个NotifyEvent对象数组,支持多播也不是一件难事。

    至此,PHP对事件的支持已经得到了比较圆满的解决。但是,人的求知欲是无穷无尽的。还有没有可能通过其他的方式来实现事件呢?

    除了方法指针,接口(interface)也可以用于实现事件。在Java中,这种技术被广泛应用。其核心思想是,将事件处理者的处理函数定义抽象为一个接口(相当于函数指针的签名),事件触发者针对这个接口编程,事件处理者则实现这个接口。

    这种方式的好处在于,不需要语言支持函数指针或方法指针,让代码显得更加清晰和优雅,缺陷在于,实现同一种功能,要使用更多的代码。如下例:

    interface IEventHandler {
    public function DoEvent($Sender, $Arg);
    }

    class EventHanlerAdapter implements IEventHandler {

    public function DoEvent($Sender, $Arg) {
    echo “Calling EventHanlerAdapter.DoEvent(), Sender = $Sender, arg = $Argn”;
    }
    }

    class EventRaiser {

    private $EventHanlerVar;

    public function __construct($EventHanlerAdapter) {
    $this->EventHanlerVar = $EventHanlerAdapter;
    }

    public function RaiseEvent() {
    if ($this->EventHanlerVar != null) {
    $this->EventHanlerVar->DoEvent($this, ’some string’);
    }
    }

    public function __tostring() {
    return ‘Object EventRaier’;
    }

    }

    $EventHanlerAdapter = new EventHanlerAdapter();
    $EventRaiser = new EventRaiser($EventHanlerAdapter);
    $EventRaiser->RaiseEvent();

    首先定义了一个接口IEventHandler,它包含了方法的签名。EventHanlerAdapter类作为事件处理者,实现了这个接口,并提供了相应的处理方法。EventRaiser类作为事件触发者,针对$EventHanlerVar变量(它应该是IEventHandler接口类型,但是在 PHP中不用显式定义)编码。

    在实际应用中,将EventHanlerAdapter的实例作为参数赋予传给EventRaiser类的构造函数,当事件发生时,相应的处理方法将被调用。输出结果如下:

    Calling EventHanlerAdapter.DoEvent(), Sender = Object EventRaier, arg = some string

    最后,让我们回到现实世界中来。虽然我们用PHP完整地实现了PME模型,但是这到底有什么用呢?毕竟,我们不会用PHP去编写IDE,也不会用它编写应用服务器。回答是,基于PME模型的组件技术可以实现更加方便和更大规模的代码复用。

    在基于PHP的应用系统中,虽然插件已经被广泛使用,但是通过组件技术可以实现功能更强大、更加规范和更容易维护的插件。此外,组件技术在实现一些大的Framework(例如,针对Web UI的Framework)时,也是不可或缺的。

    [转]PHP沉思录之一

    2009年06月28日 上午 16:57 | 作者:pangyt

    左轻候:http://blog.csdn.net/phphot/archive/2008/02/19/2105600.aspx

    本文是一个系列的第一篇,目前想到的其他一些主题是:
      
       SQL注入问题
       事件模型
       AOP模型
       UI Framework的实现
       Template机制
      
      
       PHP沉思录
       工作模型
       PHP的工作模型非常特殊。从某种程度上说,PHP和ASP、ASP.NET、JSP/Servlet等流行的Web技术,有着本质上的区别。

       以Java为例,Java在Web应用领域,有两种技术:Java Servlet和JSP(Java Server Page)。Java Servlet是一种特殊类型的Java程序,它通过实现相关接口,处理Web服务器发送过来的请求,完成相应的工作。JSP在形式上是一种类似于PHP 的脚本,但是事实上,它最后也被编译成Servlet。

    也就是说,在Java解决方案中,JSP和Servlet是作为独立的Java应用程序执行的,它们在初始化之后就驻留内存,通过特定的接口和 Web服务器通信,完成相应工作。除非被显式地重启,否则它们不会终止。因此,可以在JSP和Servlet中使用各种缓存技术,例如数据库连接池。

       ASP.NET的机制与此类似。至于ASP,虽然也是一种解释型语言,但是仍然提供了Application对象来存放应用程序级的全局变量,它依托于ASP解释器在IIS中驻留的进程,在整个应用程序的生命期有效。

       PHP却完全不是这样。作为一种纯解释型语言,PHP脚本在每次被解释时进行初始化,在解释完毕后终止运行。这种运行是互相独立的,每一次请求都会创建一个单独的进程或线程,来解释相应的页面文件。页面创建的变量和其他对象,都只在当前的页面内部可见,无法跨越页面访问旧电脑回收。

    在终止运行后,页面中申请的、没有被代码显式释放的外部资源,包括内存、数据库连接、文件句柄、Socket连接等,都会被强行释放。
       也就是说,PHP无法在语言级别直接访问跨越页面的变量,也无法创建驻留内存的对象。见下例:
      
          class StaticVarTester {
       public static $StaticVar = 0;
       }
      
       function TestStaticVar() {
       StaticVarTester :: $StaticVar += 1;
       echo "StaticVarTester :: StaticVar = " . StaticVarTester :: $StaticVar;
       }
      
       TestStaticVar();
       echo "
    “;
       TestStaticVar();
       ?>
      
      在这个例子中,定义了一个名为StaticVarTester的类,它仅有一个公共的静态成员$StaticVar,并被初始化为0。然后,在 TestStaticVar()函数中,对StaticVarTester :: $StaticVar进行累加操作,并将它打印输出。
      熟悉 Java或C++的开发者对这个例子应该并不陌生。$StaticVar作为StaticVarTester类的一个静态成员,只在类被装载时进行初始化,无论StaticVarTester类被实例化多少次,$StaticVar都只存在一个实例,而且不会被多次初始化。因此,当第一次调用 TestStaticVar()函数时,$StaticVar进行了累加操作,值为1,并被保存。第二次调用TestStaticVar()函数,$ StaticVar的值为2。
       打印出来的结果和我们预料的一样:
      
       StaticVarTester :: StaticVar = 1
       StaticVarTester :: StaticVar = 2
      
       但是,当浏览器刷新页面,再次执行这段代码时,不同的情况出现了。在Java或C++里面,$StaticVar的值会被保存并一直累加下去,我们将会看到如下的结果:
      
       StaticVarTester :: StaticVar = 3
       StaticVarTester :: StaticVar = 4
       …
      
       但是在PHP中,由于上文叙及的机制,当前页面每次都解释时,都会执行一次程序初始化和终止的过程。也就是说,每次访问时,StaticVarTester都会被重新装载,而下列这行语句
      
       public static $StaticVar = 0;
      
      也会被重复执行。当页面执行完成后,所有的内存空间都会被回收,$StaticVar这个变量(连同整个StaticVarTester类)也就不复存在。因此,无论刷新页面多少次,$StaticVar变量都会回到起点:先被初始化为0,然后在TestStaticVar()函数调用中被累加。所以,我们看到的结果永远是这个:
      
       StaticVarTester :: StaticVar = 1
       StaticVarTester :: StaticVar = 2
    PHP这种独特的工作模型的优势在于,基本上解决了令人头疼的资源泄漏问题。Web应用的特点是大量的、短时间的并发处理,对各种资源的申请和释放工作非常频繁,很容易导致泄漏。

    同时,大量的动态html脚本的存在,使得追踪和调试的工作都非常困难。PHP的运行机制,以一种非常简单的方式避免了这个问题,同时也避免了将程序员带入到繁琐的缓冲池和同步等问题中去。在实践中,基于PHP的应用往往比基于Java或.NET的应用更加稳定,不会出现由于某个页面的BUG而导致整个站点崩溃的问题。

    (相比之下,Java或.NET应用可能因为缓冲池崩溃或其他的非法操作,而导致整个站点崩溃。)后果是,即使PHP程序员水平不高,也无法写出使整个应用崩溃的代码。PHP脚本可以一次调用极多的资源,从而导致页面执行极为缓慢,但是执行完毕后所有的资源都会被释放,应用仍然不会崩溃。

    甚至即使执行了一个死循环,也会在30秒(默认时间)后因为超时而中止。从理论上来说,基于PHP的应用是不太可能崩溃的,因为它的运行机制决定它不存在常规的崩溃这个问题。在实践中,很多开发者也认为PHP是最稳定的Web应用。

    但是,这种机制的缺点也非常明显。最直接的后果是,PHP在语言级别无法实现跨页面的缓冲机制。这种缓冲机制缺失造成的影响,可以分成两个方面:

      一是对象的缓冲。如我们所知,很多设计模式都依赖于对象的缓冲机制,对于需要频繁应付大量并发的服务端软件更是如此。因此,对象缓冲的缺失,理论上会极大地降低速度。但是,由于PHP本身的定位和工作机制等原因,它在实际工作中的速度非常快。就作者自己的经验来看,在小型的Web应用中,PHP至少不比 Java慢。

    在大型的应用中,为了榨干每一分硬件资源,即使PHP本身足够快,一个优秀的对象缓冲机制仍然是必要的。在这种情况下,可以使用第三方的内存缓冲软件,如Memcached。由于Memcached本身的优异特性(高性能,支持跨服务器的分布式存储,和PHP的无缝集成等),在大型的PHP应用中, Memcached几乎已经成为不可或缺的基础设施了。比起使用PHP语言自己实现对象缓冲来,这种第三方解决方案似乎更好一些。

       二是数据库连接的缓冲。对MySQL,PHP提供了一种内置的数据库缓冲机制,使用起来非常简单,程序员需要做的只是用mysql_pconnect()代替mysql_connect()来打开数据库而已。

    PHP会自动回收被废弃的数据库连接,以供重复使用。具有讽刺意味的是,在实际应用中,这种持久性数据库连接往往会导致数据库连接的伪泄漏现象:在某个时间,并发的数据库连接过多,超过了MySQL的最大连接数,从而导致新的进程无法连接数据库。

    但是过一段时间,当并发数减少时,PHP会释放掉一些连接,网站又会恢复正常。出现这种现象的原因是,当使用pconnect时,Apache的httpd 进程会不释放connect,而当Apache的httpd进程数超过了mysql的最大连接数时,就会出现无法连接的情况。因此,需要小心地调整 Apache和Mysql的配置,以使Apache的httpd进程数不会超出MySQL的最大连接数。在某些情况下,一些有经验的PHP程序员宁可继续使用mysql_connect(),而不是mysql_pconnect()。

      就作者所知,在PHP未来的roadmap中,对于工作模型这一部分,没有根本性的变动。这是PHP的缺点,也是PHP的优势,从本质上说,这就是PHP 的独特之处。因此,我们很难期待PHP在近期内会对这一问题做出重大的改变。但是,在对待这个问题带来的一系列后果时,我们必须谨慎应对旧电脑回收。
      
       数据库访问接口
      长期以来,PHP都缺乏一个象ADO或JDBC那样的统一的数据库访问接口。PHP在访问不同的数据库时,使用不同的专门API。例如,使用 mysql_connect函数连接MySQL,使用ora_logon函数连接Oracle。平心而论,这种方式并没有象我们想象的那样麻烦。
    在真实项目中,把系统从一种数据库完全迁移到另一种数据库的要求是比较少见的,特别是对于LAMP这样的小型项目而言。而且,只要将访问数据库的代码进行了良好的封装,迁移的工作量也会较少。另外,使用专门API,在效率上多少会有一些优势。

       虽然如此,PHP的开发人员仍然在努力构建PHP的统一的数据库访问接口。从PHP 5.1开始,PHP的发行包内置了PDO(PHP Data Objects,PHP数据对象)。PDO具有如下特性:
      
        统一的数据库访问接口。PDO为访问不同的数据库提供了统一的接口,并且能够通过切换数据库驱动程序,方便地支持各种流行的数据库。
        面向对象。PDO完全基于PHP 5的对象机制,因此区别于基于过程的专用API。
        高性能。PDO的底层用C编写,比起用纯PHP开发的其他类似解决方案,有更高的性能。
      
       一个典型的PDO应用如下例:
      
       $pdo = new PDO(“mysql:host=localhost;dbname=justtest”, ” mysql_user “, ” mysql_password”);
       $query = “SELECT id, username FROM userinfo ORDER BY ID”;
       foreach ($pdo->query($query) as $row) {
       echo $row['id'].” | “.$row['username'].”
    “;
       } 但是,PDO还有一个更重要的问题没有解决,那就是对数据集的抽象。
      无论是ADO还是JDBC,除了提供统一的数据库访问接口以外,也提供了对数据集的抽象。也就是说,在通过ADO/JDBC取回数据集结果以后,这些数据集以统一的格式被存放在RecordSet/RowSet对象中,业务逻辑代码只需要与数据集对象进行交互即可。对数据集进行抽象的直接后果,是彻底地分离了业务逻辑层和数据库访问层的代码,并且也在某种程度上起到了OR Mapping的效果。
       自从ADO.NET出现后,数据集的抽象又有了一次不小的进步。和ADO相比,ADO.NET中的DataTable/DataSet类的主要新特性如下:
      
        非连接性。在传统的ADO模型中,数据集需要占用一个数据库连接,直到所有工作完成。一旦连接被关闭,数据集的内容也就失效了。ADO.NET中的数据集是非连接的,也就是说,当连接被关闭后,数据集中的内容仍然保存。这种非连接性带来的直接后果是,数据库连接可以被最大限度地利用,因为一旦工作完成就可以将连接返回到数据库连接池中。(ADO也支持非连接的数据集,但是需要程序员自己实现,而ADO.NET的数据集在本质上就是非连接的。)
        自描述性。ADO.NET中的数据集是完全自我描述的,而且具有完备的metadata,其内容不但可以从任何特定的数据库生成,而且可以由代码动态生成。DataSet可以跟踪数据的变化,并完成相应的操作。
        互操作性。由于非连接性和自描述性,ADO.NET中的数据集可以非常方便地在网络之间进行传输。DataSet可以序列化/反序列化为XML或其他特定的格式。这样,DataSet不但可以用于同一平台的分布式网络环境,而且可以用于异构网络环境。
      
       Java从J2SE 5.0开始内置了CachedRowSet,其原理和ADO.NET的数据集类似。Borland开发的用于取代BDE的新一代数据库引擎dbExpress,其改进也与此类似。
       与之对比,PHP中对数据集的支持显得非常原始。无论是传统的API还是PDO,取回的数据仅仅表现为数组,并且没有任何缓存机制。这意味着,在所有需要访问数据集的地方,都必须频繁地使用直接访问数据库的API。下面是一个使用mysql API的例子:
      
       $link = mysql_connect(‘localhost’, ‘mysql_user’, ‘mysql_password’);
       if (!$link) {
       die(‘Could not connect: ‘ . mysql_error());
       }
       mysql_select_db(“justtest”);
       $result = mysql_query(“SELECT id, username FROM userinfo ORDER BY ID”);
       while ($row = mysql_fetch_array($result)) {
       echo $row['id'].” | “.$row['username'].”
    “;
       }
       mysql_close($link);
      
       这样做的后果是业务逻辑和访问数据库的代码无法分离,在规模较大的系统中尤其严重。
      就作者所知,PHP官方没有提供支持抽象数据集的计划。但是,自己实现这样一个数据集并不是一件难事。作者参照ADO.NET的架构,使用纯PHP代码编写了一个规模非常小的数据抽象类库,姑且称之为MyPDO。MyPDO大约有1300行代码,在几个真实项目中工作得很好。由于篇幅所限,本文不列出它的所有代码,仅仅给出几个最主要的类的描述:
      
        DataAdapter接口:定义了所有与数据库操作相关的方法。
        ConceptDataAdapter类:实现了DataAdapter,封装了访问特定数据库的代码。如MySqlDataAdapter。
        DataSet类:实现了自描述的抽象数据集,与具体数据库无关。可以自我跟踪数据的变化。
        SqlCommandBuilder类:可以跟据DataSet类的内容,自动实现Insert、Update、Delete等操作。
      
       下面是MyPDO的一个典型应用:
      
       $Conn = new MySqlDataConnection(new ConnectionInfo(“localhost”, “mysql_username”, “mysql_password”, “justtest”, “gbk”));
       $Da = $Conn->GetDataAdapter();
       $Da->SetSqlString(“SELECT id, username FROM userinfo ORDER BY ID”);
       $Ds = new DataSet();
       $Da->Fill($Ds);
       $Conn->Disconnect(); // 关闭数据库连接,但$Ds仍然保存数据内容
       echo $Ds; // 调用DataSet的__tostring()方法,格式化输出内容
      
       通过替换MySqlDataConnection,可以以最小的成本实现不同数据库之间的切换。
       由于上文中讨论过的PHP的工作模型,通过MyPDO实现的缓存在性能上获得的好处有限。但是,在采用Memcached的解决方案中,MyPDO还是能够带来很大的便利。因为只有基于非连接方式的数据集,才可能在Memcached这样的内存池中被缓存。
       另外,由于MyPDO中的DataSet是自描述的,内置了WriteToXml和ReadFromXml方式,它无需程序员编码就可以保存为XML或从XML中还原,在网络上甚至异构平台之间进行传输和识别。在电子商务领域中,这个特性是非常有用的。

    [转]apache的prefork模式和worker模式

    2009年06月28日 上午 59:54 | 作者:pangyt

    出处:平凡的世界
    链接:http://www.ccvita.com/339.html

    prefork模式
    这个多路处理模块(MPM)实现了一个非线程型的、预派生的web服务器,它的工作方式类似于Apache 1.3。它适合于没有线程安全库,需要避免线程兼容性问题的系统。它是要求将每个请求相互独立的情况下最好的MPM,这样若一个请求出现问题就不会影响到其他请求。

    这个MPM具有很强的自我调节能力,只需要很少的配置指令调整。最重要的是将MaxClients设置为一个足够大的数值以处理潜在的请求高峰,同时又不能太大,以致需要使用的内存超出物理内存的大小。

    worker模式
    此多路处理模块(MPM)使网络服务器支持混合的多线程多进程。由于使用线程来处理请求,所以可以处理海量请求,而系统资源的开销小于基于进程的MPM。但是,它也使用了多进程,每个进程又有多个线程,以获得基于进程的MPM的稳定性。

    控制这个MPM的最重要的指令是,控制每个子进程允许建立的线程数的ThreadsPerChild指令,和控制允许建立的总线程数的MaxClients指令。

    prefork和worker模式的切换
    1.将当前的prefork模式启动文件改名
    mv httpd httpd.prefork
    2.将worker模式的启动文件改名
    mv httpd.worker httpd
    3.修改Apache配置文件
    vi /usr/local/apache2/conf/extra/httpd-mpm.conf
    找到里边的如下一段,可适当修改负载等参数:

    StartServers 2
    MaxClients 150
    MinSpareThreads 25
    MaxSpareThreads 75
    ThreadsPerChild 25
    MaxRequestsPerChild 0

    4.重新启动服务
    /usr/local/apache2/bin/apachectl restart
    即可换成worker方式启动apache2

    处于稳定性和安全性考虑,不建议更换apache2的运行方式,使用系统默认prefork即可。另外很多php模块不能工作在worker模式下,例如redhat linux自带的php也不能支持线程安全。所以最好不要切换工作模式。

    prefork和worker模式的比较
    prefork模式使用多个子进程,每个子进程只有一个线程。每个进程在某个确定的时间只能维持一个连接。在大多数平台上,Prefork MPM在效率上要比Worker MPM要高,但是内存使用大得多。prefork的无线程设计在某些情况下将比worker更有优势:它可以使用那些没有处理好线程安全的第三方模块,并且对于那些线程调试困难的平台而言,它也更容易调试一些。

    worker模式使用多个子进程,每个子进程有多个线程。每个线程在某个确定的时间只能维持一个连接。通常来说,在一个高流量的HTTP服务器上,Worker MPM是个比较好的选择,因为Worker MPM的内存使用比Prefork MPM要低得多。但worker MPM也由不完善的地方,如果一个线程崩溃,整个进程就会连同其所有线程一起”死掉”.由于线程共享内存空间,所以一个程序在运行时必须被系统识别为”每个线程都是安全的”。

    总的来说,prefork方式速度要稍高于worker,然而它需要的cpu和memory资源也稍多于woker。

    prefork模式配置详解

    ServerLimit 256
    StartServers 5
    MinSpareServers 5
    MaxSpareServers 10
    MaxClients 256
    MaxRequestsPerChild 0

    ServerLimit
    默认的MaxClient最大是256个线程,如果想设置更大的值,就的加上ServerLimit这个参数。20000是ServerLimit这个参数的最大值。如果需要更大,则必须编译apache,此前都是不需要重新编译Apache。
    生效前提:必须放在其他指令的前面

    StartServers
    指定服务器启动时建立的子进程数量,prefork默认为5。

    MinSpareServers
    指定空闲子进程的最小数量,默认为5。如果当前空闲子进程数少于MinSpareServers ,那么Apache将以最大每秒一个的速度产生新的子进程。此参数不要设的太大。

    MaxSpareServers
    设置空闲子进程的最大数量,默认为10。如果当前有超过MaxSpareServers数量的空闲子进程,那么父进程将杀死多余的子进程。此参数不要设的太大。如果你将该指令的值设置为比MinSpareServers小,Apache将会自动将其修改成”MinSpareServers+1″。

    MaxClients
    限定同一时间客户端最大接入请求的数量(单个进程并发线程数),默认为256。任何超过MaxClients限制的请求都将进入等候队列,一旦一个链接被释放,队列中的请求将得到服务。要增大这个值,你必须同时增大ServerLimit。

    MaxRequestsPerChild
    每个子进程在其生存期内允许伺服的最大请求数量,默认为10000.到达MaxRequestsPerChild的限制后,子进程将会结束。如果 MaxRequestsPerChild为”0″,子进程将永远不会结束。将MaxRequestsPerChild设置成非零值有两个好处:
    1.可以防止(偶然的)内存泄漏无限进行,从而耗尽内存。
    2.给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。

    worker模式配置详解

    StartServers 2
    MaxClients 150
    MinSpareThreads 25
    MaxSpareThreads 75
    ThreadsPerChild 25
    MaxRequestsPerChild 0

    StartServers
    服务器启动时建立的子进程数,默认值是”3″。

    MaxClients
    允许同时伺服的最大接入请求数量(最大线程数量)。任何超过MaxClients限制的请求都将进入等候队列。默认值是”400″ ,16(ServerLimit)乘以25(ThreadsPerChild)的结果。因此要增加MaxClients的时候,你必须同时增加 ServerLimit的值。

    MinSpareThreads
    最小空闲线程数,默认值是”75″。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太少,子进程将产生新的空闲线程。

    MaxSpareThreads
    设置最大空闲线程数。默认值是”250″。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太多,子进程将杀死多余的空闲线程。 MaxSpareThreads的取值范围是有限制的。Apache将按照如下限制自动修正你设置的值:worker要求其大于等于 MinSpareThreads加上ThreadsPerChild的和。

    ThreadsPerChild
    每个子进程建立的常驻的执行线程数。默认值是25。子进程在启动时建立这些线程后就不再建立新的线程了。

    MaxRequestsPerChild
    设置每个子进程在其生存期内允许伺服的最大请求数量。到达MaxRequestsPerChild的限制后,子进程将会结束。如果MaxRequestsPerChild为”0″,子进程将永远不会结束。将MaxRequestsPerChild设置成非零值有两个好处:
    1.可以防止(偶然的)内存泄漏无限进行,从而耗尽内存。
    2.给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。
    注意对于KeepAlive链接,只有第一个请求会被计数。事实上,它改变了每个子进程限制最大链接数量的行为。

    注:restart并不等于stop+start。

    using expect

    2009年06月23日 上午 58:36 | 作者:pangyt

    热身小脚本
    function myscp
    {
    local FNAME=$1
    local UNAME=$2
    local HNAME=$3
    local PASSWD=$4
    local FNAME_REMOTE=$5
    expect < spawn scp $FNAME $UNAME@$HNAME:$FNAME_REMOTE
    set timeout 5
    set e 0
    while 1 {
    expect {
    “*(yes/no)?” { send “yes\r” }
    “*assword:” { if {\$e == 1} { send_user “\nPassword error!\n”; exit 1 }; set e [expr \$e+1]; send “$PASSWD\r” }
    timeout { send_user “Connect timeout!\n”; exit 1 }
    eof { exit 0 }
    }
    }
    EOF
    }

      Expect是一个控制交互式程序的工具。他用非交互的方式实现了所有交互式的功能
      Expect语言是基于Tcl的。Tcl实际上是一个子程序库,这些子程序库可以嵌入到程序里从而提供语言服务。 最终的语言有点象一个典型的 Shell语言。里面有给变量赋值的set命令,控制程序执行的if,for,continue等命令,还能进行普通的数学和字符串操作。当然了,还可以用exec来调用Unix程序。所有这些功能,Tcl都有。Tcl在参考书籍 Outerhour[3][4]里有详细的描述。

       Expect是在Tcl基础上创建起来的,它还提供了一些Tcl所没有的命令。spawn命令激活一个Unix程序来进行交互式的运行。 send命令向进程发送字符串。expect命令等待进程的某些字符串。 expect支持正规表达式并能同时等待多个字符串,并对每一个字符串执行不同的操作。 expect还能理解一些特殊情况,如超时和遇到文件尾。

      expect命令和Tcl的case命令的风格很相似。都是用一个字符串去匹配多个字符串。(只要有可能,新的命令总是和已有的Tcl命令相似,以使得该语言保持工具族的继承性)。下面关于expect的定义是从手册[5]上摘录下来的。

          expect patlist1 action1 patlist2 action2…..

        该命令一直等到当前进程的输出和以上的某一个模式相匹配,或者等到时间超过一个特定的时间长度,或者等到遇到了文件的结束为止。
        
        如果最后一个action是空的,就可以省略它。

        每一个patlist都由一个模式或者模式的表(lists)组成。如果有一个模式匹配成功,相应的action就被执行。执行的结果从expect返回。
        被精确匹配的字符串(或者当超时发生时,已经读取但未进行匹配的字符串)被存贮在变量expect_match里面。如果patlist是eof 或者timeout,则发生文件结束或者超时时才执行相应的action.一般超时的时值是10秒,但可以用类似”set timeout 30″之类的命令把超时时值设定为30秒。
        
        下面的一个程序段是从一个有关登录的脚本里面摘取的。abort是在脚本的别处定义的过程,而其他的action使用类似与C语言的Tcl原语。

          expect “*welcome*”        break     
               “*busy*”        {print busy;continue}
              “*failed*”        abort 
              timeout        abort

        模式是通常的C Shell风格的正规表达式。模式必须匹配当前进程的从上一个expect或者interact开始的所有输出(所以统配符*使用的非常)的普遍。但是,一旦输出超过2000个字节,前面的字符就会被忘记,这可以通过设定match_max的值来改变。

      expect命令确实体现了 expect语言的最好和最坏的性质。特别是,expect命令的灵活性是以经常出现令人迷惑的语法做代价。除了关键字模式(比如说eof,timeout)那些模式表可以包括多个模式。这保证提供了一种方法来区分他们。但是分开这些表需要额外的扫描,如果没有恰当的用 ["]括起来,这有可能会把和当成空白字符。由于Tcl提供了两种字符串引用的方法:单引和双引,情况变的更糟。(在Tcl里面,如果不会出现二义性话,没有必要使用引号)。在expect的手册里面,还有一个独立的部分来解释这种复杂性。幸运的是:有一些很好的例子似乎阻止了这种抱怨。但是,这个复杂性很有可能在将来的版本中再度出现。为了增强可读性,在本文中,提供的脚本都假定双引号是足够的。
    为“Tk”的工具箱如何简洁地表达 GUI 解决方案。

    B tree

    2009年06月22日 下午 42:19 | 作者:pangyt

    [转]B树:

    http://blog.csdn.net/manesking/archive/2007/02/09/1505979.aspx

    PG调优

    2009年06月21日 上午 31:15 | 作者:pangyt

    参考:http://www.varlena.com/GeneralBits/Tidbits/perf.html

    http://www.varlena.com/GeneralBits/Tidbits/annotated_conf_e.html

    http://linuxfinances.info/info/postgresql.html

    设置这些选项的一个方法是编辑文件 postgresql.conf
    每次postmaster收到SIGHUP(最简单的发送方法就是使用 pg_ctl reload),信号后都会重新读取这个配置文件。 postmaster 同时也将这个信号广播给所有正在运行的服务器进程,这样现有会话也能得到新的缺省。 另外,你可以只向一个服务器进程直接发送信号。 有些参数只能在服务器启动的时候设置;对这些条目中的任何进行修改都将忽略,直到下次服务器重启。
    第二种设置这些配置参数的方法是把它们作为命令行参数传递给 postmaster, 比如:postmaster -c log_connections=yes -c log_destination=’syslog’.命令行选项覆盖任何与 postgresql.conf 冲突的选项,请注意,这意味着你不能通过编辑 postgresql.conf, 在运行时改变其数值,因此,虽然命令行方法很方便,但你在以后可能会付出灵活性的代价。
    有时候,给某一个特定会话一个命令行参数也是很有用的。 可以在客户端使用环境变量 PGOPTIONS 来实现这个目的:
    env PGOPTIONS=’-c geqo=off’ psql

    数据库导出:(-f可以换成>)
    sudo -u postgres /usr/local/pgsql/bin/pg_dump -d URT1 -f /tmp/dump.sql
    数据库导入:(-f可以换成<)
    sudo -u postgres /usr/local/pgsql/bin/pgsql -d URT1 -f /tmp/dump.sql
    导出指定的字段:
    select * into test_tbl from some_tbl where ****;
    pg_dump -d db_name -t test_tbl > /tmp/test.sql
    导入则类似。

    环境变量设置:
    vi .bash_profile
    LD_LIBRARY_PATH=/usr/local/pgsql/lib
    export LD_LIBRARY_PATH
    PATH=$PATH:$HOME/bin:/usr/local/pgsql/bin
    export PATH
    MANPATH=/usr/local/pgsql/man:$MANPATH
    export MANPATH

    应用程序
    initdb — 创建一个新的 PostgreSQL数据库集群
    initlocation — 创建一个从属的 PostgreSQL数据库存储区
    ipcclean — 从退出的PostgreSQL服务器中删除共享内存和信号灯
    pg_ctl — 启动,停止和重起 PostgreSQL
    pg_controldata — 显示服务器范围哪的空值信息
    pg_resetxlog — 重置预写日志以及 pg_control 内容
    postgres — 意单用户模式运行一个 PostgreSQL服务器
    postmaster — PostgreSQL多用户数据库服务器

    clusterdb — 对一个PostgreSQL数据库进行建簇
    createdb — 创建一个新的 PostgreSQL 数据库
    createlang — 定义一种新的 PostgreSQL 过程语言.
    createuser — 定义一个新的 PostgreSQL 用户帐户
    dropdb — 删除一个现有 PostgreSQL 数据库
    droplang — 删除一种 PostgreSQL 过程语言
    dropuser — 删除一个 PostgreSQL 用户帐户
    ecpg — 嵌入的 SQL C 预处理器
    pg_config — 检索已安装版本的 PostgreSQL 的信息
    pg_dump — 将一个PostgreSQL数据库抽出到一个脚本文件 或者其它归档文件中
    pg_dumpall — 抽出一个 PostgreSQL 数据库集群到脚本文件中。
    pg_restore — 从一个由 pg_dump 创建的备份文件中恢复 PostgreSQL 数据库.
    psql — PostgreSQL 交互终端
    pgtclsh — PostgreSQL TCL shell 客户端
    pgtksh — PostgreSQL Tcl/Tk shell 客户端.
    vacuumdb — 收集垃圾并且分析一个PostgreSQL 数据库

    监控数据库活动
    select now() as Time,datname as Database ,xact_commit as commit,blks_read as read,blks_hit as hit,tup_returned as return,tup_fetched as fetch,tup_inserted as insert,tup_updated as update,tup_deleted as delete from pg_stat_database;
    pg_stat_activity 每个服务器进程一行,显示进程ID,数据库,用户,和当前查询. 只有超级用户看得到当前查询字段;对于其它用户,它显示为 NULL. (请注意因为收集器的报告延迟,当前查询只是对长时间运行的查询 及时更新.)
    pg_stat_database 每个数据库一行,显示激活的后端的数量,提交的事务总数以及在该 数据库中回卷数目的总数,读取的磁盘块的总数,以及缓冲区命中的总数( 也就是中所需要的块已经在缓冲区中找到,从而避免了读取块的动作).

    pg_stat_get_tuples_returned(oid) bigint 如果参数是一个表,那么就是顺序扫描读取的元组数目, 如果是一个索引,那么就是索引元组的数目
    pg_stat_get_tuples_fetched(oid) bigint 如果参数是一个表,那么就是顺序扫描抓取的有效(未过期)的表元组数目, 如果是一个索引,那么就是用这个索引抓取的有效表元组数目
    pg_stat_get_tuples_inserted(oid) bigint 插入表中的元组数量
    pg_stat_get_tuples_updated(oid) bigint 在表中已更新的元组数量
    pg_stat_get_tuples_deleted(oid) bigint 从表中删除的元组数量
    pg_stat_get_blocks_fetched(oid) bigint 表或者索引的磁盘块抓取请求的数量
    pg_stat_get_blocks_hit(oid) bigint 在缓冲区中找到的表或者索引的磁盘块请求数目

    sysctl.conf

    2009年06月19日 下午 48:16 | 作者:pangyt

    {转}sysctl 是一个用来在系统运行中查看及调整系统参数的工具。有的 sysctl 参数只是用来报告目前的系统状况,例如报告目前已开机时间、所使用的操作系统版本、核心名称等等;而有的可以让我们修改参数以调整系统运作的行为,例如网络暂存内存的大小、最大的上线人数等等。sysctl.conf就是sysctl的配置文件,而这些可以调整的参数中有的必须在一开机系统执行其它程序前就设定好,有的可以在开机完后任意调整。同大多数配置文件一样,我们可以对sysctl.conf进行配置来优化系统的性能.

    网上关于sysctl.conf的优化方案有各种版本,大多都是抄来抄去的,让新人看了很迷茫。为解决此问题,经过两天的整理,查了N多资料,将大家常用的总结如下,很多默认的不需要修改的暂未涉及,今后将逐步把所有的项目都有个翻译、讲解、修改建议,如有修改,将以此文为准,其他地方的内容,本人不负责更新。因此转载请注明链接地址:http://www.bsdlover.cn/securit…如果您有补充或修订意见,请于本文后评论或邮件联系cujxtm@gmail.com,万分感谢!

    ###################

    所有rfc相关的选项都是默认启用的,因此网上的那些还自己写rfc支持的都可以扔掉了

    ###############################

    net.inet.ip.sourceroute=0

    net.inet.ip.accept_sourceroute=0

    #############################

    通过源路由,攻击者可以尝试到达内部IP地址 –包括RFC1918中的地址,所以不接受源路由信息包可以防止你的内部网络被探测。

    #################################

    net.inet.tcp.drop_synfin=1

    ###################################

    安全参数,编译内核的时候加了options TCP_DROP_SYNFIN才可以用,可以阻止某些OS探测。

    ##################################

    kern.maxvnodes=8446

    vnode 是对文件或目录的一种内部表达。因此,增加可以被操作系统利用的vnode数量将降低磁盘的I/O。

    一般而言,这是由操作系统自行完成的,也不需要加以修改。但在某些时候磁盘 I/O 会成为瓶颈,

    而系统的 vnode 不足,则这一配置应被增加。此时需要考虑是非活跃和空闲内存的数量。

    要查看当前在用的 vnode 数量: sysctl vfs.numvnodes

    vfs.numvnodes: 91349

    要查看最大可用的 vnode 数量: sysctl kern.maxvnodes

    kern.maxvnodes: 100000

    如果当前的 vnode 用量接近最大值,则将 kern.maxvnodes 值增大 1,000 可能是个好主意。

    您应继续查看 vfs.numvnodes 的数值, 如果它再次攀升到接近最大值的程度,仍需继续提高 kern.maxvnodes。 在 top(1) 中显示的内存用量应有显著变化,更多内存会处于活跃 (active) 状态。

    ####################################

    kern.maxproc: 964

    ###################…

    Maximum number of processes

    ####################################

    kern.maxprocperuid: 867

    Maximum processes allowed per userid

    ####################################

    因为我的maxusers设置的是256,20+16*maxusers=4116。

    maxprocperuid至少要比maxproc少1,因为init(8) 这个系统程序绝对要保持在运作状态。我给它设置的2068。

    kern.maxfiles: 1928

    系统中支持最多同时开启的文件数量,如果你在运行数据库或大的很吃描述符的进程,那么应该设置在20000以上,

    比如kde这样的桌面环境,它同时要用的文件非常多。一般推荐设置为32768或者65536。

    ####################################

    kern.argmax: 262144

    maximum number of bytes (or characters) in an argument list.

    命令行下最多支持的参数,比如你在用find命令来批量删除一些文件的时候

    find . -name “*.old” -delete,如果文件数超过了这个数字,那么会提示你数字太多的。

    可以利用find . -name “*.old” -ok rm {} \;来删除。

    默认的参数已经足够多了,因此不建议再做修改。

    ####################################

    kern.securelevel: -1

    -1:这是系统默认级别,没有提供任何内核的保护错误;

    0:基本上作用不多,当你的系统刚启动就是0级别的,当进入多用户模式的时候就自动变成1级了。

    1:在这个级别上,有如下几个限制:

    a. 不能通过kldload或者kldunload加载或者卸载可加载内核模块;

    b. 应用程序不能通过/dev/mem或者/dev/kmem直接写内存;

    c. 不能直接往已经装在(mounted)的磁盘写东西,也就是不能格式化磁盘,但是可以通过标准的内核接口执行写操作;

    d. 不能启动X-windows,同时不能使用chflags来修改文件属性;

    2:在 1 级别的基础上还不能写没装载的磁盘,而且不能在1秒之内制造多次警告,这个是防止DoS控制台的;

    3:在 2 级别的级别上不允许修改IPFW防火墙的规则。

    如果你已经装了防火墙,并且把规则设好了,不轻易改动,那么建议使用3级别,如果你没有装防火墙,而且还准备装防火墙的话,不建议使用。

    我们这里推荐使用 2 级别,能够避免比较多对内核攻击。

    ####################################

    kern.maxfilesperproc: 1735

    每个进程能够同时打开的最大文件数量,网上很多资料写的是32768,除非用异步I/O或大量线程,打开这么多的文件恐怕是不太正常的。我个人建议不做修改,保留默认。

    ####################################

    kern.ipc.maxsockbuf: 262144

    最大的套接字缓冲区,网上有建议设置为2097152(2M)、8388608(8M)的。

    我个人倒是建议不做修改,保持默认的256K即可,缓冲区大了可能造成碎片、阻塞或者丢包。

    ####################################

    kern.ipc.somaxconn: 128

    最大的等待连接完成的套接字队列大小,即并发连接数。

    高负载服务器和受到Dos攻击的系统也许会因为这个队列被塞满而不能提供正常服务。

    默认为128,推荐在1024-4096之间,根据机器和实际情况需要改动,数字越大占用内存也越大。

    ####################################

    kern.ipc.nmbclusters: 4800

    这个值用来调整系统在开机后所要分配给网络 mbufs 的 cluster 数量,由于每个 cluster 大小为 2K,所以当这个值为 1024 时,也是会用到 2MB 的核心内存空间。假设我们的网页同时约有 1000 个联机,而 TCP 传送及接收的暂存区大小都是 16K,则最糟的情况下,我们会需要 (16K+16K) * 1024,也就是 32MB 的空间,然而所需的 mbufs 大概是这个空间的二倍,也就是 64MB,所以所需的 cluster 数量为 64MB/2K,也就是 32768。对于内存有限的机器,建议值是 1024 到 4096 之间,而当拥有海量存储器空间时,我们可以将它设定为 4096 到 32768 之间。我们可以使用 netstat 这个指令并加上参数 -m 来查看目前所使用的 mbufs 数量。要修改这个值必须在一开机就修改,所以只能在 /boot/loader.conf 中加入修改的设定

    kern.ipc.nmbclusters=32768

    ####################################

    kern.ipc.shmmax: 33554432

    # 线程可使用的最大共享内存.如果这些过小的话,有些大型的软件将无法启动

    安装xine和mplayer提示的设置为67108864,即64M,如果内存多的话,可以设置为134217728,即128M

    ####################################

    kern.ipc.shmall: 8192

    # 最大线程数量 .如果这些过小的话,有些大型的软件将无法启动

    安装xine和mplayer提示的设置为32768

    ####################################

    kern.ipc.shm_use_phys: 0

    如果我们将它设成 1,则所有 System V 共享内存 (share memory,一种程序间沟通的方式)部份都会被留在实体的内存 (physical memory) 中,

    而不会被放到硬盘上的 swap 空间。我们知道物理内存的存取速度比硬盘快许多,而当物理内存空间不足时,

    部份数据会被放到虚拟的内存上,从物理内存和虚拟内存之间移转的动作就叫作 swap。如果时常做 swap 的动作,

    则需要一直对硬盘作 I/O,速度会很慢。因此,如果我们有大量的程序 (数百个) 需要共同分享一个小的共享内存空间,

    或者是共享内存空间很大时,我们可以将这个值打开。这一项,我个人建议不做修改,除非你的内存非常大。

    ####################################

    kern.ipc.shm_allow_removed: 0

    共享内存是否允许移除?这项似乎是在fb下装vmware需要设置为1的,否则会有加载SVGA出错的提示

    作为服务器,这项不动也罢。

    kern.ipc.numopensockets: 12
    已经开启的socket数目,可以在最繁忙的时候看看它是多少,然后就可以知道maxsockets应该设置成多少了。
    ####################################
    kern.ipc.maxsockets: 1928
    这是用来设定系统最大可以开启的 socket 数目。如果您的服务器会提供大量的 FTP 服务,
    而且常快速的传输一些小档案,您也许会发现常传输到一半就中断。因为 FTP 在传输档案时,
    每一个档案都必须开启一个 socket 来传输,但关闭 socket 需要一段时间,如果传输速度很快,
    而档案又多,则同一时间所开启的 socket 会超过原本系统所许可的值,这时我们就必须把这个值调大一点。
    除了 FTP 外,也许有其它网络程序也会有这种问题。
    然而,这个值必须在系统一开机就设定好,所以如果要修改这项设定,我们必须修改 /boot/loader.conf 才行
    kern.ipc.maxsockets=”16424″
    ####################################
    kern.ipc.nsfbufs: 1456
    经常使用 sendfile(2) 系统调用的繁忙的服务器,
    有必要通过 NSFBUFS 内核选项或者在 /boot/loader.conf (查看 loader(8) 以获得更多细节) 中设置它的值来调节 sendfile(2) 缓存数量。
    这个参数需要调节的普通原因是在进程中看到 sfbufa 状态。sysctl kern.ipc.nsfbufs 变量在内核配置变量中是只读的。
    这个参数是由 kern.maxusers 决定的,然而它可能有必要因此而调整。
    在/boot/loader.conf里加入
    kern.ipc.nsfbufs=”2496″
    ####################################
    kern.maxusers: 59
    maxusers 的值决定了处理程序所容许的最大值,20+16*maxusers 就是你将得到的所容许处理程序。系统一开机就必须要有 18 个处理程序 (process),即便是简单的执行指令 man 又会产生 9 个 process,所以将这个值设为 64 应该是一个合理的数目。如果你的系统会出现 proc table full 的讯息的话,可以就把它设大一点,例如 128。除非您的系统会需要同时开启很多档案,否则请不要设定超过 256。可以在 /boot/loader.conf 中加入该选项的设定,kern.maxusers=256
    ####################################
    kern.coredump: 1
    如果设置为0,则程序异常退出时不会生成core文件,作为服务器,不建议这样。
    ####################################
    kern.corefile: %N.core
    可设置为kern.corefile=”/data/coredump/%U-%P-%N.core”,其中 %U是UID,%P是进程ID,%N是进程名,当然/data/coredump必须是一个实际存在的目录
    ####################################
    vm.swap_idle_enabled: 0
    vm.swap_idle_threshold1: 2
    vm.swap_idle_threshold2: 10
    #########################
    在有很多用户进入、离开系统和有很多空闲进程的大的多用户系统中很有用。可以让进程更快地进入内存,但它会吃掉更多的交换和磁盘带宽。
    系统默认的页面调度算法已经很好了,最好不要更改。
    ########################
    vfs.ufs.dirhash_maxmem: 2097152
    #########################
    默认的dirhash最大内存,默认2M. 增加它有助于改善单目录超过100K个文件时的反复读目录时的性能, 建议修改为33554432(32M)
    #############################
    vfs.vmiodirenable: 1
    #################
    这个变量控制目录是否被系统缓存。大多数目录是小的,在系统中只使用单个片断(典型的是1K)并且在缓存中使用的更小 (典型的是512字节)。当这个变量设置为关闭 (0) 时,缓存器仅仅缓存固定数量的目录,即使您有很大的内存。而将其开启 (设置为1) 时,则允许缓存器用 VM 页面缓存来缓存这些目录,让所有可用内存来缓存目录。不利的是最小的用来缓存目录的核心内存是大于 512 字节的物理页面大小(通常是 4k)。我们建议如果您在运行任何操作大量文件的程序时保持这个选项打开的默认值。这些服务包括 web 缓存,大容量邮件系统和新闻系统。尽管可能会浪费一些内存,但打开这个选项通常不会降低性能。但还是应该检验一下。
    ####################
    vfs.hirunningspace: 1048576
    这个值决定了系统可以将多少数据放在写入储存设备的等候区。通常使用默认值即可,但当我们有多颗硬盘时,我们可以将它调大为 4MB 或 5MB。注意这个设置成很高的值(超过缓存器的写极限)会导致坏的性能。不要盲目的把它设置太高!高的数值会导致同时发生的读操作的迟延。
    #############################
    vfs.write_behind: 1
    这个选项预设为 1,也就是打开的状态。在打开时,在系统需要写入数据在硬盘或其它储存设备上时,它会等到收集了一个 cluster 单位的数据后再一次写入,否则会在一个暂存区空间有写入需求时就立即写到硬盘上。这个选项打开时,对于一个大的连续的文件写入速度非常有帮助。但如果您遇到有很多行程延滞在等待写入动作时,您可能必须关闭这个功能。
    ############################
    net.local.stream.sendspace: 8192
    本地套接字连接的数据发送空间 建议设置为65536
    ###################################
    net.local.stream.recvspace: 8192
    本地套接字连接的数据接收空间 建议设置为65536
    ###################################
    net.inet.ip.portrange.lowfirst: 1023
    net.inet.ip.portrange.lowlast: 600
    net.inet.ip.portrange.first: 49152
    net.inet.ip.portrange.last: 65535
    net.inet.ip.portrange.hifirst: 49152
    net.inet.ip.portrange.hilast: 65535
    ###################
    以上六项是用来控制TCP及UDP所使用的port范围,这个范围被分成三个部份,低范围、预设范围、及高范围。这些是你的服务器主动发起连接时的临时端口的范围,预设的已经1万多了,一般的应用就足够了。如果是比较忙碌的FTP server,一般也不会同时提供给1万多人访问的,当然如果很不幸,你的服务器就要提供很多,那么可以修改first的值,比如直接用1024开始
    #########################
    net.inet.ip.redirect: 1
    设置为0,屏蔽ip重定向功能
    ###########################
    net.inet.ip.rtexpire: 3600
    net.inet.ip.rtminexpire: 10
    很多apache产生的CLOSE_WAIT状态,这种状态是等待客户端关闭,但是客户端那边并没有正常的关闭,于是留下很多这样的东东。建议都修改为2
    #########################
    net.inet.ip.intr_queue_maxlen: 50
    Maximum size of the IP input queue,如果下面的net.inet.ip.intr_queue_drops一直在增加,那就说明你的队列空间不足了,那么可以考虑增加该值。
    ##########################
    net.inet.ip.intr_queue_drops: 0
    Number of packets dropped from the IP input queue,如果你sysctl它一直在增加,那么增加net.inet.ip.intr_queue_maxlen的值。
    #######################
    net.inet.ip.fastforwarding: 0
    如果打开的话,每个目标地址一次转发成功以后它的数据都将被记录进路由表和arp数据表,节约路由的计算时间,但会需要大量的内核内存空间来保存路由表。如果内存够大,打开吧,呵呵
    #############################
    net.inet.ip.random_id: 0
    默认情况下,ip包的id号是连续的,而这些可能会被攻击者利用,比如可以知道你nat后面带了多少主机。如果设置成1,则这个id号是随机的,嘿嘿。
    #####################
    net.inet.icmp.maskrepl: 0
    防止广播风暴,关闭其他广播探测的响应。默认即是,无须修改。
    ###############################
    net.inet.icmp.icmplim: 200
    限制系统发送ICMP速率,改为100吧,或者保留也可,并不会给系统带来太大的压力。
    ###########################
    net.inet.icmp.icmplim_output: 1
    如果设置成0,就不会看到提示说Limiting icmp unreach response from 214 to 200 packets per second 等等了.不过禁止输出容易让我们忽视攻击的存在。这个自己看着办吧。
    ######################################
    net.inet.icmp.drop_redirect: 0
    net.inet.icmp.log_redirect: 0
    设置为1,屏蔽ICMP重定向功能
    ###################################
    net.inet.icmp.bmcastecho: 0
    防止广播风暴,关闭广播ECHO响应,默认即是,无须修改。
    ###############################
    net.inet.tcp.mssdflt: 512
    net.inet.tcp.minmss: 216
    ###############################
    数据包数据段最小值,以上两个选项最好不动!或者只修改mssdflt为1460,minmss不动。原因详见http://www.bsdlover.cn/security/2007/1211/article_4.html
    #############################
    net.inet.tcp.keepidle: 7200000
    TCP的套接字的空闲时间,默认时间太长,可以改为600000(10分钟)。
    ##########################
    net.inet.tcp.sendspace: 32768
    最大的待发送TCP数据缓冲区空间,应用程序将数据放到这里就认为发送成功了,系统TCP堆栈保证数据的正常发送。
    ####################################
    net.inet.tcp.recvspace: 65536
    最大的接受TCP缓冲区空间,系统从这里将数据分发给不同的套接字,增大该空间可提高系统瞬间接受数据的能力以提高性能。
    ###################################
    这二个选项分别控制了网络TCP联机所使用的传送及接收暂存区的大小。预设的传送暂存区为 32K,而接收暂存区为 64K。如果需要加速 TCP 的传输,可以将这二个值调大一点,但缺点是太大的值会造成系统核心占用太多的内存。如果我们的机器会同时服务数百或数千个网络联机,那么这二个选项最好维持默认值,否则会造成系统核心内存不足。但如果我们使用的是 gigabite 的网络,将这二个值调大会有明显效能的提升。传送及接收的暂存区大小可以分开调整,
    例如,假设我们的系统主要做为网页服务器,我们可以将接收的暂存区调小一点,并将传送的暂存区调大,如此一来,我们就可以避免占去太多的核心内存空间。
    ###################################
    net.inet.udp.maxdgram: 9216
    最大的发送UDP数据缓冲区大小,网上的资料大多都是65536,我个人认为没多大必要,如果要调整,可以试试24576。
    ##############################
    net.inet.udp.recvspace: 42080
    最大的接受UDP缓冲区大小,网上的资料大多都是65536,我个人认为没多大必要,如果要调整,可以试试49152。
    #######################
    以上四项配置通常不会导致问题,一般说来网络流量是不对称的,因此应该根据实际情况调整,并观察其效果。如果我们将传送或接收的暂存区设为大于 65535,除非服务器本身及客户端所使用的操作系统都支持 TCP 协议的 windows scaling extension (请参考 RFC 1323 文件)。FreeBSD默认已支持 rfs1323 (即 sysctl 的 net.inet.tcp.rfc1323 选项)。
    ###################################################
    net.inet.tcp.log_in_vain: 0
    记录下任何TCP连接,这个一般情况下不应该更改。
    ####################
    net.inet.tcp.blackhole: 0
    建议设置为2,接收到一个已经关闭的端口发来的所有包,直接drop,如果设置为1则是只针对TCP包
    #####################################
    net.inet.tcp.delayed_ack: 1
    当一台计算机发起TCP连接请求时,系统会回应ACK应答数据包。该选项设置是否延迟ACK应答数据包,把它和包含数据的数据包一起发送。在高速网络和低负载的情况下会略微提高性能,但在网络连接较差的时候,对方计算机得不到应答会持续发起连接请求,反而会让网络更加拥堵,降低性能。因此这个值我建议您看情况而定,如果您的网速不是问题,可以将封包数量减少一半 .如果网络不是特别好,那么就设置为0,有请求就先回应,这样其实浪费的网通、电信的带宽速率而不是你的处理时间:)
    ############################
    net.inet.tcp.inflight.enable: 1
    net.inet.tcp.inflight.debug: 0
    net.inet.tcp.inflight.rttthresh: 10
    net.inet.tcp.inflight.min: 6144
    net.inet.tcp.inflight.max: 1073725440
    net.inet.tcp.inflight.stab: 20
    限制 TCP 带宽延迟积和 NetBSD 的 TCP/Vegas 类似。它可以通过将 sysctl变量net.inet.tcp.inflight.enable设置成1来启用。系统将尝试计算每一个连接的带宽延迟积,并将排队的数据量限制在恰好能保持最优吞吐量的水平上。这一特性在您的服务器同时向使用普通调制解调器,千兆以太网,乃至更高速度的光与网络连接 (或其他带宽延迟积很大的连接) 的时候尤为重要,特别是当您同时使用滑动窗缩放,或使用了大的发送窗口的时候。如果启用了这个选项,您还应该把 net.inet.tcp.inflight.debug 设置为 0 (禁用调试),对于生产环境而言, 将 net.inet.tcp.inflight.min 设置成至少 6144 会很有好处。然而, 需要注意的是,这个值设置过大事实上相当于禁用了连接带宽延迟积限制功能。这个限制特性减少了在路由和交换包队列的堵塞数据数量,也减少了在本地主机接口队列阻塞的数据的数量。在少数的等候队列中、交互式连接,尤其是通过慢速的调制解调器,也能用低的 往返时间操作。但是,注意这只影响到数据发送 (上载/服务端)。对数据接收(下载)没有效果。
    调整 net.inet.tcp.inflight.stab 是 不 推荐的。这个参数的默认值是 20,表示把 2 个最大包加入到带宽延迟积窗口的计算中。额外的窗口似的算法更为稳定,并改善对于多变网络环境的相应能力,但也会导致慢速连接下的 ping 时间增长 (尽管还是会比没有使用 inflight 算法低许多)。对于这些情形, 您可能会希望把这个参数减少到 15, 10, 或 5;
    并可能因此而不得不减少 net.inet.tcp.inflight.min (比如说, 3500) 来得到希望的效果。减少这些参数的值, 只应作为最后不得已时的手段来使用。
    ############################
    net.inet.tcp.syncookies: 1
    SYN cookies是一种用于通过选择加密的初始化TCP序列号,可以对回应的包做验证来降低SYN’洪水’攻击的影响的技术。默认即是,不需修改
    ########################
    net.inet.tcp.msl: 30000
    这个值网上很多文章都推荐的7500,还可以改的更小一些(如2000或2500),这样可以加快不正常连接的释放过程(三次握手2秒、FIN_WAIT4秒)。
    #########################
    net.inet.tcp.always_keepalive: 1
    帮助系统清除没有正常断开的TCP连接,这增加了一些网络带宽的使用,但是一些死掉的连接最终能被识别并清除。死的TCP连接是被拨号用户存取的系统的一个特别的问题,因为用户经常断开modem而不正确的关闭活动的连接。
    #############################
    net.inet.udp.checksum: 1
    防止不正确的udp包的攻击,默认即是,不需修改
    ##############################
    net.inet.udp.log_in_vain: 0
    记录下任何UDP连接,这个一般情况下不应该修改。
    #######################
    net.inet.udp.blackhole: 0
    建议设置为1,接收到一个已经关闭的端口发来的所有UDP包直接drop
    #######################
    net.inet.raw.maxdgram: 8192
    Maximum outgoing raw IP datagram size 很多文章建议设置为65536,好像没多大必要。
    ######################################
    net.inet.raw.recvspace: 8192
    Maximum incoming raw IP datagram size 很多文章建议设置为65536,好像没多大必要。
    #######################
    net.link.ether.inet.max_age: 1200
    调整ARP清理的时间,通过向IP路由缓冲填充伪造的ARP条目可以让恶意用户产生资源耗竭和性能减低攻击。这项似乎大家都未做改动,我建议不动或者稍微减少,比如300(HP-UX默认的5分钟)
    #######################
    net.inet6.ip6.redirect: 1
    设置为0,屏蔽ipv6重定向功能
    ###########################
    net.isr.direct: 0
    所有MPSAFE的网络ISR对包做立即响应,提高网卡性能,设置为1。
    ####################################
    hw.ata.wc: 1
    这个选项用来打开 IDE 硬盘快取。当打开时,如果有数据要写入硬盘时,硬盘会假装已完成写入,并将数据快取起来。这种作法会加速硬盘的存取速度,但当系统异常关机时,比较容易造成数据遗失。不过由于关闭这个功能所带来的速度差异实在太大,建议还是保留原本打开的状态吧,不做修改。
    ###################
    security.bsd.see_other_uids: 1
    security.bsd.see_other_gids: 1
    #####################
    不允许用户看到其他用户的进程,因此应该改成0.

    memcache临摹

    2009年06月19日 下午 44:26 | 作者:pangyt

    先发牢骚:即便规模尚小,酷讯、抓虾、一见等公司都成立了产品委员会:由产品部门调查和整理用户需求,提交产品委员会审议。以酷讯为例,它现 在的产品部 门有8个人,由产品部、研发、项目管理部、测试、设计等各个部门的人参加,目的是让每个细分产品都能明确指向用户需求。迅雷甚至建立起了更完整的信息反馈 机制,在内部,员工提出意见、纠错能够得到奖励,对外,鼓励用户提出意见,甚至对相关人士每周进行抽奖。

    我的博客有个特点,就是图多,呵呵,一图顶千言么。

    这篇文章的目的是对当下极度流行的memcache进行代码级别的分析,虽然在网上已经流行了几篇很不错的分析文章,但是我还是忍不住做一些补充,多以图的形式。
    参考文章:

    memcache分析调试

    Memcached深度分析(原创)

    高性能,分布式,轻量级缓存组件memcached的源码剖析—第二篇,libevent

    主题:Memcached源码分析(线程模型)

    那么我们开始吧。