不一样的思想~~ http://shop34712791.taobao.com MSN:wins0910@hotmail.com

发布新日志

  • Thread 摘抄

    2008-01-29 22:55:48

    我在Delphi中一个TSocketServer控件看到一个东西,它的概念不是很清楚,我说说它的原理.  
           
          定义一个CacheThreadSize,   default:10,当有客户连接,Accept返回时,到一个线程数组列表中找有没有空闲的线程,有则直接激活(SetEvent)这个线程,并将Accept   SocketHandle传于它,进入与客户会话期间,会话结束后,根据这个线程的索引决定是否退出这个线程,如果它是CacheThreadSize中的线程,即是线程池的线程,则waitfor.  
          如果没有空闲的线程,则新建一个线程,这个线程是否是线程池内的线程,是根据它是第几个线程,如果超过CacheThreadSize,说明不是,会话结束后线程也结束.  
           
          这样如果有10客户连接后,如果有客户断开,那Server端并不删除线程,只是进入等待,如有另外客户连接,则激活线程,超过CacheThreadSize后,则是新建线程==>会话==>断开连接==>析构线程.
  • mysql tuning

    2008-01-20 23:55:38

    mysql性能指标

    mysqladmin extended (绝对值)
    重点去监视的值有:
    * Slave_running:如果系统有一个从复制服务器,这个值指明了从服务器的健康度
    * Threads_connected:当前客户端已连接的数量。这个值会少于预设的值,但你也能监视到这个值较大,这可保证客户端是处在活跃状态。
    * Threads_running:如果数据库超负荷了,你将会得到一个正在(查询的语句持续)增长的数值。这个值也可以少于预先设定的值。这个值在很短的时间内超过限定值是没问题的。当Threads_running值超过预设值时并且该值在5秒内没有回落时, 要同时监视其他的一些值。
    2)mysqladmin extended(计数器)
    * Aborted_clients:客户端被异常中断的数值(因为连接到mysql服务器的客户端没有正常地断开或关闭)。对于一些应用程序是没有影响的,但对于另一些应用程序可能你要跟踪该值,因为异常中断连接可能表明了一些应用程序有问题。
    * Questions:每秒钟获得的查询数量。也可以是全部查询的数量(注:可以根据你输入不同的命令会得到你想要的不同的值)。
    * Handler_*:如果你想监视底层(low-level)数据库负载,这些值是值得去跟踪的。如果Handler_read_rnd_next值相对于你认为是正常值相差悬殊,可能会告诉你需要优化或索引出问题了。Handler_rollback表明事务被回滚的查询数量。你可能想调查一下原因。
    * Opened_tables:表缓存没有命中的数量。如果该值很大,你可能需要增加table_cache的数值。典型地,你可能想要这个值每秒打开的表数量少于1或2.
    * Select_full_join: 没有主键(key)联合(Join)的执行。该值可能是零。这是捕获开发错误的好方法,因为一些这样的查询可能降低系统的性能。
    * Select_scan:执行全表搜索查询的数量。在某些情况下是没问题的,但占总查询数量该比值应该是常量(注:Select_scan除以总查询数量商应该是常数)。如果你发现该值持续增长,说明需要优化,缺乏必要的索引或其他问题。
    * Slow_queries:超过该值(--long-query-time)的查询数量,或没有使用索引查询数量。对于全部查询会有小的冲突。如果该值增长,表明系统有性能问题。
    * Threads_created:该值应该是低的。较高的值可能意味着你需要增加thread_cache的数值,或你遇到了持续增加的连接,表明了潜在的问题。
    3)mysqladmin processlist or "SHOW FULLPROCESSLIST"命令
    你可以通过使用其他的统计信息得到已连接线程数量和正在运行线程的数量,检查正在运行的查询花了多长时间是一个好主意。如果有一些长时间的查询(由于很差的构思或公式),管理员可以被通知。你可能也想了解多少个查询是在"Locked"的状态—---该值作为正在运行的查询不被计算在内而是作为非活跃的。一个用户正在等待一个数据库响应。
    4) "SHOW INNODB STATUS"命令
    该语句产生很多信息,从中你可以得到你感兴趣的。首先你要检查的就是“从最近的XX秒计算出来的每秒的平均负载”。
    * Pending normal aio reads: 该值是innodb io请求查询的大小(size)。如果该值大到超过了10—20,你可能有一些瓶颈。
    * reads/s, avg bytes/read, writes/s, fsyncs/s:这些值是io统计。对于reads/writes大值意味着io子系统正在被装载。适当的值取决于你系统的配置。
    * Buffer pool hit rate:这个命中率非常依赖于你的应用程序。当你觉得有问题时请检查你的命中率
    * inserts/s, updates/s, deletes/s, reads/s:有一些Innodb的底层操作。你可以用这些值检查你的负载情况查看是否是期待的数值范围。
    4)OS数据。查看系统状态好的工具是vmstat/iostat/mpstat,这些可以看man手册
    5)SQL" ōnclick="tagshow(event)" class="t_tag">MySQL错误日志-----在服务器正常完成初始化后,什么都不会写到错误日志中,因此任何在该日志中的信息都要引起管理员的注意。
    6)InnoDB表空间信息。InnoDB仅有的危险情况就是表空间填满----日志不会填满。检查的最好方式就是:show table status;你可以用任何InnoDB表来监视InnoDB表的剩余空间。
    ----

    我最近在网上查了一些有关优化MySql的资料,并对照MySql手册对一些调优设置进行了细结,但是由于时间比较勿忙,没来得及对这些设置进行实验/测试,你可以把这些方法应用到实际中,得出具体结论。调优方法大致如下:

     

    MySql服务器的后台管理程序,要想使用客户端程序,该程序必须运行,因为客户端通过连接服务器来访问数据库。下面让我们以服务器的系统变量和状态变量为根据,优化我们的MySql数据库服务。


    在这之前,我们需要掌握以下方法:


    查看MySql状态及变量的方法:


    Mysql> show status ——显示状态信息(扩展show status like 'XXX')
    Mysql> show variables ——显示系统变量(扩展show variables like 'XXX')
    Mysql> show innodb status ——显示InnoDB存储引擎的状态
    Shell> mysqladmin variables -u username -p password——显示系统变量
    Shell> mysqladmin extended-status -u username -p password——显示状态信息


    查看状态变量及帮助:


    Shell> mysqld --verbose --help [|more #逐行显示]


    首先,让我们看看有关请求连接的变量:


    为了能适应更多数据库应用用户,MySql提供了连接(客户端)变量,以对不同性质的用户群体提供不同的解决方案,笔者就max_connections,back_log做了一些细结,如下:


    max_connections是指MySql的最大连接数,如果服务器的并发连接请求量比较大,建议调高此值,以增加并行连接数量,当然这建立在机器能支撑的情况下,因为如果连接数越多,介于MySql会为每个连接提供连接缓冲区,就会开销越多的内存,所以要适当调整该值,不能盲目提高设值。可以过'conn%'通配符查看当前状态的连接数量,以定夺该值的大小。


    back_log是要求MySQL能有的连接数量。当主要MySQL线程在一个很短时间内得到非常多的连接请求,这就起作用,然后主线程花些时间(尽管很短)检查连接并且启动一个新线程。back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。如果期望在一个短时间内有很多连接,你需要增加它。也就是说,如果MySql的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。另外,这值(back_log)限于您的操作系统对到来的TCP/IP连接的侦听队列的大小。你的操作系统在这个队列大小上有它自己的限制(可以检查你的OS文档找出这个变量的最大值),试图设定back_log高于你的操作系统的限制将是无效的。


    优化了MySql的连接后属性后,我们需要看看缓冲区变量:


    使用MySql数据库存储大量数据(或使用复杂查询)时,我们应该考虑MySql的内存配置。如果配置MySQL服务器使用太少的内存会导致性能不是最优的;如果配置了太多的内存则会导致崩溃,无法执行查询或者导致交换操作严重变慢。在现在的32位平台下,仍有可能把所有的地址空间都用完,因此需要审视。


    计算内存使用的秘诀公式就能相对地解决这一部分问题。不过,如今这个公式已经很复杂了,更重要的是,通过它计算得到的值只是“理论可能”并不是真正消耗的值。事实上,有8GB内存的常规服务器经常能运行到最大的理论值(100GB甚至更高)。此外,你轻易不会使用到“超额因素”(它实际上依赖于应用以及配置)。一些应用可能需要理论内存的10%而有些仅需1%。
    那么,我们可以做什么呢?


    来看看那些在启动时就需要分配并且总是存在的全局缓冲吧!


    全局缓冲:
    key_buffer_size, innodb_buffer_pool_size, innodb_additional_mem_pool_size,innodb_log_buffer_size, query_cache_size


    注:如果你大量地使用MyISAM表,那么你也可以增加操作系统的缓存空间使得MySQL也能用得着。把这些也都加到操作系统和应用程序所需的内存值之中,可能需要增加32MB甚至更多的内存给MySQL服务器代码以及各种不同的小静态缓冲。这些就是你需要考虑的在MySQL服务器启动时所需的内存。其他剩下的内存用于连接。


    key_buffer_size决定索引处理的速度,尤其是索引读的速度。一般我们设为16M,通过检查状态值Key_read_requests和Key_reads,可以知道key_buffer_size设置是否合理。比例key_reads / key_read_requests应该尽可能的低,至少是1:100,1:1000更好(上述状态值可以使用'key_read%'获得用来显示状态数据)。key_buffer_size只对MyISAM表起作用。即使你不使用MyISAM表,但是内部的临时磁盘表是MyISAM表,也要使用该值。可以使用检查状态值'created_tmp_disk_tables'得知详情。


    innodb_buffer_pool_size对于InnoDB表来说,作用就相当于key_buffer_size对于MyISAM表的作用一样。InnoDB使用该参数指定大小的内存来缓冲数据和索引。对于单独的MySQL数据库服务器,最大可以把该值设置成物理内存的80%。


    innodb_additional_mem_pool_size指定InnoDB用来存储数据字典和其他内部数据结构的内存池大小。缺省值是1M。通常不用太大,只要够用就行,应该与表结构的复杂度有关系。如果不够用,MySQL会在错误日志中写入一条警告信息。


    innodb_log_buffer_size指定InnoDB用来存储日志数据的缓存大小,如果您的表操作中包含大量并发事务(或大规模事务),并且在事务提交前要求记录日志文件,请尽量调高此项值,以提高日志效率。


    query_cache_size是MySql的查询缓冲大小。(从4.0.1开始,MySQL提供了查询缓冲机制)使用查询缓冲,MySQL将SELECT语句和查询结果存放在缓冲区中,今后对于同样的SELECT语句(区分大小写),将直接从缓冲区中读取结果。根据MySQL用户手册,使用查询缓冲最多可以达到238%的效率。通过检查状态值’Qcache_%’,可以知道query_cache_size设置是否合理:如果Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况,如果Qcache_hits的值也非常大,则表明查询缓冲使用非常频繁,此时需要增加缓冲大小;如果Qcache_hits的值不大,则表明你的查询重复率很低,这种情况下使用查询缓冲反而会影响效率,那么可以考虑不用查询缓冲。此外,在SELECT语句中加入SQL_NO_CACHE可以明确表示不使用查询缓冲。


    除了全局缓冲,MySql还会为每个连接发放连接缓冲。


    连接缓冲:
    每个连接到MySQL服务器的线程都需要有自己的缓冲。大概需要立刻分配256K,甚至在线程空闲时,它们使用默认的线程堆栈,网络缓存等。事务开始之后,则需要增加更多的空间。运行较小的查询可能仅给指定的线程增加少量的内存消耗,然而如果对数据表做复杂的操作例如扫描、排序或者需要临时表,则需分配大约read_buffer_size,sort_buffer_size,read_rnd_buffer_size,tmp_table_size大小的内存空间。不过它们只是在需要的时候才分配,并且在那些操作做完之后就释放了。有的是立刻分配成单独的组块。tmp_table_size 可能高达MySQL所能分配给这个操作的最大内存空间了。注意,这里需要考虑的不只有一点 —— 可能会分配多个同一种类型的缓存,例如用来处理子查询。一些特殊的查询的内存使用量可能更大——如果在MyISAM表上做成批的插入时需要分配 bulk_insert_buffer_size 大小的内存;执行 ALTER TABLE, OPTIMIZE TABLE, REPAIR TABLE 命令时需要分配 myisam_sort_buffer_size 大小的内存。

    read_buffer_size是MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。read_buffer_size变量控制这一缓冲区的大小。如果对表的顺序扫描请求非常频繁,并且你认为频繁扫描进行得太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能。

    sort_buffer_size是MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小。

    read_rnd_buffer_size是MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。

    tmp_table_size是MySql的heap(堆积)表缓冲大小。所有联合在一个DML指令内完成,并且大多数联合甚至可以不用临时表即可以完成。大多数临时表是基于内存的(HEAP)表。具有大的记录长度的临时表 (所有列的长度的和)或包含BLOB列的表存储在硬盘上。如果某个内部heap(堆积)表大小超过tmp_table_size,MySQL可以根据需要自动将内存中的heap表改为基于硬盘的MyISAM表。还可以通过设置tmp_table_size选项来增加临时表的大小。也就是说,如果调高该值,MySql同时将增加heap表的大小,可达到提高联接查询速度的效果。

    当我们设置好了缓冲区大小之后,再来看看:

    table_cache所有线程打开的表的数目,增大该值可以增加mysqld需要的文件描述符的数量。每当MySQL访问一个表时,如果在表缓冲区中还有空间,该表就被打开并放入其中,这样可以更快地访问表内容。通过检查峰值时间的状态值’Open_tables’和’Opened_tables’,可以决定是否需要增加table_cache的值。如果你发现open_tables等于table_cache,并且opened_tables在不断增长,那么你就需要增加table_cache的值了(上述状态值可以使用’Open%tables’获得)。注意,不能盲目地把table_cache设置成很大的值。如果设置得太高,可能会造成文件描述符不足,从而造成性能不稳定或者连接失败。

    做了以上方面的调优设置之后,MySql应该基本能满足您需求(当然是建立在调优设置适当的情况下),我们还应该了解并注意:

    只有简单查询OLTP(联机事务处理)应用的内存消耗经常是使用默认缓冲的每个线程小于1MB,除非需要使用复杂的查询否则无需增加每个线程的缓冲大小。使用1MB的缓冲来对10行记录进行排序和用16MB的缓冲基本是一样快的(实际上16MB可能会更慢,不过这是其他方面的事了)。

    找出MySQL服务器内存消耗的峰值。这很容易就能计算出操作系统所需的内存、文件缓存以及其他应用。在32位环境下,还需要考虑到32位的限制,限制 “mysqld” 的值大约为2.5G(实际上还要考虑到很多其他因素)。现在运行 “ps aux” 命令来查看 “VSZ” 的值(MySQL 进程分配的虚拟内存)。监视着内存变化的值,就能知道是需要增加或减少当前的内存值了。

    最后来看看调优设置方法:

    安装好MySql后,配制文件应该在 ./share/mysql ("./"即MySql安装目录) 目录中,配制文件有几个,有my-huge.cnf my-medium.cnf my-large.cnf my-small.cnf。win环境下即存在于MySql安装目录中的.ini文件。不同的流量的网站和不同配制的服务器环境,当然需要有不同的配制文件了。

    一般的情况下,my-medium.cnf这个配制文件就能满足我们的大多需要;一般我们会把配置文件拷贝到 /etc/my.cnf ,win环境下则拷备到 my.ini 下即可,只需要修改这个配置文件就可以了。

  • 摘抄

    2008-01-14 23:41:46

  • JMeter j技巧集锦(二)

    2007-11-25 19:22:00

              图 4  在代理服务器组建中增加一个高斯随机定时器


      定时器将会使相应的的取样器被延迟。 延时的规则是,在上一个访问请求被响应并延时了指定的时间后,下一个被定时器影响的取样访问请求才会被发送出去。 因此, 你必须手工删除第一个取样器中自动生成的定时器,因为第一个取样器不需要定时器。

      在启动HTTP代理服务器以前,要在测试计划中增加一个线程组(thread group),在线程组中增加一个录制控制器(recording controller)用于存储生成的结果。 否则, 生成的元件将会被直接添加到工作台里。另外, 在录制控制器里增加一个HTTP请求默认值元件HTTP Request Defaults 元件 (是一个配置元件) 也很重要,这样Jmeter就不填写使用了默认值的字段。

      录制完成后, 停止HTTP 代理服务器; 在录制控制器元件上单击右键将记录的元件保存为一个文件用于以后重用,另外,不要忘了恢复浏览器的代理服务器设置。

      指定响应时间需求并校验结果

      尽管本节内容与Jmeter不是直接相关,但是Jmeter仍旧是指定响应时间需求和校验测试结果这两个负载测试评价任务互相联系的纽带。

      在web应用的环境里,响应时间指的是从提交访问请求到等到HTML结果所耗费的时间。从技术的角度看,响应时间也应包括浏览器重绘HTML页面的时间,但是浏览器一般是一块接着一块地显示而不是直接显示完整的整个页面,让人感觉响应时间要少一些。 另外,典型的情况是,负载测试工具不会考虑浏览器的重绘时间。 因此, 在实际的性能测试中,我们将考虑以上描述的情形, 如果不能确信,可以在正常的响应时间上加一个固定值,如0.5秒。

      以下是一套众所周知的确定相应时间的标准:
       ·用户将不会注意到少于0.1秒的延迟
       ·少于1秒的延迟不会中断用户的正常思维, 但是一些延迟会被用户注意到
       ·延迟时间少于10秒,用户会继续等待响应
       ·延迟时间超过10秒后,用户将会放弃并开始其他操作

      这些阀值很有名并且一般不会改变,因为是关乎人类的感知特性的。 所以要根据这些规则来设置响应时间需求, 也需要适当调整以适应实际应用。例如,亚马逊公司(Amazon.com) 的主页也遵循了以上规则,但是由于更偏重于风格上的一致,所以在响应时间上有一点损失。

      乍一看,好像有两种不同的方式来确定相应时间需求:
       ·平均响应时间(Average response time )
       ·绝对响应时间(Absolute response time);即, 所有的响应时间必须低于某一阀值

      指定平均响应时间比较简单一些(straightforward),但是由于数据变化的干扰,这个需求往往难以实现。为什么取样中的20%的响应时间要比平均值高3倍以上呢?请注意,JMeter 计算平均响应时间与图形结果监视器中的标准偏差是一致的。

      另一方面, 对绝对响应时间需求过于苛求是不实际的。 如果只有0。5%的取样不能通过测试该怎么办?如果再测一次,又会有很大的变化。 幸运的是, 使用置信区间(confidence interva)分析这种正规的统计方法可以顾及到取样变化的影响。
    在继续进行前,让我们首先回顾一些基本的统计学知识。

      中心极限定理(The central limit theorem)

      中心极限定理表明如果总体的分布有一个平均值μ和标准偏差σ,那么对于一个十分大的n(>30),其取样平均值的分布将接近于正态分布,其平均值μmean = μ ,标准偏差σmean = σ/√n。注意取样平均值的分布是正态的,而取样自身的分布不必是正态的。也就是说如果多次运行测试脚本则测试结果的平均响应时间将会是正态的。

      图 5 和图 6 分别展示了两个正态分布。 在这里横坐标是采样响应时间的均值, 总体的均值被调整到坐标的原点(shifted so the population mean is at the origin)。 图5 表明90%的时间里,采样均值位于±Zσ的区间里(percent of the time, the sampling means are within the interval ±Zσ,),这里的Z=1.645 和 σ 是标准偏差。 图 6 表明了99%的情况下的情形这时的Z=2.576。 在给定的概率下,如90%, 我们可以看到相应的Z呈现正态曲线,反之亦然。

    ---

                 Figure 5。 Z value for 90 percent

                 Figure 6。 Z value for 99 percent

      在相关资料中所列的是可提供正态曲线计算的一些网站。在这些网站,我们可以计算随意的相对区间内的概率(如,-1.5 < X < 1.5)或者在一个聚集的区域(cumulated area)内 ,(如, X < 1.5)。 也可以从下面的表中得到近似值。

      表 1  对应于给定的置信区间(confidence interval)的标准偏差范围(Standard deviation range)

      表 2  对应于给定的标准偏差范围(Standard deviation)的置信区间(confidence interval)

      置信区间(Confidence interval)

      置信区间(confidence interval)的定义是[取样平均值- Z*σ/√n, 取样平均值+ Z*σ/√n]。 例如, 如果置信区间(概率)是90%, 经查找可知Z 值是1。645, 于是置信区间就是 [取样平均值- 1。645*σ/√n, 取样平均值+ 1。645*σ/√n], 这意味着在90%的时间里, 总体平均值(population mean)(是未知的) 会落入这个置信区间内。 也就是说, 我们的测试结果是十分接近的。 如果 σ(标准偏差) 更大一些, 置信区间也会更大,这就意味着置信区间的上限就会更可能会越过可以接受的范围,即σ 越大,结果越不可信。

      响应时间需求(Response-time requirements )

      现在我们把所有的信息都归结到响应时间需求上来。首先。必须要定义性能需求,如: %95概率的置信区间的平均响应时间的上限必须小于5秒。 当然,最好有相应的需求或场景。

      在性能测试结束后,假设进分析得出结论是平均响应时间是4.5秒,标准偏差时4.9秒,样本数量是120个,然后就可以计算%95概率的置信区间了。 通过查表1,找到Z值是 1。95996。 于是置信区间就是 [4.5 – 1.95996*4.9/√120, 4.5 + 1.95996*4.9/√120], 也就是 [3.62, 5.38]。 尽管看起来这个响应时间看起来很不错,但这个结果(因为超出了需求的要求,因而)是不可接受的。 实际上, 可以检验的是即使是对于80%概率的可信区间,这个测试结果也是不能接受的。正如你所看到的,使用了置信区间分析后,会得到一个十分精确的方法来估算测试质量。

      在web应用中,为了测定某一场景的响应时间,我们一般要通过测试工具来发送多个访问请求,例如:
        登陆
        显示表单
        提交表单

      假设我们对请求3更感兴趣。为进行置信区间分析,我们需要的仅是请求3的所有样本的响应时间均值和标准偏差,而不是全部被统计的样本的。

      在Jmeter的图表结果监听器中计算的却是全部请求的响应时间均值和标准偏差。 而Jmeter的聚合报告监听器计算的是独立的采样器的响应时间均值,可惜没有计算标准偏差。
    总之, 仅仅指定响应时间均值是危险的, 因为不能反映出数据的变化。 即使响应时间均值是可以接受的,但是置信区间仅有75%,这个结果也不能令人信服。但是,使用置信区间分析还是会带来更多的确定性。

      结论

      本文讨论了以下内容:
       ·详细讲解了Jmeter 线程组在加载负载时的特别设置

       ·使用Jmeter代理服务器(Proxy Server)元件自动建立测试脚本的指导方针,其重点在于模拟用户思考时间(user think time )。

       ·置信区间分析(Confidence interval analysis), 一种我们可以用来更好地满足响应时间需求的统计分析方法

      通过使用本文提及的技术可以改善测试脚本的质量,更广泛地说,本文所讨论的内容属于是性能测试的一个工作流程的一部分, 是其中的一个较困难的部分。性能测试包括并不仅限于以下内容:

       ·编写性能测试需求
       ·选择测试情景
       ·准备测试环境
       ·编写测试脚本
       ·执行测试
       ·回顾测试脚本和测试结果 指出性能瓶颈
       ·书写测试报告

      此外, 性能测试结果,包括确定下来的瓶颈, 都需要反馈给开发团队或者架构师进行优化设计。 在这个过程中,并写测试脚本和回顾测试脚本是其中很重要的部分,要精心筹划和管理实施。凭借测试脚本指导和一个好的性能测试流程,你将会有更多的机会来在较重负载下优化软件性能。

  • JMeter 技巧集锦

    2007-11-25 19:20:01

    JMeter 是一个流行的用于负载测试的开源工具, 具有许多有用的功能元件,如线程组(thread group), 定时器(timer), 和HTTP 取样 (sampler) 元件。 本文是对JMeter 用户手册的补充,而且提供了关于使用Jmeter的一些模拟元件开发质量测试脚本的指导。

      本文同时也讨论了一项重要的内容:在指定了精确的响应时间要求后,如何来校验测试结果,特别是在采用了置信区间分析这种严格的统计方式的情况下应如何操作。请注意,我假定本文的读者们了解关于Jmeter的基础知识,本文的例子基于Jmeter2.0.3版。

      确定一个线程组的ramp-up period (Determine)

      Jmeter脚本的第一个要素是线程组(Thread Group),因此首先让我们来回顾一下。 正如图一所示,线程组需要设置以下参数:
      ·线程数量。
      ·ramp-up period。
      ·运行测试的次数。
      ·启动时间:立即或者预定的时间,如果是后者,线程组所包含的元素也要指定这个起止时间。

               图 1  JMeter 线程组(JMeter Thread Group)   

      每个线程均独立运行测试计划。因此, 线程组常用来模拟并发用户访问。如果客户机没有足够的能力来模拟较重的负载,可以使用Jmeter的分布式测试功能来通过一个Jmeter控制台来远程控制多个Jmeter引擎完成测试。

      参数 ramp-up period 用于告知JMeter 要在多长时间内建立全部的线程。默认值是0。如果未指定ramp-up period ,也就是说ramp-up period 为零, JMeter 将立即建立所有线程,假设ramp-up period 设置成T 秒, 全部线程数设置成N个, JMeter 将每隔T/N秒建立一个线程。

      线程组的大部分参数是不言自明的,只有ramp-up period有些难以理解, 因为如何设置适当的值并不容易。 首先,如果要使用大量线程的话,ramp-up period 一般不要设置成零。 因为如果设置成零,Jmeter将会在测试的开始就建立全部线程并立即发送访问请求, 这样一来就很容易使服务器饱和,更重要的是会隐性地增加了负载,这就意味着服务器将可能过载,不是因为平均访问率高而是因为所有线程的第一次并发访问而引起的不正常的初始访问峰值,可以通过Jmeter的聚合报告监听器看到这种现象。 这种异常不是我们需要的,因此,确定一个合理的ramp-up period 的规则就是让初始点击率接近平均点击率。当然,也许需要运行一些测试来确定合理访问量。

      基于同样的原因,过大的ramp-up period 也是不恰当的,因为将会降低访问峰值的负载,换句话说,在一些线程还未启动时,初期启动的部分线程可能已经结束了。   

      那么,如何检验ramp-up period I太小了或者太大了呢?首先,推测一下平均点击率并用总线程除点击率来计算初始的ramp-up period。 例如,假设线程数为100, 估计的点击率为每秒10次, 那么估计的理想ramp-up period 就是 100/10 = 10 秒。 那么,应怎样来提出一个合理的估算点击率呢?没有什么好办法,必须通过运行一次测试脚本来获得。   

      其次, 在测试计划(test plan)中增加一个聚合报告监听器,如图2所示,其中包含了所有独立的访问请求(一个samplers)的平均点击率。 第一次取样的点击率(如http请求)与ramp-up period 和线程数量密切相关。通过调整ramp-up period 可以使首次取样的奠基率接近平均取样的点击率。

                      图2 JMeter 聚合报告

      第三, 查验一下Jmeter日志(文件位置:JMeter_Home_Directory/bin) 的最后一个线程开始时第一个线程是否真正结束了,二者的时间差是否正常。

      总之,是否能确定一个适当的ramp-up time 取决于以下两条规则:
      ·第一个取样器的点击率(hit rate)是否接近其他取样器的平均值,从而能否避免ramp-up period 过小。

    --

    ·在最后一个线程启动时,第一个线程是否在真正结束了,最好二者的时间要尽可能的长,以避免ramp-up period过大。

      有时,这两条规则的结论会互相冲突。 这就意味着无法找到同时满足两条规则的合适的ramp-up period。 糟糕的测试计划通常会导致这些问题,这是因为在这样的测试计划里,取样器将不能充分地采集数据,可能因为测试计划执行时间太短并且线程会很快的运行结束。


      用户思考时间(User think time),定时器,和代理服务器(proxy server)

      在负载测试中需要考虑的的一个重要要素是思考时间(think time), 也就是在两次成功的访问请求之间的暂停时间。 有多种情形挥发导致延迟的发生: 用户需要时间阅读文字内容,或者填表, 或者查找正确的链接等。未认真考虑思考时间经常会导致测试结果的失真。例如,估计数值不恰当,也就是被测系统可以支持的最多用户量(并发用户)看起来好像要少一些等。

      Jmeter提供了一整套的计时器(timer)来模拟思考时间(think time), 但是仍旧存在一个问题:: 如何确定适当的思考时间呢?幸运的是, JMeter 提供了一个不错的答案:使用 JMeter HTTP 代理服务器(Proxy Server)元件。

      代理服务器会记录在使用一个普通的浏览器(如FireFox 或 Internet Explorer)浏览一个web应用时的操作。 另外, JMeter 在记录操作的同时会建立一个测试计划(test plan)。 这个功能能提供以下便利:

      ·不必手工建立HTTP 访问请求, 尤其是当要设置一些令人乏味的参数时(然而,非英文的参数也许不能正常工作) 。JMeter 将会录制包括隐含字段(hidden fields)在内的所有内容。

      ·在生成的测试计划中,Jmeter会包含浏览器生成的所有的 HTTP 报头,如User-Agent (e。g。, Mozilla/4。0), 或AcceptLanguage (e。g。, zh-tw,en-us;q=0。7,zh-cn;q=0。3)等。

      ·JMeter 会根据设置在录制操作的同时建立一些定时器,其延迟时间是完全根据真实的操作来设置的。

      现在让我们来看一下如何配置Jmeter的录制功能。 在JMeter 的控制台上, 在工作台(WorkBench)元件上单击右键,然后选择”add the HTTP Proxy Server “。 注意是在WorkBench 上单击右键而不是在Test Plan上, 因为现在是要为记录操作进行配置而不是要运行测试计划。 HTTP Proxy Server 的实现原理就是通过配置浏览器的代理服务器而使所有的访问请求通过JMeter发送(,因而被Jmeter把访问过程录制下来)。

      如图3所示, HTTP代理服务器(HTTP Proxy Server)元件的一些参数必须被配置:

      ·端口(port): 代理服务器的监听端口

      ·目标控制器(Target Controller): 是代理用于存储生成的数据的控制器,默认情况下,, JMeter 将会在当前的测试计划中找一个记录用的控制器用于存储,此外也可以在下拉菜单中选择任意控制起来存储,通常默认值就可以了。

      ·分组(Grouping): 确定在测试计划中如何来为生成的元件分组。 有多个选项, 一般可以选择“只存储每个组的第一个样本”,否则,将会原样录制URLs,包括包含图像和Javascrīpts脚本的页面。当然 也可以尝试一下默认值“不对样本分组”("Do not group samples"),来看一下JMeter 建立的原版的测试计划。

      ·包含模式(Patterns to Include) 和 排除模式(Patterns to Exclude) :帮助过滤一些不需要的访问请求。

               图 3  JMeter 代理服务器(Proxy Server)

      当你点击开始(Start)按钮时,代理服务器就会开始记录所接受的HTTP 访问请求。 当然,在开始记录前,要首先设置好浏览器的代理服务器设置。在代理服务器元件中可以增加一个定时器子元件(配置元件),用于告知Jmeter来在其生成的HTTP请求中自动的增加一个定时器。Jmeter会自动把实际的延迟时间存储为一个被命名为T的Jmeter变量,因此,如果在代理服务器元件里使用了高斯随机定时器,就应该在其中的固定延迟偏移(Constant Delay Offset)设置项里添上${T}(用于自动引用纪录的延迟时间),如图4所示。这是另一个节省时间的便利特性。

  • JMeter 摘抄

    2007-11-25 19:15:08

    使用 JMeter 完成常用的压力测试

    developerWorks
    文档选项
    将此页作为电子邮件发送

    将此页作为电子邮件发送



    级别: 初级

    胡 键 (jianhgreat@hotmail.com), 西安交通大学硕士

    2006 年 6 月 26 日

    本文介绍了 JMeter 相关的基本概念。并以 JMeter 为例,介绍了使用它来完成最常用的三种类型服务器,即 Web 服务器、数据库服务器和消息中间件,压力测试的方法、步骤以及注意事项。

    讲到测试,人们脑海中首先浮现的就是针对软件正确性的测试,即常说的功能测试。但是软件仅仅只是功能正确是不够的。在实际开发中,还有其它的非功能因素也起着决定性的因素,例如软件的响应速度。影响软件响应速度的因素有很多,有些是因为算法不够高效;还有些可能受用户并发数的影响。

    在众多类型的软件测试中,压力测试正是以软件响应速度为测试目标,尤其是针对在较短时间内大量并发用户的访问时,软件的抗压能力。本文以 JMeter 为例,介绍了如何使用它来完成常用的压力测试:Web 测试、数据库测试和 JMS 测试。

    概述

    JMeter 最早是为了测试 Tomcat 的前身 JServ 的执行效率而诞生的。到目前为止,它的最新版本是2.1.1,它的测试能力也不再仅仅只局限于对于Web服务器的测试,而是涵盖了数据库、JMS、Web Service、LDAP等多种对象的测试能力。在最新的 2.1.1 中,它还提供了对于 JUNIT 的测试。

    JMeter 的安装非常简单,从官方网站上下载,解压之后即可使用。运行命令在%JMETER_HOME%/bin 下,对于 Windows 用户来说,命令是 jmeter.bat。运行前请检查JMeter 的文档,查看是否具备相关的运行条件。对于最新版(即2.1.1),需要JDK的版本要求是JDK 1.4。

    JMeter 的主要测试组件总结如下:

    1. 测试计划是使用 JMeter 进行测试的起点,它是其它 JMeter 测试元件的容器。

    2. 线程组代表一定数量的并发用户,它可以用来模拟并发用户发送请求。实际的请求内容在Sampler中定义,它被线程组包含。

    3. 监听器负责收集测试结果,同时也被告知了结果显示的方式。

    4. 逻辑控制器可以自定义JMeter发送请求的行为逻辑,它与Sampler结合使用可以模拟复杂的请求序列。

    5. 断言可以用来判断请求响应的结果是否如用户所期望的。它可以用来隔离问题域,即在确保功能正确的前提下执行压力测试。这个限制对于有效的测试是非常有用的。

    6. 配置元件维护Sampler需要的配置信息,并根据实际的需要会修改请求的内容。

    7. 前置处理器和后置处理器负责在生成请求之前和之后完成工作。前置处理器常常用来修改请求的设置,后置处理器则常常用来处理响应的数据。

    8. 定时器负责定义请求之间的延迟间隔。

    JMeter的使用非常的容易,在 ONJava.com 上的文章 Using JMeter 提供了一个非常好的入门。





    回页首


    常用测试

    压力测试不同于功能测试,软件的正确性并不是它的测试重点。它所看重的是软件的执行效率,尤其是短时间内访问用户数爆炸性增长时软件的响应速度,压力测试往往是在功能测试之后进行的。在实际的开发过程中,软件潜在的效率瓶颈一般都是那些可能有多个用户同时访问的节点。

    就目前 Java EE 的平台下开发的软件来说,这种节点通常可能是:Web 服务器、数据库服务器和 JMS 服务器。它们都是请求主要发生的地点,请求频率较其它的节点要高,而且处于请求序列的关键路径之上。如果它们效率无法提高的话,对于整个软件的效率有致命的影响。而且在这些节点上一般都会发生较大规模的数据交换,有时其中还包含有业务逻辑处理,它们正是在进行压力测试时首先需要考虑的。

    本文以这三种节点为例,介绍如何使用 JMeter 来完成针对于它们的压力测试。

    Web 服务器

    对于大多数的项目来说,并不会自行开发一个Web服务器,因此Web服务器压力测试的对象实际就是--发布到Web服务器中的软件。最简单的Web测试计划只需要三个 JMeter 的测试元件,如下图:



    其中:

    • 在线程组中定义线程数、产生线程发生的时间和测试循环次数。
    • 在http请求中定义服务器、端口、协议和方法、请求路径等。
    • 表格监听器负责收集和显示结果。

    这种设置对于包含了安全机制的 web 应用是不够的,典型的 web 应用一般都会:

    1. 有一个登录页,它是整个应用的入口。当用户登录之后,应用会将用户相关的安全信息放到 session 中。

    2. 有一个 filter,它拦截请求,检查每个请求相关的 session 中是否包含有用户安全信息。如果没有,那么请求被重定向到登录页,要求用户提供安全信息。

    在这种配置下应用上面的测试计划,那么除了登录页之外的其它请求都将因为缺少用户安全信息,而使请求实际定位到登录页。如果不加断言,那么在监听器看来所有的请求都是成功。而实际上,这些请求最终都没有到达它们应该去的地方。显然,这种测试结果不是我们所期望的。

    为了成功的测试,至少有2种方法:

    • 方法一,去掉程序的安全设置,如filter,使得不需要用户安全信息也能访问受限内容;
    • 方法二,不修改程序,使用JMeter提供的"Http URL重写修饰符"或"Http Cookie管理器"。

    对于第一种方法,有其局限性:

    • 需要修改程序配置,如去掉web.xml中关于安全filter的设置。需要维护多个版本的web.xml,如压力测试和功能测试分别各自的web.xml,增加了维护成本,而且有可能会在测试之后忘记将web.xml修改回来。
    • 对于一些需要用户安全信息的页面无能为力,如某些业务审计操作需要用户安全信息来记录。因为缺少这样的信息,注定了测试的失败。如果解决为了这个问题进一步的修改程序,那么因为存在多个版本的程序,那么其维护难度将大大增加。

    虽然,第二种方法配置难度增加了,但是它不用修改程序。而且还可将测试计划保存成文件,以便重复使用。因此,选用第二种方法是较为理想的做法。下面以一个简化的例子说明使用方法二的配置步骤。

    1. 例子由以下几个文件组成:

    • AuthorizenFilter.java,过滤器负责检验session中是否存在用户信息。如果没有,那么就转向到 login.jsp。它的主要方法 doFilter 内容如下:

      public void doFilter(ServletRequest request,
                           ServletResponse response,
                           FilterChain chain)
                           throws IOException, ServletException {
          HttpServletRequest req = (HttpServletRequest)request;
          HttpServletResponse res = (HttpServletResponse)response;
          HttpSession session= req.getSession();
          User user = (User)session.getAttribute("user");
          if(null == user){
              String uri= req.getRequestURI();
              //如果请求页是登录页,不转向
              if( uri.equalsIgnoreCase("/gWeb/login.jsp")){
                  chain.doFilter(request, response);
      		} else{
                  res.sendRedirect("/gWeb/login.jsp");
      		}
      	}else{
              chain.doFilter(request, response);
          }    
      }
      

    • User.java,用户类负责记录用户的信息。为了简化,这里的登录操作只允许指定用户名和密码。主要内容如下:

      public class User {
      	private String user;
      	private String pwd;
      	public User(String user, String pwd) {
      		this.user = user;
      		this.pwd = pwd;
      	}
      	public boolean login(){
      		return user.equals("foxgem") && pwd.equals("12345678");
      	}
      	public String getUser() {
      		return user;
      	}
      	public void setUser(String user) {
      		this.user = user;
      	}
      }
      

    • Login.jsp 和welcome.jsp。其中 login.jsp 负责生成 User 对象,并调用 User 的login。当 login 返回为 true 时转向到 welcome.jsp。其验证部分的代码:

      <%
        if( request.getParameter("Submit") != null) {
      	  User ur= new User( request.getParameter("user"), request.getParameter("pwd"));
            if( ur.login()){
        	     session.setAttribute("user", ur);
               response.sendRedirect("/gWeb/welcome.jsp");
            } else{
               session.setAttribute( "LOGIN_ERROR_MSG", 
      "无效的用户,可能原因:用户不存在或被禁用。");
               response.sendRedirect("/gWeb/index.jsp");
               return;
            }
        }
      %>
      

    • web.xml,配置 filter 拦截所有访问 JSP 页面的请求:

      <filter>
          <filter-name>authorizen</filter-name>
          <filter-class>org.foxgem.jmeter.AuthorizenFilter</filter-class>
      </filter>
      <filter-mapping>
          <filter-name>authorizen</filter-name>
      	<url-pattern>*.jsp</url-pattern>
      </filter-mapping>
      

    2. 创建如下结构的Web测试计划:



    其中主要测试元件说明如下:

    • http请求默认值负责记录请求的默认值,如服务器、协议、端口等。
    • 第一个http请求,请求login.jsp,并附加验证所需要的参数(user=foxgem,pwd=12345678,Submit=Submit);其包含的响应断言验证url中包含"welcome.jsp",这一点可以从程序中反应。
    • 第二个http请求,请求是welcome.jsp;其包含的响应断言验证响应文本中包含"foxgem",它是welcome.jsp页面逻辑的一部分。
    • http cookie管理器负责管理整个测试过程中使用的cookie,它不需要设置任何属性。
    • 循环控制器设置发送第二个请求的循环次数,表格监听器负责收集和显示第二个请求的测试结果。

    启动测试计划之后,执行的顺序是:首先,第一个请求登录页进行登录;成功登录之后,使用循环控制器执行第二个请求。请求welcome.jsp时,响应断言用来验证是否确实是welocme.jsp来处理请求,而不是因为其它页。在这个测试计划中需要注意的是http cookie管理器。正是由于它的作用,使得第二个请求能顺利的发送到welcome.jsp进行处理,而不是因为缺少用户安全信息转发到login.jsp。

    在这个例子中,我们并没有在程序中使用cookie(使用的是session),那么http cookie管理器怎么会起作用呢?这是因为在servlet/jsp规范中对于session的状态跟踪有2种方式:

    • 使用cookie,保留和传递sessionid。它不要求程序对于url有什么特殊的处理,但是要求浏览器允许cookie。在这个例子中,就是这种情形。
    • 使用url重写,每次显式的在浏览器和服务器之间传递sessionid。它要求程序对url进行编码,对浏览器没有要求。

    对于第二种情形,可以使用JMeter前置管理器中的http url重写修饰符来完成。对于Tomcat,Session参数是jsessionid,路径扩展使用";"。使用url编码时需要注意,必须将浏览器的cookie功能关闭。因为url编码函数,如encodeURL,会判断是否需要将sessionid编码到url中。当浏览器允许cookie时,就不会进行编码。

    如果cookie而不是session来保存用户安全信息,那么直接使用http cookie管理器就行了。此时,需要将使用的cookie参数和值直接写到管理器中,由它负责管理。对于其它的cookie使用,也是如此操作。

    登录问题解决之后,对于 Web 服务器的测试就没什么难点了。剩下的就是根据实际需要,灵活运用相关的测试组件搭建编写的测试计划。(当然,对于安全问题还有其它的使用情景。在使用时需要明确:JMeter 是否支持,如果支持使用哪种测试组件解决。)

    数据库服务器

    数据库服务器在大多数企业项目中是不可缺少的,对于它进行压力测试是为了找出:数据库对象是否可以有效地承受来自多个用户的访问。这些对象主要是:索引、触发器、存储过程和锁。通过对于SQL语句和存储过程的测试,JMeter 可以间接的反应数据库对象是否需要优化。

    JMeter 使用 JDBC 发送请求,完成对于数据库的测试。一个数据库测试计划,建立如下结构即可:



    其中:

    • JDBC连接配置,负责配置数据库连接相关的信息。如:数据库url、数据库驱动类名、用户名和密码等等。在这些配置中,"绑定到池的变量名"(Variable Name Bound to Pool)是一个非常重要的属性,这个属性会在JDBC请求中被引用。通过它, JDBC请求和JDBC连接配置建立关联。(测试前,请将所需要的数据库驱动放到JMeter的classpath中)。
    • JDBC请求,负责发送请求进行测试。
    • 图形结果,收集显示测试结果。

    在实际的项目中,至少有2种类型的JDBC请求需要关注:select语句和存储过程。前者反应了select语句是否高效,以及表的索引等是否需要优化;后者则是反应存储过程的算法是否高效。它们如果效率低下,必然会带来响应上的不尽如人意。对于这两种请求,JDBC请求的配置略有区别:

    • Select语句





    • 存储过程





    如果对于Oracle,如果测试的是函数,那么也可以使用select语句来进行配置,此时可以使用:select 函数(入参) from dual形式的语句来测试,其中dual是oracle的关键字,表示哑表。对于其它厂商的数据库产品,请查找手册。

    JMS服务器

    MOM 作为消息数据交换的平台,也是影响应用执行效率的潜在环节。在 Java 程序中,是通过 JMS 与 MOM 进行交互的。作为 Java 实现的压力测试工具,JMeter 也能使用 JMS 对应用的消息交换和相关的数据处理能力进行测试。这一点应该不难理解,因为在整个测试过程中,JMeter 测试的重点应该是消息的产生者和消费者的本身能力,而不是 MOM本身。

    根据 JMS 规范,消息交换有2种方式:发布/订阅和点对点。JMeter针对这两种情形,分别提供了不同的Sampler进行支持。以下MOM我们使用ActiveMQ 3.2.1,分别描述这两种消息交换方式是如何使用 JMeter 进行测试。

    1. 测试前的准备(两种情况都适用)

    JMeter 虽然能使用 JMS 对 MOM 进行测试,但是它本身并没有提供JMS需要使用的包。因此,在测试之前需要将这些包复制到 %JMETER_HOME%/lib 下。对于 ActiveMQ 来说,就是复制 %ACTIVEMQ_HOME%/lib。%ACTIVEMQ_HOME%/optional 是可选包,可根据实际情况来考虑是否复制。

    JMeter 在测试时使用了 JNDI,为了提供 JNDI 提供者的信息,需要提供 jndi.properties。同时需要将 jndi.properties 放到 JMeter 的 classpath 中,建议将它与 bin下的 ApacheJMeter.jar 打包在一起。对于 ActiveMQ,jndi.properties 的示例内容如下:


    java.naming.factory.initial = org.activemq.jndi.ActiveMQInitialContextFactory
    java.naming.provider.url = tcp://localhost:61616
    
    #指定connectionFactory的jndi名字,多个名字之间可以逗号分隔。
    #以下为例:
    #对于topic,使用(TopicConnectionFactory)context.lookup("connectionFactry")
    #对于queue,(QueueConnectionFactory)context.lookup("connectionFactory")
    connectionFactoryNames = connectionFactory
    
    #注册queue,格式:
    #queue.[jndiName] = [physicalName]
    #使用时:(Queue)context.lookup("jndiName"),此处是MyQueue
    queue.MyQueue = example.MyQueue
    
    #注册topic,格式:
    # topic.[jndiName] = [physicalName]
    #使用时:(Topic)context.lookup("jndiName"),此处是MyTopic
    topic.MyTopic = example.MyTopic
    

    2. 发布/订阅

    在实际测试时,发布者和订阅者并不是需要同时出现的。例如,有时我们可能想测试单位时间内消息发布者的消息产生量,此时就不需要消息发布者,只需要订阅者就可以了。本例为了说明这两种Sampler的使用,因此建立如下的测试计划:



    其中JMS Publisher和JMS Subscriber的属性:选择"使用jndi.properties",连接工厂是connectionFactory,主题是MyTopic,其它使用默认配置。对于JMS Publisher,还需提供测试用的文本消息。

    启动ActiveMQ,运行测试计划。如果配置正确,那么与ActiveMQ成功连接之后,在JMeter的后台会打印出相关信息。在测试过程中,JMeter 后台打印可能会出现java.lang.InterruptedException 信息,这个是正常现象,不会影响测试过程和结果。这一点可以从 bin 下的 jmeter.log 看出。

    3. 点对点

    对于点对点,JMeter只提供了一种Sampler:JMS Point-to-Point。在例子中,建立如下图的测试计划:



    其中:Communication style是Request Only。对于另一种风格:Request Response,会验证收到消息的JMS Header中的JMSCorrelationID,以判断是否是对请求消息的响应。





    回页首


    结论

    本文介绍了如何使用JMeter完成最常用的三种类型服务器的压力测试,这三种类型的压力测试涵盖了很大一部分的使用情形,然而需要记住的是工具毕竟是工具。效果好不好,关键还是在于使用的人。而且,对于压力测试,测试计划的好坏是关键。针对不同的情况,分析后有针对的进行测试,比起拿枪乱打、无的放矢显然要高效得多。



    参考资料

  • apache日志的cronolog轮循(转贴)

    2007-11-04 14:37:59

    多服务器的日志合并统计——apache日志的cronolog轮循


    内容摘要:你完全不必耐心地看完下面的所有内容,因为结论无非以下2点:
    1 用 cronolog 干净,安全地轮循apache“日”志
    2 用 sort -m 合并排序多个日志

    根据个人的使用经历:
    1 先介绍apache日志的合并方法;
    2 然后根据由此引出的问题说明日志轮循的必要性和解决方法,介绍如何通过cronolog对apache日志进行轮循;
    中间有很多在设计日志合并过程中一些相关工具的使用技巧和一些尝试的失败经历……
    我相信解决以上问题的路径不止这一条途径,以下方案肯定不是最简便或者说成本最低的,希望能和大家有更多的交流。


    多服务器日志合并统计的必要性

    越来越多大型的WEB服务使用DNS轮循来实现负载均衡:使用多个同样角色的服务器做前台的WEB服务,这大大方便了服务的分布规划和扩展性,但多个服务器的分布使得日志的分析统计也变得有些麻烦。如果使用webalizer等日志分析工具对每台机器分别做日志统计:
    1 会对数据的汇总带来很多麻烦,比如:统计的总访问量需要将SERVER1 SERVER2...上指定月份的数字相加。
    2 会大大影响统计结果中唯一访客数unique visits,唯一站点数unique sites的等指标的统计,因为这几个指标并非几台机器的代数相加。

    统一日志统计所带来的好处是显而易见的,但如何把所有机器的统计合并到一个统计结果里呢?
    首先也许会想:多个服务器能不能将日志记录到同一个远程文件里呢?我们不考虑使用远程文件系统记录日志的问题,因为带来的麻烦远比你获得的方便多的多……
    因此,要统计的多个服务器的日志还是:分别记录=>并通过一定方式定期同步到后台=>合并=>后用日志分析工具来进行分析。

    首先,要说明为什么要合并日志:因为webalizer没有将同一天的多个日志合并的功能
    先后运行
    webalizer log1
    webalizer log2
    webalizer log3
    这样最后的结果是:只有log3的结果。

    能不能将log1<<log2<<log3简单叠加呢?
    因为一个日志的分析工具不是将日志一次全部读取后进行分析,而且流式的读取日志并按一定时间间隔,保存阶段性的统计结果。因此时间跨度过大(比如2条日志间隔超过5分钟),一些日志统计工具的算法就会将前面的结果“忘掉”。因此, log1<<log2<<log3直接文件连接的统计结果还是:只有log3的统计结果。

    多台服务日志合并问题:把多个日志中的记录按时间排序后合并成一个文件

    典型的多个日志文件的时间字段是这样的:
    log1 log2 log3
    00:15:00 00:14:00 00:11:00
    00:16:00 00:15:00 00:12:00
    00:17:00 00:18:00 00:13:00
    00:18:00 00:19:00 00:14:00
    14:18:00 11:19:00 10:14:00
    15:18:00 17:19:00 11:14:00
    23:18:00 23:19:00 23:14:00

    日志合并必须是按时间将多个日志的交叉合并。合并后的日志应该是:
    00:15:00 来自log1
    00:15:00 来自log2
    00:16:00 来自log1
    00:17:00 来自log3
    00:18:00 来自log2
    00:19:00 来自log1
    ....

    如何合并多个日志文件?
    下面以标准的clf格式日志(apache)为例:
    apche的日志格式是这样的:
    %h %l %u %t \"%r\" %>s %b
    具体的例子:
    111.222.111.222 - - [03/Apr/2002:10:30:17 +0800] "GET /index.html HTTP/1.1" 200 419

    最简单的想法是将日志一一读出来,然后按日志中的时间字段排序
    cat log1 log2 log3 |sort -k 4 -t " "
    注释:
    -t " ": 日志字段分割符号是空格
    -k 4: 按第4个字段排序,也就是:[03/Apr/2002:10:30:17 +0800] 这个字段
    -o log_all: 输出到log_all这个文件中

    但这样的效率比较低,要知道。如果一个服务已经需要使用负载均衡,其服务的单机日志条数往往都超过了千万级,大小在几百M,这样要同时对多个几百M的日志进行排序,机器的负载可想而之……
    其实有一个优化的途径,要知道:即使单个日志本身已经是一个“已经按照时间排好序“的文件了,而sort对于这种文件的排序合并提供了一个优化合并算法:使用 -m merge合并选项,
    因此:合并这样格式的3个日志文件log1 log2 log3并输出到log_all中比较好方法是:
    sort -m -t " " -k 4 -o log_all log1 log2 log3
    注释:
    -m: 使用 merge优化算法

    注意:合并后的日志输出最好压缩以后再发给webalizer处理
    有的系统能处理2G的文件,有的不能。有的程序能处理大于2G的文件,有的不能。尽量避免大于2G的文件,除非确认所有参与处理的程序和操作系统都能处理这样的文件。所以输出后的文件如果大于2G,最好将日志gzip后再发给webalizer处理:大于2G的文件分析过程中文件系统出错的可能性比较大,并且gzip后也能大大降低分析期间的I/O操作。

    日志的按时间排序合并就是这样实现的。

    日志的轮循机制

    让我们关心一下数据源问题:webalizer其实是一个按月统计的工具,支持增量统计:因此对于大型的服务,我可以按天将apache的日志合并后送给 webalizer统计。WEB日志是如何按天(比如每天子夜00:00:00)截断呢?
    如果你每天使用crontab:每天0点准时将日志备份成access_log_yesterday
    mv /path/to/apache/log/access_log /path/to/apache/log/access_log_yesterday
    的话:你还需要:马上运行一下:apache restart 否则:apache会因为的日志文件句柄丢失不知道将日志记录到哪里去了。这样归档每天子夜重启apache服务会受到影响。
    比较简便不影响服务的方法是:先复制,后清空
    cp /path/to/apache/log/access_log /path/to/apache/log/access_log_yesterday
    echo >/path/to/apache/log/access_log

    严肃的分析员会这样做发现一个问题:
    但cp不可能严格保证严格的0点截断。加入复制过程用了6秒,截断的access_log_yesterday日志中会出现复制过程到00:00:06期间的日志。对于单个日志统计这些每天多出来几百行日志是没有问题的。但对于多个日志在跨月的1天会有一个合并的排序问题:
    [31/Mar/2002:59:59:59 +0800]
    [31/Mar/2002:23:59:59 +0800]
    [01/Apr/2002:00:00:00 +0800]
    [01/Apr/2002:00:00:00 +0800]

    要知道[01/Apr/2002:00:00:00 这个字段是不可以进行“跨天排序”的。因为日期中使用了dd/mm/yyyy,月份还是英文名,如果按照字母排序,很有可能是这样的结果:排序导致了日志的错误
    [01/Apr/2002:00:00:00 +0800]
    [01/Apr/2002:00:00:00 +0800]
    [01/Apr/2002:00:00:00 +0800]
    [01/Apr/2002:00:00:00 +0800]
    [01/Apr/2002:00:00:00 +0800]
    [01/Apr/2002:00:00:00 +0800]
    [01/Apr/2002:00:00:00 +0800]
    [31/Mar/2002:59:59:59 +0800]
    [31/Mar/2002:59:59:59 +0800]
    [31/Mar/2002:23:59:59 +0800]
    [31/Mar/2002:59:59:59 +0800]
    [31/Mar/2002:23:59:59 +0800]

    这些跨天过程中的非正常数据对于webalizer等分析工具来说简直就好像是吃了一个臭虫一样,运行的结果是:它可能会把前一个月所有的数据都丢失!因此这样的数据会有很多风险出现在处理上月最后一天的数据的过程中。

    问题的解决有几个思路:
    1 事后处理:
    。所以一个事后的处理的方法是:用grep命令在每月第1天将日志跨月的日志去掉,比如:
    grep -v "01/Apr" access_log_04_01 > access_log_new

    修改SORT后的日志:所有跨天的数据去掉。也许对日志的事后处理是一个途径,虽然sort命令中有对日期排序的特殊选项 -M(注意是:大写M),可以让指定字段按照英文月份排序而非字母顺序,但对于apache日志来说,用SORT命令切分出月份字段很麻烦。(我尝试过用 "/"做分割符,并且使用“月份” “年:时间”这两个字段排序)。虽然用一些PERL的脚本肯定可以实现,但最终我还是放弃了。这不符合系统管理员的设计原则:通用性。并且你需要一直问自己:有没有更简单的方法呢?
    还有就是将日志格式改成用TIMESTAMP(象SQUID的日志就没有这个问题,它的日志本身就是使用TIMESTAMP做时间时间戳的),但我无法保证所有的日志工具都能识别你在日期这个字段使用了特别的格式。

    2 优化数据源:
    最好的办法还是优化数据源。将数据源保证按天轮循,同一天的日志中的数据都在同一天内。这样以后你无论使用什么工具(商业的,免费的)来分析日志,都不会因为日志复杂的预处理机制受到影响。

    首先可能会想到的是控制截取日志的时间:比如严格从0点开始截取日志,但在子夜前1分钟还是后一分钟开始截取是没有区别的,你仍然无法控制一个日志中有跨 2天记录的问题,而且你也无法预测日志归档过程使用的时间。
    因此必须要好好考虑一下使用日志轮循工具的问题,这些日志轮循工具要符合:
    1 不中断WEB服务:不能停apache=>移动日志=>重启apache
    2 保证同一天日志能够按天轮循:每天一个日志00:00:00-23:59:59
    3 不受apache重启的影响:如果apache每次重启都会生成一个新的日志是不符合要求的
    4 安装配置简单

    首先考虑了apache/bin目录下自带的一个轮循工具:rotatelogs 这个工具基本是用来按时间或按大小控制日志的,无法控制何时截断和如何按天归档。
    然后考虑logrotate后台服务:logrotate是一个专门对各种系统日志(syslogd,mail)进行轮循的后台服务,比如SYSTEM LOG,但其配置比较复杂,放弃,实际上它也是对相应服务进程发出一个-HUP重启命令来实现日志的截断归档的。

    在apache的FAQ中,推荐了经过近2年发展已经比较成熟的一个工具cronolog:安装很简单:configure=>make=> make install

    他的一个配置的例子会让你了解它有多么适合日志按天轮循:对httpd.conf做一个很小的修改就能实现:
    TransferLog "|/usr/sbin/cronolog /web/logs/%Y/%m/%d/access.log"
    ErrorLog "|/usr/sbin/cronolog /web/logs/%Y/%m/%d/errors.log"

    然后:日志将写入
    /web/logs/2002/12/31/access.log
    /web/logs/2002/12/31/errors.log
    午夜过后:日志将写入
    /web/logs/2003/01/01/access.log
    /web/logs/2003/01/01/errors.log
    而2003 2003/01 和 2003/01/01 如果不存在的话,将自动创建

    所以,只要你不在0点调整系统时间之类的话,日志应该是完全按天存放的(00:00:00-23:59:59),后面日志分析中: [31/Mar/2002:15:44:59这个字段就和日期无关了,只和时间有关。

    测试:考虑到系统硬盘容量,决定按星期轮循日志
    apache配置中加入:
    #%w weekday
    TransferLog "|/usr/sbin/cronolog /path/to/apache/logs/%w/access_log"

    重启apache后,除了原来的CustomLog /path/to/apche/logs/access_log继续增长外,系统log目录下新建立了 3/目录(测试是在周3),过了一会儿,我忽然发现2个日志的增长速度居然不一样!
    分别tail了2个日志才发现:
    我设置CustomLog使用的是combined格式,就是包含(扩展信息的),而TransferLog使用的是缺省日志格式,看了apache的手册才知道,TransferLog是用配置文件中离它自己最近的一个格式作为日志格式的。我的httpd.conf里写的是:
    LogFormat ..... combined
    LogFormat ... common
    ...
    CustomLog ... combined
    TransferLog ...

    所以TrasferLog日志用的是缺省格式,手册里说要让TRANSFER日志使用指定的格式需要:
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
    TransferLog "|/usr/local/sbin/cronolog /path/to/apache/logs/%w/access_log"

    重启,OK,日志格式一样了。
    这样的设置结果其实是同时在logs目录下分别记录2个日志access_log和%w/access_log,能不能只记录%w/下的日志那?
    查apache手册,更简单的方法:直接让CustomLog输出到cronolog归档日志,并且还能指定格式。
    CustomLog "|/usr/local/sbin/cronolog /path/to/apache/logs/%w/access_log" combined

    最后是一个日志同步的问题。

    任务:每天凌晨找到前1天的日志,另存一个文件准备发送到服务器上。
    比如我要保留前1周的日志:每天复制前1天的日志到指定目录,等待日志服务器来抓取:
    /bin/cp -f /path/to/apache/logs/`date -v-1d +%w`/access_log /path/for/backup/logs/access_log_yesterday

    在FREEBSD上使用以下命令
    date -v-1d +%w
    注释:
    -v-1d: 前1天,而在GNU/Linux上这个选项应该是date -d yesterday
    +%w: weekday,由于使用的都是标准时间函数库,所有工具中的WEEKDAY定义都是一样的 0-6 => 周日-周六

    注意:
    写到CRONTAB里的时候"%"前面需要加一个"\"转义:每天0点5分进行一次日志归档,
    另外一个问题就是在cront中需要用:rm -f {} ; 而不是rm -f {}\;
    5 0 * * * /bin/cp /path/to/logs/`date -v-1d +\%w`/access_log /path/to/for_sync/logs/access_yesterday
    37 10 * * * /usr/bin/find /home/apache/logs/ -name access_log -mtime +1 -exec /bin/rm -f {} ;

    首次开始cronolog日志统计是周3,一周以后日志又将轮循回3/access_log
    但这次日志是追加到3/access_log还是重新创建一个文件呢?>>access_log or >access_log?
    我测试的结果是日志将被追加:
    [01/Apr/2002:23:59:59 +0800]
    [01/Apr/2002:23:59:59 +0800]
    [08/Apr/2002:00:00:00 +0800]
    [08/Apr/2002:00:00:00 +0800]

    肯定是不希望每次日志还带着上周的数据的并重复统计一次的(虽然对结果没影响),而且这样%w/下的日志不是也越来越多了吗?
    解决方法1 把每天的cp改成mv
    解决方法2 每天复制完成后:删除6天以前的access_log日志
    find /path/to/apache/logs -name access_log -mtime +6 -exec rm -f {}\;
    多保留几天的日志还是有必要的:万一日志分析服务器坏了一天呢?

    以下是把apache安装在/home/apache下每天统计的一个脚本文件:
    #!/bin/sh

    #backup old log
    /bin/cp -f /home/apache/logs/`date -d yesterday +%w`/access_log /home/apache/logs/access_log_yesterday

    #remove old log
    /usr/bin/find /home/apache/logs -name access_log -mtime +6 -exec rm -f {}\;

    #analysis with webalizer
    /usr/local/sbin/webalizer

    总结:
    1 用 cronolog 干净,安全地轮循日志
    2 用 sort -m 排序合并多个日志


    参考资料:

    日志分析统计工具:
    http://directory.google.com/Top/Computers/Software/Internet/Site_Management/Log_Analysis/

    Apche的日志设置:
    http://httpd.apache.org/docs/mod/mod_log_config.html

    Apache的日志轮循:
    http://httpd.apache.org/docs/misc/FAQ.html#rotate

    Cronolog
    http://www.cronolog.org

    Webalizer
    http://www.mrunix.net/webalizer/
    Webalzer的Windows版
    http://www.medasys-lille.com/webalizer/

    AWStats的使用简介
    http://www.chedong.com/tech/awstats.html

  • apache 容量规划(转贴)

    2007-11-04 14:33:56

    Apache 1.3.x的安装与配置笔记


    内容摘要:

    Apache是一个历史悠久并且功能十分强大的WEB服务器,但其丰富的功能对于一个新手来说往往不知道从何下手。我个人感觉Apache的设计充分体现了模块化设计的优势,通过在动态模块加载(DSO)模式下的安装,任何子应用模块都可以通过配置文件的简单修改进行积木式的灵活配置。安装的过程可以从简单的静态html服务开始,一个模块一个模块的学习使用。从单纯的HTML静态服务(core),到复杂的动态页面服务(core + php, core + resin, core + php + mod_gzip, core + resin + mod_expire)。

    本文主要从简化安装==>性能调优==>维护方便的角度,介绍了WEB服务的规划、HTTPD安装/应用模块配置、升级/维护等过程。让Apache和PHP,Resin等应用模块的独立升级,完全互不影响。

    1. WEB应用容量规划:根据硬件配置和WEB应用的特点进行WEB服务的规划及一些简单的估算公式;
    2. Apache安装过程:apache的通用的简化安装选项,方便以后的应用的模块化配置;
      修改 HARD_SERVER_LIMIT:
      vi /path/to/apache_src/src/include/httpd.h
      #define HARD_SERVER_LIMIT 2560 <===将原来的 HARD_SERVER_LIMIT 256 后面加个“0”
      apache编译:
      ./configure --prefix=/home/apache --enable-shared=max --enable-module=most
    3. 可选应用模块/工具的安装:php resin mod_gzip mod_expire及各个模块之间的配合;
      mod_php安装:./configure --with-apxs=/home/apache/bin/apxs --enable-track-vars --with-mysql
      mod_resin安装:./configure --with-apxs=/home/apache/bin/apxs
      mod_gzip安装:修改Makefile中的 apxs路径:然后make make install
      工具:日志轮循工具cronolog安装:http://www.cronolog.org
    4. 升级/维护:看看通用和模块化的安装过程如何简化了日常的升级/维护工作;
      按照以上的方法:系统管理员和应用管理员的职责可以清楚的分开,互相独立。
      系统安装:系统管理员的职责就是安装好一台DSO模式的Apache,然后COLON即可,
      应用安装:由应用管理员负责具体应用所需要的模块,比如PHP Resin等,并设置httpd.conf中相关的配置。
      系统升级:系统管理员:升级操作系统/升级Apache
      应用升级:应用管理员:升级应用模块,PHP Resin等。

    WEB应用的容量规划

    Apache主要是一个内存消耗型的服务应用,我个人总结的经验公式:
    apache_max_process_with_good_perfermance < (total_hardware_memory / apache_memory_per_process ) * 2
    apache_max_process = apache_max_process_with_good_perfermance * 1.5

    为什么会有一个apache_max_process_with_good_perfermance和apache_max_process呢?原因是在低负载下系统可以使用更多的内存用于文件系统的缓存,从而进一步提高单个请求的响应速度。在高负载下,系统的单个请求响应速度会慢不少,而超过 apache_max_process,系统会因为开始使用硬盘做虚拟内存交换空间而导致系统效率急剧下降。此外,同样的服务:2G内存的机器的 apache_max_process一般只设置到1G内存的1.7倍,因为Apache本身会因为进程过多导致性能下降。

    例子1:
    一个apache + mod_php的服务器:一个apache进程一般需要4M内存
    因此在一个1G内存的机器上:apache_max_process_with_good_perfermance < (1g / 4m) * 2 = 500
    apache_max_process = 500 * 1.5 = 750
    所以规划你的应用让服务尽量跑在500个进程以下以保持比较高的效率,并设置Apache的软上限在800个。

    例子2:
    一个apache + mod_resin的服务器: 一个apache进程一般需要2M内存
    在一个2G内存的机器上:
    apache_max_process_with_good_perfermance < (2g / 2m ) * 2 = 2000
    apache_max_process = 2000 * 1.5 = 3000

    以上估算都是按小文件服务估算的(一个请求一般大小在20k以下)。对于文件下载类型站点,可能还会受其他因素:比如带宽等的影响。

    Apache安装过程

    服务器个数的硬上限HARD_SERVER_LIMIT的修改:
    在Apache的源代码中缺省的最大进程数是256个,需要修改apache_1.3.xx/src/include/httpd.h
    #ifndef HARD_SERVER_LIMIT
    #ifdef WIN32
    #define HARD_SERVER_LIMIT 1024
    #elif defined(NETWARE)
    #define HARD_SERVER_LIMIT 2048
    #else
    #define HARD_SERVER_LIMIT 2560 <===将原来的HARD_SERVER_LIMIT 256 后面加个“0”
    #endif
    #endif

    解释:
    Apache缺省的最大用户数是256个:这个配置对于服务器内存还是256M左右的时代是一个非常好的缺省设置,但随着内存成本的急剧下降,现在大型站点的服务器内存配置一般比当时要高一个数量级不止。所以256个进程的硬限制对于一台1G内存的机器来说是太浪费了,而且Apache的软上限 max_client是受限于HARD_SERVER_LIMIT的,因此如果WEB服务器内存大于256M,都应该调高Apache的 HARD_SERVER_LIMIT。根据个人的经验:2560已经可以满足大部分小于2G内存的服务器的容量规划了(Apache的软上限的规划请看后面)。

    Apache的编译:以下通用的编译选项能满足以后任意模块的安装
    ./configure --prefix=/another_driver/apache/ --enable-shared=max --enable-module=most
    比如:
    ./configure --prefix=/home/apache/ --enable-shared=max --enable-module=most

    解释:
    --prefix=/another_driver/apache/:建议将apache服务安装在另外一个驱动设备上的目的在于硬盘往往是一个系统使用寿命最低的设备,因此:将服务数据和系统完全分开,不仅能提高了数据的访问速度,更重要的,大大方便系统升级,应用备份和恢复过程。

    --shared-module=max:使用动态加载方式载入子模块会带来5%的性能下降,但和带来的配置方便相比更本不算什么:比如模块升级方便,系统升级风险降低,安装过程标准化等

    --enable-module=most:用most可以将一些不常用的module编译进来,比如后面讲到的mod_expire是就不在 apache的缺省常用模块中

    如果不想build so, 也可以这样:
    ./configure \
    "--with-layout=Apache" \
    "--prefix=/path/to/apache" \
    "--disable-module=access" \
    "--disable-module=actions" \
    "--disable-module=autoindex" \
    "--disable-module=env" \
    "--disable-module=imap" \
    "--disable-module=negotiation" \
    "--disable-module=setenvif" \
    "--disable-module=status" \
    "--disable-module=userdir" \
    "--disable-module=cgi" \
    "--disable-module=include" \
    "--disable-module=auth" \
    "--disable-module=asis"

    但结果会发现,这样编译对服务性能只能有微小的提高(5%左右),但却失去了以后系统升级和模块升级的灵活性,无论是模块还是Apache本身升级都必须把Apache和PHP的SOURCE加在一起重新编译。

    apache的缺省配置文件一般比较大:可以使用去掉注释的方法精简一下:然后再进入具体的培植过程能让你更快的定制出你所需要的。
    grep -v "#" httpd.conf.default >httpd.conf

    需要修改的通用项目有以下几个:

    #服务端口,缺省是8080,建议将整个Apache配置调整好后再将服务端口改到正式服务的端口
    Port 8080 => 80

    #服务器名:缺省没有
    ServerName name.example.com

    #最大服务进程数:根据服务容量预测设置
    MaxClients 256 => 800

    #缺省启动服务后的服务进程数:等服务比较平稳后,按平均负载下的httpd个数设置就可以
    StartServers 5 => 200

    不要修改:
    以前有建议说修改:
    MinSpareServers 5 => 100
    MaxSpareServers 10 => 200

    但从我的经验看来:缺省值已经是非常优化的了,而且让Apache自己调整子共享进程个数还是比较好的。

    特别修改:
    在solaris或一些比较容易出现内存泄露的应用上:
    MaxRequestsPerChild 0 =>3000

    应用模块和工具的安装配置:

    由于使用模块动态加载的模式,所以可以方便的通过简单的配置调整来把Apache定制成你需要的:最好把不常用模块全部清除(无论处于安全还是效率)。
    比如:对于静态页面服务器:就什么其他子模块都不加载,对于PHP应用就加上PHP模块,对于JAVA应用就把Resin模块加载上。而且各种模块的插拔非常简单,这样调试过程中就可以简单的通过注释掉不需要的模块,而不用重新编译。

    一般说来,可以不需要的模块包括:
    #LoadModule env_module libexec/mod_env.so
    #LoadModule negotiation_module libexec/mod_negotiation.so
    #LoadModule status_module libexec/mod_status.so
    #server side include已经过时了
    #LoadModule includes_module libexec/mod_include.so
    #不需要将没有缺省index文件的目录下所有文件列出
    #LoadModule autoindex_module libexec/mod_autoindex.so
    #尽量不使用CGI:一直是Apache安全问题最多的地方
    #LoadModule cgi_module libexec/mod_cgi.so
    #LoadModule asis_module libexec/mod_asis.so
    #LoadModule imap_module libexec/mod_imap.so
    #LoadModule action_module libexec/mod_actions.so
    #不使用安全认证可以大大提高访问速度
    #LoadModule access_module libexec/mod_access.so
    #LoadModule auth_module libexec/mod_auth.so
    #LoadModule setenvif_module libexec/mod_setenvif.so

    最好保留的有:
    #用于定制log格式
    LoadModule config_log_module libexec/mod_log_config.so
    #用于增加文件应用的关联
    LoadModule mime_module libexec/mod_mime.so
    #用于缺省index文件:index.php等
    LoadModule dir_module libexec/mod_dir.so

    可用可不用的有:
    #比如:需要在~/username/下调试php可以将
    LoadModule userdir_module libexec/mod_userdir.so
    #比如:需要将以前的URL进行转向或者需要使用CGI scrīpt-alias
    LoadModule alias_module libexec/mod_alias.so

    常用的模块:
    最常用的可能就是php和JAVA应用服务器的前端,此外,从性能上讲利用mod_gzip可以减少40%左右的流量,减少机器用于传输的负载,而 mod_expires可以减少10%左右的重复请求,让重复的用户对指定的页面请求结果都CACHE在本地,根本不向服务器发出请求。

    建议将所有MODULE的配置都放到相应模块的配置内部:<IfModule some_module.c>some_module config </IfModule>

    PHP的安装:
    /path/to/php_src/configure --with-apxs=/path/to/apache/bin/apxs --with-other-modules-you-need
    需要修改的配置:
    AddType application/x-httpd-php .php .php3 .any_file_in_php

    resin的安装设置:
    /path/to/resin/src/configure --with-apxs=/path/to/apache/bin/apxs

    具体的resin设置放在另外一个文件中:比如/home/resin/conf/resin.conf
    <IfModule mod_caucho.c>
    CauchoConfigFile /path/to/apache/conf/resin.conf
    </IfModule>

    mod_expires的安装配置:
    <IfModule mod_expires.c>
        ExpiresActive on
        ExpiresByType image/gif "access plus 1 month"
        ExpiresByType text/css "now plus 1 month"
        ExpiresDefault "now plus 1 day"
    </IfModule>

    注释:
    所有的.gif文件1个月以后过期
    所有的文件缺省1天以后过期

    mod_gzip的安装
    http://www.chedong.com/tech/compress.html

    日志的轮循:cronolog的安装和设置

    cronolog可以非常整齐的将日志按天轮循存储
    缺省编译安装到/usr/local/bin/下,只需要将配置改成:

    CustomLog "|/usr/local/sbin/cronolog /home/apache/logs/%w/access_log" combined

    日志将按天截断并存放在以星期为目录名的目录下:比如:log/1是周一,log/5是周五, log/0是周日

    用gzip压缩每天的日志:
    30 4 * * * /usr/bin/gzip -f /home/apache/logs/`date -d yesterday +%w`/access_log

    日志的定期删除:
    30 5 * * */usr/bin/find /home/apache/logs/ -name access_log.gz -mtime +3 |xargs -r /bin/rm -f

    升级维护

    由于使用动态模块加载方式(DSO模式)安装Apache,Apache的HTTPD核心服务和应用模块以及应用模块之间都变的非常灵活,建议将所有独立模块的配置都放在
    <IfModule mod_name>
    CONFIGURATIONS..
    </IfModule>
    里,这样配置非常容易通过屏蔽某个模块来进行功能调整:比如:
    #AddModule mod_gzip.c
    就屏蔽了mod_gzip,而其他模块不首任何影响。

    安装和维护过程:

    • 系统安装:系统管理员的职责就是安装系统和一个按照DSO模式安装的Apache,然后COLON。
    • 应用安装:由应用管理员负责具体应用所需要的模块并设置HTTPD。
    • 系统升级:系统管理员:升级系统/升级Apache
    • 应用升级:应用管理员:升级应用模块:PHP CAUCHO等
    • 系统备份/恢复:如果Apache不在缺省的系统盘上,只需要将Apache目录备份就可以了,遇到系统分区的硬件问题直接使用预先准备好的系统COLON,再直接将Apache所在物理盘恢复就行了。
    系统管理员:Apache的最简化安装 OS + Apache (httpd core only)
    应用管理员:应用模块定制 纯静态页面服务
    core
    PHP动态页面
    core+so
    +php
    JAVA应用
    core+so
    +caucho
    +ssl
    应用例子: www.example.com
    image.example.com
    bbs.example.com mall.example.com


    例子:Apache和PHP模块的独立升级。

    如果Apache是按照以下方式安装:
    ./configure --prefix=/home/apache --enable-shared=max --enable-module=most
    PHP是按照以下方式安装:
    ./configure --with-apxs=/home/apache/bin/apxs --enable-track-vars --with-mysql

    以后单独升级Apache的时候,仍然是:
    ./configure --prefix=/home/apache --enable-shared=max --enable-module=most
    make
    su
    #/home/apache/bin/apachectl stop
    #make install

    单独升级php时,仍然是:
    ./configure --with-apxs=/home/apache/bin/apxs --enable-track-vars --with-mysql
    make
    su
    #/home/apache/bin/apachectl stop
    #make install
    #/home/apache/bin/apachectl start

    基于反相代理的WEB加速:
    squid和mod_proxy都可以实现反相代理加速。而基于缓存的代理加速比起原有WEB服务,速度会有数量级的提升。

    小提示:

    Apache安装后,缺省根目录下没有但很有用的2个文件:

    • favicon.ico: favicon.ico是一个16x16的站点图标文件,如果浏览器发现有这个文件,在地址栏中会用这个图标替换调浏览器的网页图标。IE6和 MOZILLA等主流浏览器都支持这个功能。
      例如: http://www.chedong.com/favicon.ico
    • robots.txt: 用于告诉搜索引擎的爬虫程序(spider)网站那些页面可以被索引,那些不可以。
      具体说明请参考:http://www.robotstxt.org/wc/robots.html

    参考文档:

    Apache项目
    http://httpd.apache.org

    PHP
    http://www.php.net

    Resin
    http://www.caucho.com

    mod_gzip
    http://sourceforge.net/projects/mod-gzip/

    Cronolog
    http://www.cronolog.org

    mod_expires
    http://httpd.apache.org/docs/mod/mod_expires.html

    面向搜索引擎的CMS设计:
    http://www.chedong.com/tech/cms.html

  • Jmeter 摘抄

    2007-10-28 21:02:13

    zz 大部分转自 jackie的测试生活和人文生活读本---一个很好的blog

    作为一个纯 JAVA 的GUI应用,JMeter 对于CPU和内存的消耗还是很惊人的,所以当需要模拟数以千计的并发用户时,使用单台机器模拟所有的并发用户就有些力不从心,甚至还会引起JAVA内存溢出的错误。不过,JMeter 也可以像 LoadRunner 一样通过使用多台机器运行所谓的 Agent 来分担 Load Generator 自身的压力,并借此来获取更大的并发用户数。根据 JMeter官方文档的署名,你需要自己完成这个配置,不过不用担心,这将非常简单 ^_^

    1.在所有期望运行 JMeter 作为 Load Generator 的机器上安装 JMeter,并确定其中一台机器作为 Controller,其他的机器作为 Agent。然后运行所有 Agent 机器上的JMeter-server.bat文件——假定我们使用两台机器 192.168.0.1 和 192.168.0.2 作为 Agent;

    2.在Controller 机器的 JMeter 安装目录下找到 bin 目录,再找到 JMeter.properties 这个文件,使用记事本或者其他文字编辑工具打开它;

    3.在打开的文件中查找“remote_hosts=”这个字符串,你可以找到这样一行“remote_hosts=127.0.0.1”。其中的 127.0..0.1 表示运行 JMeter Agent 的机器,这里需要修改为“remote_hosts=192.168.0.1:1099,192.168.0.2:1099”——其中的 1099为端口号。一般资料显示1644 为 JMeter 的 Controller 和 Agent 之间进行通讯的默认 RMI 端口号,但是我在测试的过程中发现设置为1644运行不成功。

    在设置中还会出现一个问题,就是rmiregistry文件找不到,一般只要设置一下Java_home和JMeter_home就能成功,或者一个很野蛮的方法是可以直接将rmiregistry.exe放到%JMeter_home%bin下。

    ----

    2.4.3 Non-GUI Mode (Command Line mode)

    For non-interactive testing, you may choose to run JMeter without the GUI. To do so, use the following command options

    -n This specifies JMeter is to run in non-gui mode

    -t [name of JMX file that contains the Test Plan].

    -l [name of JTL file to log sample results to].

    -r Run all remote servers specified in JMeter.properties (or remote servers specified on command line by overriding properties)

    The scrīpt also lets you specify the optional firewall/proxy server information:

    -H [proxy server hostname or ip address]

    -P [proxy server port]

    Example : JMeter -n -t my_test.jmx -l log.jtl -H my.proxy.server -P 8000

    ----

    在 JMeter 压力测试工具中使用变量

    Apache JMeter( http://jakarta.apache.org/jmeter/ )是来自 Apache Jakarta 项目的一个压力测试工具, 目前版本2.0.3, JMeter 支持 HTTP, FTP, SOAP/XML-RPC, JDBC 等多种目标的压力测试(参见下图).



    关于 JMeter 的一般使用在它的官方主页和其它网站可以搜索到不少文章, 但是很少看到如何使用一些动态内容(比如在 HTTP 请求中使用变量作为参数)的文章, 最近因为工作需要, 在这方面做了一些摸索, 总结如下.

    0.测试项目概述

    为了尝试如何使用变量, 我们首先需要建立一个测试项目, 在这里使用了 Buffalo (一种 AJAX 技术, 详细资料参见 http://www.amowa.net/buffalo/index.html ), 或者可以看一下我下载的这个文件(att:在 JMeter 压力测试工具中使用变量.Buffalo-info.zip)中的说明和例子. 目前 Buffalo 还不是很稳定, 但是建立一个测试环境已经足够了, 而且很方便.

    我们建立的这个例子叫做 "buffalo-jmeter", 将这个压缩文件(att:在 JMeter 压力测试工具中使用变量.buffalo-jmeter.zip)中的 buffalo-jmeter.war 文件复制到 Tomcat(我用的是 Tomcat 5.0.30) 的 webapps 目录下, 待 Tomcat 自动发布完成之后就可以通过 http://localhost:8080/buffalo-jmeter/ (假设是发布在本地的Tomcat上) 访问测试页面(如下图).



    在这个例子中我们假设一个业务: 首先通过 getToken() 获得一个凭证, 然后通过这个凭证使用 getOrder() 去申请一个订单, 凭证上存在时间记录, 如果超过设定的时间(例子中是10秒)后这个凭证就失效而无法用于申请订单了(在上图中的对话框正说明了这种情形).

    1.测试中遇到的问题

    首先我们需要知道 Buffalo 其实是一种 XML-RPC 技术, 所以我们可以使用 JMeter 的 SOAP/XML-RPC Request 这个 Sampler 进行测试, 但是为了方便快捷进行测试, 以下两个问题需要解决:

    1. 如果测试服务器发生变化, 如何方便的一次性改变所有请求的 URL 地址;
    2. 如上一节所述, 10秒钟后凭证会失效, 因此我们在测试 getOrder() 的时候不能输入固定的凭证号, 应该每10秒左右获取一个新的凭证, 这样操作的难点在于如何自动让 JMeter 得到新获得的凭证号并应用到 getOrder() 请求中.

    2.静态变量(用户定义的变量)

    JMeter 允许对一个测试计划(*.jmx)设置用户定义的变量, 因此我们可以把象 URL 等需要统一修改的值作为变量定义起来(如下图);


    变量在使用时可以使用 ${变量名} 的方式引用, 如下图:



     

    3.从 Response 中获得数据

    可以使用 JMeter 提供的 后置处理器(Post Processers) --> 正则表达式提取器 (Regular Expression Extractor) 从返回的结果中取得数据, 在确定 getToken() 请求的返回值是类似下列的 XML 之后,

    <?xml version="1.0" encoding="utf-8"?>
    <burlap:reply xmlns:burlap="http://www.amowa.net/burlap/">
        <string>TK1119466440468</string>
    </burlap:reply>

    我们可以使用正则表达式 "<string>(.*)<\/string>" 来提取我们需要的凭证号.
    首先我们可以使用Javascrīpt 正则表达式测试页面来测试一下这个正则表达式是如何被执行的(如下图):
    可以看到执行结果中, 我们需要的凭证号处于"array[1]"的位置.


    这样使用"正则表达式提取器"(如下图), 注意图中的"引用名称"就可以认为是存放提取出来的数据的变量名:



    提取出来的变量可以这样被引用(如下图), 其中"_g1"代表"group number"(参见 JMeter 的联机帮助: ... [refname]_g# ... ... and # is the group number, where group 0 is the entire match, group 1 is the match from the first set of parentheses, etc.)



     

    4.测试结果分析

    我们使用3个线程同时对测试项目进行压力测试(如下图)



    通过对结果的分析我们看到了提取出来的变量确实在起作用, 而且, 这个变量是每个线程各自独立的(如下图)


     


    后记

     

     

    在 JMeter 压力测试工具中使用函数(Function)

    文章 JMeter 压力测试工具中使用变量 中谈到了可以 JMeter 中使用变量来方便进行压力测试, 此外, JMeter 还支持测试计划中使用函数, 下图是 JMeter 提供的"函数助手对话框":


    通过"函数助手对话框"中的"帮助"按钮可以查找到相关函数的帮助.



     

    实现方式

    下面使用一个例子来说明如何使用函数, 如下面的几张图所示, 例子使用了 JMeter 提供的 "Java请求" 这个 Sampler.

      • <1>这里设置了一个 Label "JavaTest001", 以便运行结果中区分当前请求
      • <2>${_ _javascrīpt((new Date()).getTime(),timestamp)}就是 JMeter 中使用函数 "_ _javascrīpt" 的方法
      • <3>这里演示了如何使用上一条中 "_ _javascrīpt" 函数中产生的结果 "timestamp"
      • <4>这里使用了另外一个 JMeter 函数 "_ _threadNum", 用以获得运行是线程编号
      • <1>这里设置了一个 Label "JavaTest002", 以便与"Java请求01"中的运行结果分开
      • <2>这里演示了如何再次使用"Java请求01"中 "_ _javascrīpt" 函数中产生的结果 "timestamp"


     

    结果分析

      • 从这个结果可以看到 "_ _javascrīpt" 函数的测试结果, 以及如何使用这个函数执行时产生的结果 "timestamp"(=1120144567828)
      • 从这个结果中可以看到另外一个函数 "_ _threadNum" 的使用效果
      • 从这个结果中可以看到"Java请求01"中 "_ _javascrīpt" 函数中产生的结果 "timestamp" 是如何被引用 "Java请求02" 中的


     

    其它

    -----

    在 LR 中是有一个“网页细分图”的,通过这个图,你可以比较容易的区分哪些请求的响应时间最长,如果响应时间过程,是消耗在server处理的时候,还是消耗在网络传输过程中——也就是所谓的 Server time 和 Network time。
    JMeter 并没有提供这么详细的区分——至少目前尚未发现,但是在 JMeter 的执行结果中也有一个字段可以利用一下。如果想看到这一项,首先要设置将 JMeter 运行结果保存到 XML 格式。

    JMeter.properties 中找到
    JMeter.save.saveservice.output_format=csv   改为
    JMeter.save.saveservice.output_format=xml

    重新启动 JMeter ,执行一个脚本并保存测试结果。
    使用任何一个文本编辑工具打开 .jtl 文件,内容如下:

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <testResults version="1.2">
     3 <httpSample t="2969" lt="1906" ts="1159349557390" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-1" dt="text" ng="5" na="5"/>
     4 <httpSample t="2797" lt="1719" ts="1159349557609" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-2" dt="text" ng="5" na="5"/>
     5 <httpSample t="2625" lt="1594" ts="1159349558015" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-4" dt="text" ng="5" na="5"/>
     6 <httpSample t="2843" lt="1812" ts="1159349557812" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-3" dt="text" ng="5" na="5"/>
     7 <httpSample t="2687" lt="1110" ts="1159349558218" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-5" dt="text" ng="5" na="5"/>
     8 <httpSample t="844" lt="391" ts="1159349560374" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-1" dt="text" ng="5" na="5"/>
     9 <httpSample t="843" lt="437" ts="1159349560406" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-2" dt="text" ng="4" na="4"/>
    10 <httpSample t="781" lt="422" ts="1159349560640" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-4" dt="text" ng="3" na="3"/>
    11 <httpSample t="782" lt="391" ts="1159349560905" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-5" dt="text" ng="2" na="2"/>
    12 <httpSample t="1188" lt="485" ts="1159349560655" s="true" lb="http://jackei.cnblogs.com/" rc="200" rm="OK" tn="Thread Group 1-3" dt="text" ng="1" na="1"/>
    13 
    14 </testResults>
    15 


    找到 lt 这一项。

    结合 JMeter 的几篇文章和 email,解释一下 lt 的意思。
    lt = latency time (ms)

    JMeter 中执行一个脚本时,大概的过程如下:

    Start timer
    Send Request
    Wait for data
    Initial (first) response packet occurs - this is latency
    more data
    ...
    end of response
    Stop timer - this is the response time


     


    这里可以看到 lt 是接收到响应的第一个包的时间。

    而上面的 XML 文件中,t 这一项表示的是 elapsed time。也就是一个请求从发出到收到完整的响应的时间。
    那么 lt 就相当于 LR 中的 Server time,而 t-lt 就相当于 LR 中的 Netwrok time。

  • apache 性能调优-2.2版中文手册(转)

    2007-10-28 12:37:39

    硬件和操作系统

    影响web服务器性能的最大的因素是内存。一个web服务器应该从不使用交换机制,因为交换产生的滞后使用户总感觉"不够快",所以用户就可能去按"停止"和"刷新",从而带来更大的负载。你可以,也应该,控制MaxClients的设置,以避免服务器产生太多的子进程而发生交换。这个过程很简单:通过top命令计算出每个Apache进程平均消耗的内存,然后再为其它进程留出足够多的内存。

    其他因素就很普通了,装一个足够快的CPU,一个足够快的网卡,几个足够快的硬盘,这里说的"足够快"是指能满足实际应用的需求。

    操作系统是很值得关注的又一个因素,已经被证实的很有用的经验有:

    • 选择能够得到的最新最稳定的版本并打好补丁。近年来,许多操作系统厂商都提供了可以显著改善性能的TCP协议栈和线程库。

    • 如果你的操作系统支持sendfile()系统调用,则务必安装带有此功能的版本或补丁(译者注:Linux2.4内核支持sendfile()系统调用,但2.6内核已经不再支持;对Solaris8的早期版本,则需要安装补丁)。在支持sendfile的系统中,Apache2可以更快地发送静态内容而且占用较少的CPU时间。

    top

    运行时的配置

    HostnameLookups 和其他DNS考虑

    在Apache1.3以前的版本中,HostnameLookups默认被设为 On 。它会带来延迟,因为对每一个请求都需要作一次DNS查询。在Apache1.3中,它被默认地设置为 Off 。如果需要日志文件提供主机名信息以生成分析报告,则可以使用日志后处理程序logresolve ,以完成DNS查询,而客户端无须等待。

    推荐你最好是在其他机器上,而不是在web服务器上执行后处理和其他日志统计操作,以免影响服务器的性能。

    如果你使用了任何"Allow from domain"或"Deny from domain"指令(也就是domain使用的是主机名而不是IP地址),则代价是要进行两次DNS查询(一次正向和一次反向,以确认没有作假)。所以,为了得到最高的性能,应该避免使用这些指令(不用域名而用IP地址也是可以的)。

    注意,可以把这些指令包含在<Location /server-status>段中使之局部化。在这种情况下,只有对这个区域的请求才会发生DNS查询。下例禁止除了.html.cgi以外的所有DNS查询:

    HostnameLookups off
    <Files ~ "\.(html|cgi)$">
    HostnameLookups on
    </Files>

    如果在某些CGI中偶尔需要DNS名称,则可以调用gethostbyname来解决。

    FollowSymLinks 和 SymLinksIfOwnerMatch

    如果网站空间中没有使用 Options FollowSymLinks ,或使用Options SymLinksIfOwnerMatch ,Apache就必须执行额外的系统调用以验证符号连接。文件名的每一个组成部分都需要一个额外的调用。例如,如果设置了:

    DocumentRoot /www/htdocs
    <Directory />
    Options SymLinksIfOwnerMatch
    </Directory>

    在请求"/index.html"时,Apache将对"/www"、"/www/htdocs"、"/www/htdocs/index.html"执行lstat()调用。而且lstat()的执行结果不被缓存,因此对每一个请求都要执行一次。如果确实需要验证符号连接的安全性,则可以这样:

    DocumentRoot /www/htdocs
    <Directory />
    Options FollowSymLinks
    </Directory>

    <Directory /www/htdocs>
    Options -FollowSymLinks +SymLinksIfOwnerMatch
    </Directory>

    这样,至少可以避免对DocumentRoot路径的多余的验证。注意,如果AliasRewriteRule中含有DocumentRoot以外的路径,那么同样需要增加这样的段。为了得到最佳性能,应当放弃对符号连接的保护,在所有地方都设置FollowSymLinks ,并放弃使用SymLinksIfOwnerMatch

    AllowOverride

    如果网站空间允许覆盖(通常是用.htaccess文件),则Apache会试图对文件名的每一个组成部分都打开.htaccess ,例如:

    DocumentRoot /www/htdocs
    <Directory />
    AllowOverride all
    </Directory>

    如果请求"/index.html",则Apache会试图打开"/.htaccess"、"/www/.htaccess"、"/www/htdocs/.htaccess"。其解决方法和前面所述的 Options FollowSymLinks 类似。为了得到最佳性能,应当对文件系统中所有的地方都使用 AllowOverride None

    内容协商

    实践中,内容协商的好处大于性能的损失,如果你很在意那一点点的性能损失,则可以禁止使用内容协商。但是仍然有个方法可以提高服务器的速度,就是不要使用通配符,如:

    DirectoryIndex index

    而使用完整的列表,如:

    DirectoryIndex index.cgi index.pl index.shtml index.html

    其中最常用的应该放在前面。

    还有,建立一个明确的type-map文件在性能上优于使用"Options MultiViews",因为所有需要的信息都在一个单独的文件中,而无须搜索目录。请参考内容协商文档以获得更详细的协商方法和创建type-map文件的指导。

    内存映射

    在Apache2.0需要搜索被发送文件的内容时,比如处理服务器端包含时,如果操作系统支持某种形式的mmap() ,则会对此文件执行内存映射。

    在某些平台上,内存映射可以提高性能,但是在某些情况下,内存映射会降低性能甚至影响到httpd的稳定性:

    • 在某些操作系统中,如果增加了CPU,mmap还不如read()迅速。比如,在多处理器的Solaris服务器上,关闭了mmap ,Apache2.0传送服务端解析文件有时候反而更快。

    • 如果你对作为NFS装载的文件系统中的一个文件进行了内存映射,而另一个NFS客户端的进程删除或者截断了这个文件,那么你的进程在下一次访问已经被映射的文件内容时,会产生一个总线错误。

    如果有上述情况发生,则应该使用 EnableMMAP off 关闭对发送文件的内存映射。注意:此指令可以被针对目录的设置覆盖。

    Sendfile

    在Apache2.0能够忽略将要被发送的文件的内容的时候(比如发送静态内容),如果操作系统支持sendfile() ,则Apache将使用内核提供的sendfile()来发送文件。译者注:Linux2.4内核支持sendfile()系统调用,但2.6内核已经不再支持。

    在大多数平台上,使用sendfile可以通过免除分离的读和写操作来提升性能。然而在某些情况下,使用sendfile会危害到httpd的稳定性

    • 一些平台可能会有Apache编译系统检测不到的有缺陷的sendfile支持,特别是将在其他平台上使用交叉编译得到的二进制文件运行于当前对sendfile支持有缺陷的平台时。

    • 对于一个挂载了NFS文件系统的内核,它可能无法可靠的通过自己的cache服务于网络文件。

    如果出现以上情况,你应当使用"EnableSendfile off"来禁用sendfile 。注意,这个指令可以被针对目录的设置覆盖。

    进程的建立

    在Apache1.3以前,MinSpareServers, MaxSpareServers, StartServers的设置对性能都有很大的影响。尤其是为了应对负载而建立足够的子进程时,Apache需要有一个"渐进"的过程。在最初建立StartServers数量的子进程后,为了满足MinSpareServers设置的需要,每一秒钟只能建立一个子进程。所以,对一个需要同时处理100个客户端的服务器,如果StartServers使用默认的设置5,则为了应对负载而建立足够多的子进程需要95秒。在实际应用中,如果不频繁重新启动服务器,这样还可以,但是如果仅仅为了提供10分钟的服务,这样就很糟糕了。

    "一秒钟一个"的规定是为了避免在创建子进程过程中服务器对请求的响应停顿,但是它对服务器性能的影响太大了,必须予以改变。在Apache1.3中,这个"一秒钟一个"的规定变得宽松了,创建一个进程,等待一秒钟,继续创建第二个,再等待一秒钟,继而创建四个,如此按指数级增加创建的进程数,最多达到每秒32个,直到满足MinSpareServers设置的值为止。

    从多数反映看来,似乎没有必要调整MinSpareServers, MaxSpareServers, StartServers 。如果每秒钟创建的进程数超过4个,则会在ErrorLog中产生一条消息,如果产生大量此消息,则可以考虑修改这些设置。可以使用mod_status的输出作为参考。

    与进程创建相关的是由MaxRequestsPerChild引发的进程的销毁。其默认值是"0",意味着每个进程所处理的请求数是不受限制的。如果此值设置得很小,比如30,则可能需要大幅增加。在SunOS或者Solaris的早期版本上,其最大值为10000以免内存泄漏。

    如果启用了持久链接,子进程将保持忙碌状态以等待被打开连接上的新请求。为了最小化其负面影响,KeepAliveTimeout的默认值被设置为5秒,以谋求网络带宽和服务器资源之间的平衡。在任何情况下此值都不应当大于60秒,参见most of the benefits are lost

    top

    编译时的配置

    选择一个MPM

    Apache 2.x 支持插入式并行处理模块,称为多路处理模块(MPM)。在编译Apache时你必须选择也只能选择一个MPM,这里有几个针对非UNIX系统的MPM:beos, mpm_netware, mpmt_os2, mpm_winnt。对类UNIX系统,有几个不同的MPM可供选择,他们都会影响到httpd的速度和可伸缩性:

    • workerMPM使用多个子进程,每个子进程中又有多个线程。每个线程处理一个请求。该MPM通常对高流量的服务器是一个不错的选择。因为它比preforkMPM需要更少的内存且更具有伸缩性。
    • preforkMPM使用多个子进程,但每个子进程并不包含多线程。每个进程只处理一个链接。在许多系统上它的速度和workerMPM一样快,但是需要更多的内存。这种无线程的设计在某些情况下优于workerMPM:它可以应用于不具备线程安全的第三方模块(比如php3/4/5),且在不支持线程调试的平台上易于调试,而且还具有比workerMPM更高的稳定性。

    关于MPM的更多内容,请参考其文档

    模块

    既然内存用量是影响性能的重要因素,你就应当尽量去除你不需要的模块。如果你将模块编译成DSO ,取消不必要的模块就是一件非常简单的事情:注释掉LoadModule指令中不需要的模块。

    如果你已经将模块静态链接进Apache二进制核心,你就必须重新编译Apache并去掉你不想要的模块。

    增减模块牵涉到的一个问题是:究竟需要哪些模块、不需要哪些模块?这取决于服务器的具体情况。一般说来,至少要包含下列模块:mod_mime, mod_dir, mod_log_config 。你也可以不要mod_log_config ,但是一般不推荐这样做。

    原子操作

    一些模块,比如mod_cacheworker使用APR(Apache可移植运行时)的原子API。这些API提供了能够用于轻量级线程同步的原子操作。

    默认情况下,APR在每个目标OS/CPU上使用其最有效的特性执行这些操作。比如许多现代CPU的指令集中有一个原子的比较交换(compare-and-swap, CAS)操作指令。在一些老式平台上,APR默认使用一种缓慢的、基于互斥执行的原子API以保持对没有CAS指令的老式CPU的兼容。如果你只打算在新式的CPU上运行Apache,你可以在编译时使用 --enable-nonportable-atomics 选项:

    ./buildconf
    ./configure --with-mpm=worker --enable-nonportable-atomics=yes

    --enable-nonportable-atomics 选项只和下列平台相关:

    • SPARC上的Solaris
      默认情况下,APR使用基于互斥执行的原子操作。如果你使用 --enable-nonportable-atomics 选项,APR将使用SPARC v8plus操作码来加快基于硬件的CAS操作。注意,这仅对UltraSPARC CPU有效。
    • x86上的Linux
      默认情况下,APR在Linux上使用基于互斥执行的原子操作。如果你使用 --enable-nonportable-atomics 选项,APR将使用486操作码来加快基于硬件的CAS操作。注意,这仅对486以上的CPU有效。

    mod_status 和 "ExtendedStatus On"

    如果Apache在编译时包含了mod_status ,而且在运行时设置了"ExtendedStatus On",那么Apache会对每个请求调用两次gettimeofday()(或者根据操作系统的不同,调用times())以及(1.3版之前)几个额外的time()调用,使状态记录带有时间标志。为了得到最佳性能,可以设置"ExtendedStatus off"(这也是默认值)。

    多socket情况下的串行accept

    警告

    这部分内容尚未完全根据Apache2.0中的变化进行更新 。一些信息依然有效,使用中请注意。

    这里要说的是 Unix socket API 的一个缺点。假设web服务器使用了多个Listen语句监听多个端口或者多个地址,Apache会使用select()以检测每个socket是否就绪。select()会表明一个socket有至少一个连接正等候处理。由于Apache的模型是多子进程的,所有空闲进程会同时检测新的连接。一个很天真的实现方法是这样的(这些例子并不是源代码,只是为了说明问题而已):

    for (;;) {
    for (;;) {
    fd_set accept_fds;

    FD_ZERO (&accept_fds);
    for (i = first_socket; i <= last_socket; ++i) {
    FD_SET (i, &accept_fds);
    }
    rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
    if (rc < 1) continue;
    new_connection = -1;
    for (i = first_socket; i <= last_socket; ++i) {
    if (FD_ISSET (i, &accept_fds)) {
    new_connection = accept (i, NULL, NULL);
    if (new_connection != -1) break;
    }
    }
    if (new_connection != -1) break;
    }
    process the new_connection;
    }

    这种天真的实现方法有一个严重的"饥饿"问题。如果多个子进程同时执行这个循环,则在多个请求之间,进程会被阻塞在select ,随即进入循环并试图accept此连接,但是只有一个进程可以成功执行(假设还有一个连接就绪),而其余的则会被阻塞accept 。这样,只有那一个socket可以处理请求,而其他都被锁住了,直到有足够多的请求将它们唤醒。此"饥饿"问题在PR#467中有专门的讲述。目前至少有两种解决方案。

    一种方案是使用非阻塞型socket ,不阻塞子进程并允许它们立即继续执行。但是这样会浪费CPU时间。设想一下,select有10个子进程,当一个请求到达的时候,其中9个被唤醒,并试图accept此连接,继而进入select循环,无所事事,并且其间没有一个子进程能够响应出现在其他socket上的请求,直到退出select循环。总之,这个方案效率并不怎么高,除非你有很多的CPU,而且开了很多子进程。

    另一种也是Apache所使用的方案是,使内层循环的入口串行化,形如(不同之处以高亮显示):

    for (;;) {
    accept_mutex_on ();
    for (;;) {
    fd_set accept_fds;

    FD_ZERO (&accept_fds);
    for (i = first_socket; i <= last_socket; ++i) {
    FD_SET (i, &accept_fds);
    }
    rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
    if (rc < 1) continue;
    new_connection = -1;
    for (i = first_socket; i <= last_socket; ++i) {
    if (FD_ISSET (i, &accept_fds)) {
    new_connection = accept (i, NULL, NULL);
    if (new_connection != -1) break;
    }
    }
    if (new_connection != -1) break;
    }
    accept_mutex_off ();
    process the new_connection;
    }

    函数accept_mutex_onaccept_mutex_off实现了一个互斥信号灯,在任何时刻只被为一个子进程所拥有。实现互斥的方法有多种,其定义位于src/conf.h(1.3以前的版本)或src/include/ap_config.h(1.3或以后的版本)中。在一些根本没有锁定机制的体系中,使用多个Listen指令就是不安全的。

    AcceptMutex指令被用来改变在运行时使用的互斥方案。

    AcceptMutex flock

    这种方法调用系统函数flock()来锁定一个加锁文件(其位置取决于LockFile指令)。

    AcceptMutex fcntl

    这种方法调用系统函数fcntl()来锁定一个加锁文件(其位置取决于LockFile指令)。

    AcceptMutex sysvsem

    (1.3及更新版本)这种方案使用SysV风格的信号灯以实现互斥。不幸的是,SysV风格的信号灯有一些副作用,其一是,Apache有可能不能在结束以前释放这种信号灯(见ipcs()的man page),另外,这种信号灯API给与网络服务器有相同uid的CGI提供了拒绝服务攻击的机会(所有CGI,除非用了类似suexeccgiwrapper)。鉴于此,在多数体系中都不用这种方法,除了IRIX(因为前两种方法在IRIX中代价太高)。

    AcceptMutex pthread

    (1.3及更新版本)这种方法使用了POSIX互斥,按理应该可以用于所有完整实现了POSIX线程规范的体系中,但是似乎只能用在Solaris2.5及更新版本中,甚至只能在某种配置下才正常运作。如果遇到这种情况,则应该提防服务器的挂起和失去响应。只提供静态内容的服务器可能不受影响。

    AcceptMutex posixsem

    (2.0及更新版本)这种方法使用了POSIX信号灯。如果一个运行中的线程占有了互斥segfault ,则信号灯的所有者将不会被恢复,从而导致服务器的挂起和失去响应。

    如果你的系统提供了上述方法以外的串行机制,那就可能需要为APR增加代码(或者提交一个补丁给Apache)。

    还有一种曾经考虑过但从未予以实施的方案是使循环部分地串行化,即只允许一定数量的进程进入循环。这种方法仅在多个进程可以同时进行的多处理器的系统中才是有价值的,而且这样的串行方法并没有占用整个带宽。它也许是将来研究的一个领域,但是由于高度并行的网络服务器并不符合规范,所以其被优先考虑的程度会比较低。

    当然,为了得到最佳性能,最后就根本不使用多个Listen语句。但是上述内容还是值得读一读。

    单socket情况下的串行accept

    上述对多socket的服务器进行了一流的讲述,那么对单socket的服务器又怎样呢?理论上似乎应该没有什么问题,因为所有进程在连接到来的时候可以由accept()阻塞,而不会产生进程"饥饿"的问题,但是在实际应用中,它掩盖了与上述非阻塞方案几乎相同的问题。按大多数TCP栈的实现方法,在单个连接到来时,内核实际上唤醒了所有阻塞在accept的进程,但只有一个能得到此连接并返回到用户空间,而其余的由于得不到连接而在内核中处于休眠状态。这种休眠状态为代码所掩盖,但的确存在,并产生与多socket中采用非阻塞方案相同的负载尖峰的浪费。

    同时,我们发现在许多体系结构中,即使在单socket的情况下,实施串行化的效果也不错,因此在几乎所有的情况下,事实上就都这样处理了。在Linux(2.0.30,双Pentium pro 166/128M RAM)下的测试显示,对单socket,串行化比不串行化每秒钟可以处理的请求少了不到3%,但是,不串行化对每一个请求多了额外的100ms的延迟,此延迟可能是因为长距离的网络线路所致,并且仅发生在LAN中。如果需要改变对单socket的串行化,可以定义SINGLE_LISTEN_UNSERIALIZED_ACCEPT ,使单socket的服务器彻底放弃串行化。

    延迟的关闭

    正如draft-ietf-http-connection-00.txt section 8所述,HTTP服务器为了可靠地实现此协议,需要单独地在每个方向上关闭通讯(重申一下,一个TCP连接是双向的,两个方向之间是独立的)。在这一点上,其他服务器经常敷衍了事,但从1.2版本开始被Apache正确实现了。

    但是增加了此功能以后,由于一些Unix版本的短见,随之也出现了许多问题。TCP规范并没有规定FIN_WAIT_2必须有一个超时,但也没有明确禁止。在没有超时的系统中,Apache1.2经常会陷于FIN_WAIT_2状态中。多数情况下,这个问题可以用供应商提供的TCP/IP补丁予以解决。而如果供应商不提供补丁(指SunOS4 -- 尽管用户们持有允许自己修补代码的许可证),那么只能关闭此功能。

    实现的方法有两种,其一是socket选项SO_LINGER ,但是似乎命中注定,大多数TCP/IP栈都从未予以正确实现。即使在正确实现的栈中(指Linux2.0.31),此方法也被证明其代价比下一种方法高昂。

    Apache对此的实现代码大多位于函数lingering_close(位于http_main.c)中。此函数大致形如:

    void lingering_close (int s)
    {
    char junk_buffer[2048];

    /* shutdown the sending side */
    shutdown (s, 1);

    signal (SIGALRM, lingering_death);
    alarm (30);

    for (;;) {
    select (s for reading, 2 second timeout);
    if (error) break;
    if (s is ready for reading) {
    if (read (s, junk_buffer, sizeof (junk_buffer)) <= 0) {
    break;
    }
    /* just toss away whatever is here */
    }
    }

    close (s);
    }

    此代码在连接结束时多了一些开销,但这是可靠实现所必须的。由于HTTP/1.1越来越流行,而且所有连接都是稳定的,此开销将由更多的请求共同分担。如果你要玩火去关闭这个功能,可以定义NO_LINGCLOSE ,但绝不推荐这样做。尤其是,随着HTTP/1.1中管道化稳定连接的启用,lingering_close已经成为绝对必须。而且,管道化连接速度更快,应该考虑予以支持。

    Scoreboard 文件

    Apache父进程和子进程通过scoreboard进行通讯。通过共享内存来实现当然是最理想的。在我们曾经实践过或者提供了完整移植的操作系统中,都使用共享内存,其余的则使用磁盘文件。磁盘文件不仅速度慢,而且不可靠(功能也少)。仔细阅读你的体系所对应的src/main/conf.h文件,并查找USE_MMAP_SCOREBOARDUSE_SHMGET_SCOREBOARD 。定义其中之一(或者分别类似HAVE_MMAP和HAVE_SHMGET),可以使共享内容的相关代码生效。如果你的系统提供其他类型的共享内容,则需要修改src/main/http_main.c文件,并把必需的挂钩添加到服务器中。(也请发送一个补丁给我们)

    注意:在对Linux的Apache1.2移植版本之前,没有使用内存共享,此失误使Apache的早期版本在Linux中表现很差。

    DYNAMIC_MODULE_LIMIT

    如果你不想使用动态加载模块(或者是因为看见了这段话,或者是为了获得最后一点点性能上的提高),可以在编译服务器时定义 -DDYNAMIC_MODULE_LIMIT=0 ,这样可以节省为支持动态加载模块而分配的内存。

    top

    附录:踪迹的详细分析

    在Solaris8的MPM中,Apache2.0.38使用一个系统调用以收集踪迹:

    truss -l -p httpd_child_pid.

    -l 参数使truss记录每个执行系统调用的LWP(lightweight process--Solaris核心级线程)的ID。

    其他系统可能使用不同的系统调用追踪工具,诸如strace, ktrace, par ,其输出都是相似的。

    下例中,一个客户端向httpd请求了一个10KB的静态文件。对非静态或内容协商请求的记录会有很大不同(有时也很难看明白)。

    /67:    accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...)
    /67:    accept(3, 0x00200BEC, 0x00200C0C, 1)            = 9

    下例中,监听线程是 LWP #67 。

    注意对accept()串行化支持的匮乏。与这个特殊平台对应的MPM在默认情况下使用非串行的accept ,除了在监听多个端口的时候。
    /65:    lwp_park(0x00000000, 0)                         = 0
    /67:    lwp_unpark(65, 1)                               = 0

    接受了一个连接后,监听线程唤醒一个工作线程以处理此请求。下例中,处理请求的那个工作线程是 LWP #65 。

    /65:    getsockname(9, 0x00200BA4, 0x00200BC4, 1)       = 0

    为了实现虚拟主机,Apache需要知道接受连接的本地socket地址。在许多情况下,有可能无须执行此调用(比如没有虚拟主机,或者Listen指令中没有使用通配地址),但是目前并没有对此作优化处理。

    /65:    brk(0x002170E8)                                 = 0
    /65:    brk(0x002190E8)                                 = 0

    brk()调用是从堆中分配内存的,它在系统调用记录中并不多见,因为httpd在多数请求处理中使用了自己的内存分配器(apr_poolapr_bucket_alloc)。下例中,httpd刚刚启动,所以它必须调用malloc()以分配原始内存块用于自己的内存分配器。

    /65:    fcntl(9, F_GETFL, 0x00000000)                   = 2
    /65:    fstat64(9, 0xFAF7B818)                          = 0
    /65:    getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B910, 2190656) = 0
    /65:    fstat64(9, 0xFAF7B818)                          = 0
    /65:    getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B914, 2190656) = 0
    /65:    setsockopt(9, 65535, 8192, 0xFAF7B918, 4, 2190656) = 0
    /65:    fcntl(9, F_SETFL, 0x00000082)                   = 0

    接着,工作线程使客户端连接处于非阻塞模式。setsockopt()getsockopt()调用是Solaris的libc对socket执行fcntl()所必须的。

    /65:    read(9, " G E T   / 1 0 k . h t m".., 8000)     = 97

    工作线程从客户端读取请求。

    /65:    stat("/var/httpd/apache/httpd-8999/htdocs/10k.html", 0xFAF7B978) = 0
    /65:    open("/var/httpd/apache/httpd-8999/htdocs/10k.html", O_RDONLY) = 10

    这里,httpd被配置为"Options FollowSymLinks"和"AllowOverride None"。所以,无须对每个被请求文件路径中的目录执行lstat(),也不需要检查.htaccess文件,它简单地调用stat()以检查此文件是否存在,以及是一个普通的文件还是一个目录。

    /65:    sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C)      = 10269

    此例中,httpd可以通过单个系统调用sendfilev()发送HTTP响应头和被请求的文件。Sendfile因操作系统会有所不同,有些系统中,在调用sendfile()以前,需要调用write()writev()以发送响应头。

    /65:    write(4, " 1 2 7 . 0 . 0 . 1   -  ".., 78)      = 78

    write()调用在访问日志中对请求作了记录。注意,其中没有对time()的调用的记录。与Apache1.3不同,Apache2.0使用gettimeofday()以查询时间。在有些操作系统中,比如Linux和Solaris,gettimeofday有一个优化的版本,其开销比一个普通的系统调用要小一点。

    /65:    shutdown(9, 1, 1)                               = 0
    /65:    poll(0xFAF7B980, 1, 2000)                       = 1
    /65:    read(9, 0xFAF7BC20, 512)                        = 0
    /65:    close(9)                                        = 0

    工作线程对连接作延迟的关闭。

    /65:    close(10)                                       = 0
    /65:    lwp_park(0x00000000, 0)         (sleeping...)

    最后,工作线程关闭发送完的文件和块,直到监听进程把它指派给另一个连接。

    /67:    accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)

    其间,监听进程可以在把一个连接指派给一个工作进程后立即接受另一个连接(但是如果所有工作进程都处于忙碌状态,则会受MPM中的一些溢出控制逻辑的制约)。虽然在此例中并不明显,在工作线程刚接受了一个连接之后,下一个accept()会(在高负荷的情况下更会)立即并行产生。

  • 还是碎片

    2007-10-27 13:42:49

    事务是在脚本中定义的某个操作,而点击是在测试中产生的http请求。

    例如,我定义了一个提交form的事务,我关心的也就是这个提交操作的数量与分值及响应时间的关系。而实际上这个form提交可能产生多个http请求。首先提交form本身有一次http请求,如果此请求被服务器端接受,则要转向到结果页面的第一个页面,又是一次http请求,如果这个页面中含有图片的话,那么每个图片都需要通过一个http连接来下载。

    所以:

    平均每个事务产生的点击数

     = 事务的数量+事务的数量×事务成功概率+事务的数量×事务成功概率×平均每个页面中含有的图片数

     = 事务的数量×(1+平均事务成功概率×(1+平均每个页面含有的图片数))

    ---

    tcpdump host -w sms.cap 218.80.253.xxa and \(218.80.253.xxb\) -i eth1 -s 0

    ---

    exp gdtoa/buddygdtoa full=y file=gdtoa.dmp
    scp *.dmp useraccount@xx.xx.xx.xx:/home/buddyv20

    ---

    ALTER SESSION SET nls_date_format = 'YYYY-MM-DD hh24:mi:ss';

    ---

     

     

     

  • 批量修改配置文件

    2007-10-19 12:12:13

    #file:db_config_v1.2.sh
    #author:sujh
    #create time:2007-08-17 11:56
    #describe:N/A
    #check the option
    while getopts ":hp:" opt;
    do
        case $opt in
            h)
                printf "USAGE:\n sh $0 -p WORKPATH\n"
                exit 0
                ;;
            p)
                workPath=$OPTARG
                printf "Get the work-path:"${workPath}"\n"
                ;;
            :)
                printf ">> Error: '-$OPTARG' requires an argument \n please use: sh $0 -h \n"
                exit 1
                ;;
            ?)
                printf ">> Error: '-$OPTARG' not supported \n please use: sh $0 -h\n"
                exit 1
                ;;
        esac
    done
    #printf $OPTIND

    if [ "$OPTIND" = "1" ];then
        printf "Error:Option '-p WORKPATH' is need\n"
        exit 1
    fi

    #shield interregnum
    stty susp ^@#$
    stty intr ^@$#
    stty quit ^*#$

    matchStr="<value>jdbc:oracle:thin:@.*<\/value>";

    dbconf="<value>jdbc:oracle:thin:@10.17.42.111:1521:bnet</value>";

    if cd ${workPath}; then
        localPath=`pwd`
        printf "Success to open WORKPATH:"$localPath"\n" 
        bakDir="BAKDIR"`date +%s`
        mkdir ${localPath}"/"${bakDir}
        printf "Created backup directory:"${bakDir}"\n"
        cp ./* ${bakDir}
        printf "Backup file complete\n"
        find $localPath -maxdepth 1 -type f -exec basename {} \;|xargs -i awk '{
            if(match($0,/'$matchStr'/))
                gsub(/'$matchStr'/,"'$dbconf'",$0);
            print "echo \""$0"\" >> {}.new"
        }' {} |sh
        printf "Update filename\n"
        find $localPath -maxdepth 1 -type f -

    name "*.new"|awk '{print "mv "$0" " substr($0,0,length($0)-4)}'|sh
        printf "REPLACE COMPLETE\n"
    else
        printf "Open WORKPATH(${wordPath}) fail\n"
    fi
    #resume interregnum
    stty susp ^Z
    stty intr ^C
    stty quit ^\\
     

  • 自动挂载_fstab

    2007-10-07 20:35:30

    如果想开机就自动挂载(mount)上,可以在/etc/fstab上添加如下几行:
    /dev/hda5 /mnt/d vfat exec,dev,suid,rw,umask=0,iocharset=gb2312,codepage=936 0 0
    同时也解决了中文文件名的乱码问题,我的fstab文件如下,仅供参考:
    LABEL=/12 / ext3 defaults 1 1
    none /dev/pts devpts gid=5,mode=620 0 0
    none /proc proc defaults 0 0
    none /dev/shm tmpfs defaults 0 0
    /dev/hda4 swap swap defaults 0 0
    /dev/cdrom /mnt/cdrom iso9660 noauto,owner,kudzu,ro 0 0
    /dev/fd0 /mnt/floppy auto noauto,owner,kudzu 0 0
    /dev/hda1 /mnt/c vfat exec,dev,suid,rw,umask=0,iocharset=gb2312,codepage=936 0 0
    /dev/hda5 /mnt/d vfat exec,dev,suid,rw,umask=0,iocharset=gb2312,codepage=936 0 0
    /dev/hda6 /mnt/e vfat exec,dev,suid,rw,umask=0,iocharset=gb2312,codepage=936 0 0
    /dev/hda7 /mnt/f vfat exec,dev,suid,rw,umask=0,iocharset=gb2312,codepage=936 0 0
    /dev/hda8 /mnt/g vfat exec,dev,suid,rw,umask=0,iocharset=gb2312,codepage=936 0 0

    fstab解读

    从做到右:
    /dev/device mount-point type rules dump fsck
    1. /dev/device: 不用说了吧?例如,/dev/hda1 为M$-Win9x下的c:盘。
    2. mount-point: 挂载点。例如,把/dev/hda1挂到/mnt/mywinc下。
    3. type: ext3, vfat, ......就是要挂上的文件系统类型。
    4. rules:
    auto: 开机自动挂接;
    default, noauto: 开机不自动挂接;
    nouser: 只有root可挂;
    ro: 只读挂接;
    rw: 可读可写挂接;
    user: 任何用户都可以挂接;
    5. dump: 备份;0为从不备份,或显示上次至今备份之天数;
    6. fsck: 启动时fsck检查顺序,0为不检查, “/”永远为1;
    我的就是:
    /dev/hda1 /mnt/mywinc vfat default 0 0

    在fstab中加载分区

    mkdir /mnt/win_c
    vi /etc/fstab
    :::::add:::::
    /dev/hda1 /mnt/win_c vfat default,pagecode=936,iocharset=cp936 0 0

    /etc/fastab释疑

    1. fstab文件的作用
    文件/etc/fstab存放的是系统中的文件系统信息。当正确的设置了该文件,则可以通过"mount /directoryname"命令来加载一个文件系统,每种文件系统都对应一个独立的行,每行中的字段都有空格或tab键分开。同时fsck、mount、umount的等命令都利用该程序。
    2. fstab文件格式
    下面是/etc/fatab文件的一个示例行:
    fs_spec fs_file fs_type fs_options fs_dump fs_pass
    /dev/hda1 / ext2 defaults 1 1
    fs_spec - 该字段定义希望加载的文件系统所在的设备或远程文件系统,对于一般的本地块设备情况来说:IDE设备一般描述为/dev/hdaXN,X是IDE设备通道(a, b, or c),N代表分区号;SCSI设备一描述为/dev/sdaXN。对于NFS情况,格式一般为<host>:<dir>,例如:`knuth.aeb.nl:/'。对于procfs,使用`proc'来定义。
    fs_file - 该字段描述希望的文件系统加载的目录点,对于swap设备,该字段为none;对于加载目录名包含空格的情况,用40来表示空格。
    fs_type - 定义了该设备上的文件系统,一般常见的文件类型为ext2 (linux设备的常用文件类型)、vfat(Windows系统的fat32格式)、NTFS、iso9600等。
    fs_options - 指定加载该设备的文件系统是需要使用的特定参数选项,多个参数是由逗号分隔开来。对于大多数系统使用"defaults"就可以满足需要。其他常见的选项包括:
    选项 含义
    ro 以只读模式加载该文件系统
    sync 不对该设备的写操作进行缓冲处理,这可以防止在非正常关机时情况下破坏文件系统,但是却降低了计算机速度
    user 允许普通用户加载该文件系统
    quota 强制在该文件系统上进行磁盘定额限制
    noauto 不再使用mount -a命令(例如系统启动时)加载该文件系统
    fs_dump - 该选项被"dump"命令使用来检查一个文件系统应该以多快频率进行转储,若不需要转储就设置该字段为0
    fs_pass - 该字段被fsck命令用来决定在启动时需要被扫描的文件系统的顺序,根文件系统"/"对应该字段的值应该为1,其他文件系统应该为2。若该文件系统无需在启动时扫描则设置该字段为0
    3. 示例文件
    # /etc/fstab
    /dev/hda9 swap swap defaults 0 0
    /dev/hda1 / ext2 defaults 1 1
    /dev/hda5 /home ext2 defaults 1 1
    /dev/hda6 /usr ext2 defaults 1 1
    /dev/hda7 /usr/local ext2 defaults 1 1
    /dev/hda8 /var ext2 defaults 1 1
    /dev/hdb /cdrom iso9660 noauto,user 0 0
    none /proc proc defaults 0 0
    none /dev/pts devpts gid=5,mode=620 0 0

  • shell新学

    2007-10-01 21:42:05

    备用:

    内容:SHELL的最新技术内容
    1。
    ln命令最初的设想是实现定时备份,所以不使用任何参数的链接,修改一个另外一个会变化,删除一个另外一个不会被删除。
    ln -s 命令最初是用来实现跨文件系统的链接,所以符号链接在源文件删除后,符号链接就不能再使用了

    2。
    ${filename}X 可以避免 $filenameX 理解为一个变量,当然也可以使用 $filename'X'来避免

    3。
    算数扩展格式:$(( a=a+1 )),全部采用C语言的语法
    格式: $(( )) 不能有空格,但是双括号内的空格可以随便,双括号内的变量不用 $ 符号,这是最新标准,可能有些老的shell不支持

    4。
    单引号和双引号的区别
    '*' 和 * 相同,但是"*" 不再代表所有内容,而代表一个星号
    '$var'不会解释变量,认为就是字符串$var, $var 里面如果有回车,它会自动去掉,但是 "$var" 中如果有回车就不会去掉
    echo a b    c     d  返回会将多个空格缩成一个空格; echo 'a b    c     d' 而返回会保留空格

    5。
    匹配精确数目的字符串:a\{4,7\} ,意思是匹配4 到7 个 a

    6。
    保存匹配的字符串:^\(.\).*\1$ 它的意思是\(.\)将开头的第一个字符取出来,放进寄存器\1中,这样整句话就是说匹配第一个字符和最后一个字符相同的串

    7。
    反斜杠用来续行,例如:
    $lines=one\
    two

    8。
    命令替换:可以不使用`pwd`,改用$(pwd)
    优点:
    a。不容易和单引号混淆
    b。嵌套时比较好看

    9。
    如果程序需要传递9个以上的参数,不能用$10,$11,而需要写成${n}

    10。
    test $name = lanh 和 test "$name" = lanh 的区别,如果$name 不为空的情况下是一样的,如果它为空,那么推荐后者,因为此时前者就相当于输入了:test = lanh 错误提示:argument expected,而后者还能够正常判断

    但有时这个特性可作特定用途,例如:
    test $name = lanh , 而 $name 等于 'lanh    '有空格,如果使用"$name", 那就不相等,因为有空格,但不用双引号就会相等,因为它自动吃掉了空格。不过个人觉得还不如写 :test "$name" = "lanh        ",明确有空格

    11。
    在条件判断方括号中,小括号需要转义符,例如:[ \( "$count" -ge 0 \) -a \( "$count" -lt 10 \) ]

    12。
    sh -x shell.sh para , 可以打印出很多调试信息

    13.
    export -p 导出的所有变量的清单

    14。
    . file 相当于 sh file , 这样可以保留住 file 中定义变量的值,否则如果./file ,这只是在一个子shell中执行了这个程序,修改的变量的值存放到了子shell中

    15。
    (x=100) 和 {x=100}的不同,前者是在子shell中运行的,后者在当前shell中运行。故前者执行后在当前的shell中并没有x 变量,而后者执行后有 x

    16。可以在shell 命令提示符下直接执行
    估计没有设置就是没有定义,也就是没有:var= ,为空就是var='',自己猜的
    ${parameter:-value} : 如果 parameter 为空,则用value替代,不为空则不替代,但是并不给变量赋值
    ${parameter-value} : 如果 parameter 没有设置,则用value替代,不为空则不替代,但是并不给变量赋值
    ${parameter:=value} : 如果 parameter 为空,则用value替代,不为空则不替代,并且给变量赋值
    ${parameter=value} : 如果 parameter 没有设置,则用value替代,不为空则不替代,并且给变量赋值
    ${parameter:?value} : 如果 parameter 为空,写入标准错误,不为空就替代它的值
    ${parameter?value} : 如果 parameter 没有设置,写入标准错误,不为空就替代它的值
    可以用来检查变量是否为空
    ${parameter:+value} : 如果 parameter 为空,什么都不做,如果不为空,就替代它的值
    ${parameter+value} : 如果 parameter 没有设置,什么都不做,如果不为空,就替代它的值


    17。都不修改变量
    ${variable%pattern} : 看是否以给定的模式结尾,如果是,就从右边去掉最短的匹配模式(非贪婪匹配,perl中使用问号)
    ${variable%%pattern} : 看是否以给定的模式结尾,如果是,就从右边去掉最长的匹配模式(贪婪匹配)
    ${variable#pattern} : 看是否以给定的模式开头,如果是,就从左边去掉最短的匹配模式(非贪婪匹配)
    ${variable##pattern} : 看是否以给定的模式开头,如果是,就从左边去掉最长的匹配模式(贪婪匹配)


    例如:检查一个变量是否以某个字符串结束
    if [ ${file%.o} != $file ]   判断变量file的值是否以.o结束

    18。得到文件名
    a. echo '/export/home/uptel/neva2p' |awk -F/ '{print $NF}'
    b. echo '/export/home/uptel/neva2p' |echo ${1##*/} 没有试验成功

    19。设置shell跟踪模式
    a. sh -x ctype a
    b. x=* ; set -x

    20。
    set -- -5 + 1 = 4  让5前的减号不被解释成参数开头

    21。
    IFS=:
    影响read,read x y z ; 123:3:8

    22。得到八进制的值
    echo "$IFS" | od -b

    23。eval 两遍扫描
    pipe="|" ; ls $pipe wc -l  出错:提示没有文件 | , wc , -l
    此时需要用 pipe="|" ; eval ls $pipe wc -l

    24。
    wait : 等待当前作业完成
    wait 3243 :加上进程ID
    $! 代表最后一个送到后台的进程标识
    例如:
    prog1 &
    pid1=$!
    prog2 &
    pid2=$!

    wait &pid1

    wait &pid2

    25。
    trap commands signals : 代表什么信号发生,就执行命令
    如果执行空命令,就是忽略掉信号,trap "" 2
    如果恢复缺省,trap 2

    26。
    cat <<-END : 输入遇到END 就结束,-代表去掉行首缩进

    27。
    exit 会退出整个shell
    return 只退出函数

    28。
    行首指定命令解释:
    #!/usr/bin/bash
    #!/usr/bin/ksh

    29。
    bash 和 korn shell 支持算术表达式,但无美元符号,且不能变量赋值或冒号操作符

    30。
    数组:
    ${array[i]}  : 元素i的值
    $array : 第一个元素的值
    ${array[*]} : 替换所有元素的值
    ${#array[*]} : 元素的个数
    array[i]=val ; 赋值

    31。
    jobs   :显示所有任务
    kill %1 :删除任务

    32。
    - : 回到上一目录
    ~ : 回到$HOME
    ~user : 到user 用户的目录

  • mocktest

    2007-09-16 20:22:55

    mocktest

    src/junitbook.fine.tasting

    Account.java

    AccountManager.java

    AccountService.java

    MockAccountManager.java

    testAccountService.java

    --Account.java

    package junitbook.fine.tasting;
    public class Account{
     private long balance;
     public Account(String accountId,long initialBalance){
      this.balance = initialBalance;
      System.out.println("-------Account balance initialBalance:-----"+balance);
     }
     public void debit(long amount){
      this.balance -= amount;
      System.out.println("-------debit balance-amount:------"+balance);
     }
     public void credit(long amount){
      this.balance +=amount;
      System.out.println("-------credit balance+amount:------"+balance);
     }
     public long getBalance(){
      return this.balance;
     }
    }

    -----AccountManager.java

    package junitbook.fine.tasting;

    public interface AccountManager{
     Account findAccountForUser(String userId);
     void updateAccount(Account account);
     }

    -----AccountService.java

    package junitbook.fine.tasting;
    public class AccountService{
     private AccountManager accountManager;
     
     private long amount;
     
     public void setAccountManager(MockAccountManager mockAccountManager, AccountManager manager){
      this.accountManager = manager;
     }
     
     public void transfer(String senderId,String beneficiaryId,long account){
      Account sender = this.accountManager.findAccountForUser(senderId);
      Account beneficiary =this.accountManager.findAccountForUser(beneficiaryId);
      sender.debit(amount);
      beneficiary.credit(amount);
      this.accountManager.updateAccount(sender);
      this.accountManager.updateAccount(beneficiary);
     }
    }

    ----MockAccountManager.java

    package junitbook.fine.tasting;
    import java.util.Hashtable;
    public class MockAccountManager implements AccountManager{
     private Hashtable accounts = new Hashtable();
     
     public void addAccount(String userId,Account account){
     this.accounts.put(userId,account);
     }
     
     public Account findAccountForUser(String userId){
      return (Account)this.accounts.get(userId);
     }
     
     public void updateAccount(Account account){
     }

    }

    ----testAccountService.java

    package junitbook.fine.tasting;
    import junit.framework.*;
    public class testAccountService extends TestCase{

     public void testTransferOk(){
      MockAccountManager mockAccountManager = new MockAccountManager();
      Account senderAccount = new Account("1",200);
      System.out.println("-------senderAccount:------"+senderAccount);
      Account beneficiaryAccount = new Account("2",100);
      System.out.println("-------senderAccount:-----"+beneficiaryAccount);
      mockAccountManager.addAccount("1",senderAccount);
      mockAccountManager.addAccount("2",beneficiaryAccount);
      System.out.println("-------mockAccountManager:------"+mockAccountManager);
        
      AccountService accountService = new AccountService();
      System.out.println("-------accountService1:------"+accountService);
      accountService.setAccountManager(mockAccountManager, mockAccountManager);
      accountService.transfer("1","2",50);
      System.out.println("-------accountService2:-------"+accountService);
      
      assertEquals(200,senderAccount.getBalance());
      assertEquals(100,beneficiaryAccount.getBalance());
     }

    }

     

     

  • stubtest_jetty

    2007-09-05 23:23:38

    junitbook.coarse

    1、JettySample.java:

    package junitbook.coarse;
    import org.mortbay.http.HttpContext;
    import org.mortbay.http.HttpServer;
    import org.mortbay.http.SocketListener;
    import org.mortbay.http.handler.ResourceHandler;

    public class JettySample{
     public static void main(String[] args) throws Exception
     {
      HttpServer server = new HttpServer();
      SocketListener listener = new SocketListener();
      listener.setPort(8080);
      server.addListener(listener);
      HttpContext context = new HttpContext();
      context.setContextPath("/");
      context.setResourceBase("./");
      context.addHandler(new ResourceHandler());
      server.addContext(context);
      server.start();
     }
    }

    ---

    junitbook.coarse.try1

    1、TestWebClient1.java:

    package junitbook.coarse.try1;

    import java.net.*;
    import junit.framework.Test;
    import junit.framework.TestCase;
    import junit.framework.TestSuite;


    public class TestWebClient1 extends TestCase
    {
     public static Test suite()
     {
      TestSuite suite = new TestSuite();
      suite.addTestSuite(TestWebClient1.class);
      return new TestWebClientSetup1(suite);
     }
     public void testGetContentOk() throws Exception
     {
      WebClient client = new WebClient();
      String result = client.getContent(new URL("http://localhost:8080/testGetContentOk"));
      assertEquals("It works",result);
     }
     public void testGetContentNotFound()throws Exception
     {
      WebClient client = new WebClient();
      String result = client.getContent(new URL("http://localhost:8080/testGetContentNotFound"));
      assertNull(result);
     }
    }

    2、TestWebClientSetup1.java:

    package junitbook.coarse.try1;

    import java.applet.*;
    import java.io.*;
    import java.rmi.server.*;
    import org.mortbay.http.*;
    import org.mortbay.http.handler.AbstractHttpHandler;
    import org.mortbay.http.handler.ResourceHandler;
    import org.mortbay.util.ByteArrayISO8859Writer;

    import junit.extensions.*;
    import junit.framework.*;


    public class TestWebClientSetup1 extends TestSetup{
     protected static HttpServer server;
     public TestWebClientSetup1(Test suite)
     {
      super(suite);
     }
     protected void setUp() throws Exception
     {
      HttpServer server = new HttpServer();
      SocketListener listener = new SocketListener();
      listener.setPort(8080);
      server.addListener(listener);

      HttpContext context1 = new HttpContext();
      context1.setContextPath("/");
      context1.setResourceBase("./");
      context1.addHandler(new ResourceHandler());
      server.addContext(context1);
      
      HttpContext context2 = new HttpContext();
      context2.setContextPath("/");
      context2.setResourceBase("./");
      context2.addHandler(new ResourceHandler());
      server.addContext(context2);
      
      server.start(); 
     }
     protected void tearDown() throws Exception
     {
      server.stop();
     }
    // public void testGetContent() throws Exception
    // {
    //  WebClient client = new WebClient();
    //  String result = client.getContent(new URL("http://localhost:8080/testGetContentOk"));
    //  assertEquals("It works",result);
    // }
     private class TestGetContentOkHandler extends AbstractHttpHandler
     {
      public void handle(String pathInContext,String pathParams,HttpRequest request,HttpResponse response) throws IOException{
       OutputStream ōut = response.getOutputStream();
       ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer();
       writer.write("It works");
       writer.flush();
       response.setIntField(HttpFields.__ContentLength,writer.size());
       writer.writeTo(out);
       out.flush();
       request.setHandled(true);
      }
     }
     public abstract class TestGetContentServerErrorHandler extends AbstractHttpHandler{
      public void handler(String pathInContext,String pathParams,HttpRequest request,HttpResponse response) throws IOException{
       response.sendError(HttpResponse.__503_Service_Unavailable);
      }
     }
    }

     

  • md5加密程序

    2007-08-28 17:22:08

    create project:md5

    jdk:1.4.2

    create package:src/com.eshore.eca.util

    hashcode.java and MD5Crypter.java

    create webroot:WebRoot/WEB-INF/web.xml (null)

    1、hashcode.java:

    package com.eshore.eca.util;

    public class hashcode{
     
     public String getHashCode(String hashCodeString){
      String inputValue = null;
      if (hashCodeString != null) {
       //System.out.println(hashCodeString);
       inputValue = MD5Crypter.encode(hashCodeString);
       System.out.println("gethashCodeValue:"+inputValue);
      }
      return hashCodeString;
     }
    public static void main(String[] args){
     String inputValue = "1TEST3TEST3075576510001020310072772147483647123456789012345678123456aB";
     System.out.println("hashCodeValue:"+inputValue);
     hashcode mySend = new hashcode();
     mySend.getHashCode(inputValue);
     }
    }

    2、MD5Crypter.java

    package com.eshore.eca.util;
    import java.security.MessageDigest;
    import java.security.Security;
    import cryptix.util.core.Hex;

    public class MD5Crypter {

        private static boolean isInit = false;

        public MD5Crypter() {
        }

        public static String encode(String originalString) {
            if (originalString == null)
                return null;
            if (!isInit)
                init();
            try {
                MessageDigest messagedigest = MessageDigest.getInstance("MD5");
                messagedigest.reset();
                messagedigest.update(originalString.getBytes("utf8"));
    //            for(int i = 0; i < originalString.length(); i++)
    //                messagedigest.update((byte)originalString.charAt(i));
                byte abyte0[] = messagedigest.digest();
                return Hex.toString(abyte0);
            }
            catch (Exception exception) {
                System.err.println(exception.getMessage());
                return null;
            }

        }

        public static byte[] encodeByte(String originalString) {
            if (originalString == null)
                return null;
            if (!isInit)
                init();
            try {
                MessageDigest messagedigest = MessageDigest.getInstance("MD5");
                messagedigest.reset();
                messagedigest.update(originalString.getBytes("utf8"));
    //            for(int i = 0; i < originalString.length(); i++)
    //                messagedigest.update((byte)originalString.charAt(i));
                byte abyte0[] = messagedigest.digest();
                return abyte0;
            }
            catch (Exception exception) {
                System.err.println(exception.getMessage());
                return null;
            }

        }

        private static void init() {
    //        Security.addProvider(new Cryptix());
            isInit = true;
        }

     

  • jsp/servlet/javaBean 小程序

    2007-08-28 16:21:26

    create a tomcat project:test

    package:WEB-INF/src/test

    test.java and TestBean.java

    1、test.java:

    package test;
    import java.io.IOException;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    public class test extends HttpServlet{

     protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
      // TODO Auto-generated method stub
      System.out.println("asdfasdfasdf");
      //arg1.sendRedirect("index.jsp");
      arg1.sendRedirect("TestBean.jsp");
     }

     protected void doPost(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
      // TODO Auto-generated method stub
      System.out.println("asdfasdfasdf");
      //arg1.sendRedirect("index.jsp");
      arg1.sendRedirect("TestBean.jsp");
     }

    }

    2、TestBean.java:

    package test;
    public class TestBean{
      private String name = null;
      public TestBean(String strName_p){
       this.name=strName_p;
      }
    public String setName(String strName_p){
     return this.name=strName_p;
    }
    public String getName(){
     return this.name;
            }
    }

    project test 下:index.jsp and TestBean.jsp

    3、index.jsp

    <html>
    <body>
    <center>
    now time is :<%=new java.util.Date()%>
    </center>
    </body>
    </html>

    4、TestBean.jsp

    <%@ page import = "test.TestBean"%>
    <html>
    <body>
    <center>
    <%
     TestBean myBean = new TestBean("This is java bean");
    %>
    this is java bean name :<%=myBean.getName()%>
    </center>
    </body>
    </html>

    5、WEB-INF/web.xml

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <web-app xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
        version="2.4">
      <display-name>Welcome to Tomcat</display-name>
      <descrīption>
         Welcome to Tomcat
      </descrīption>
      <servlet>
            <servlet-name>test</servlet-name>
            <servlet-class>test.test</servlet-class>
      </servlet>
      <servlet-mapping>
            <servlet-name>test</servlet-name>
            <url-pattern>/index.do</url-pattern>
      </servlet-mapping>
    </web-app>

    6、http://localhost/test/index.do

    7、jsp/servlet/javaBean test example

  • 学会LR不等于学会性能测试

    2007-08-07 09:28:57

    如题

    性能测试是综合技术能力的考量,是结合硬件、应用、服务、网络、代码、业务和数据的逻辑分析等等的全方位的衡量。LR只提供了一些方法,这些方法是用工具的形式表现出来,我们不能只看到形而忽略了意,最终发现,通过以上综合考量,才能对应用软件作全方位的性能评估。在初期,我们不可能大而全,而是重点突击某一些衡量指标,越到最后发现,所有的指标都有其关联性,从而提高的是软件系统的可用性、可扩展性。我的blog不多,但不需要太多外界的拷贝粘贴,只希望能引领一种思路。呵呵

  • 欢迎扔砖头~~~

    2007-08-06 16:43:16

    我在具体实践中借鉴了很多觉得比较有用的测试经验,这里面也有自己的一些想法。

    同时也希望能与大家共同探讨。

562/3<123>
Open Toolbar