手机浏览 RSS 2.0 订阅 膘叔的简单人生 , 腾讯云RDS购买 | 超便宜的Vultr , 注册 | 登陆
浏览模式: 标准 | 列表分类:Linux

Linux下安装TM2008

正是因为看了这篇文章,我才能在linux下面使用TM进行聊天,以前都是使用虚拟机的。如今。我也可以直接在ubuntu下面聊天了。

内容如下[说明一下,本篇内容为精简版,如需查看完整版,请到http://rainux.org/running-tm2008-beta-with-wine]:

腾讯已经发布了官方的 QQ for Linux,虽然有很多问题,例如没有 amd64 版本,功能太弱,聊天记录格式与 Windows 下的 QQ/TM 格式不一致等等,但无疑是 Linux 里使用 QQ/TM 最简单的方案。如果它已经能满足你的需求了,那么不必浪费时间看本文。

精简版本

如果你没时间看我唠叨,精简的版本是:TM2008 Beta 运行需要 Unicode 版本的 IE6、rpcrt4.dll、Visual C++ 2005 SP1 运行库,安装程序需要 GDI+。不可以使用 IEs4Linux,因为它安装的 IE6 及相关运行库是 Win9x 的 ANSI 版本。必须使用 CrossOver Games 里的 rpcrt4.dll,然后用 winetricks 安装 msxml3 gdiplus riched20 riched30 ie6 vcrun6 vcrun2005sp1 即可安装运行 TM2008 Beta。

详细版本

只看精简版没搞定?请看详细的(唠叨的)版本:

安装 Wine,运行一下 winecfg,让它生成一个干净的 ~/.wine 目录。如果要使用已有的 ~/.wine,请先将其备份。同时确保 winecfg 里设置的 Windows 版本至少是 Windows 2000(我用的是 Wine 1.x 默认的 Windows XP)。

获取一份 CrossOver Games 里的 rpcrt4.dll, 将其复制为 ~/.wine/drive_c/windows/system32/rpcrt4.dll(覆盖已有的文件),运行 winecfg,在 Libraries -> DLL Overrides 里将 rpcrt4 设置为 Native (Windows)。

下载 winetricks,使用它安装 IE6 和一些重要的运行库。如果这个过程失败,删除 ~/.wine(或者恢复备份的 ~/.wine)并从头再来。

  1. sh winetricks msxml3 gdiplus riched20 riched30 ie6 vcrun6 vcrun2005sp1  

此时如果你有 Windows 上安装好的 TM2008,它已经可以运行了,但是无法登录,会提示“网络连接失败,请检查网络。”。所以还是老老实实用安装程序装一次吧。这里有个很莫名的问题,如 果直接使用 Wine 运行 TM2008 安装程序,它很可能会直接崩溃,看不到任何图形界面的提示。而使用 Wine 运行一个其它的程序,例如 cmd.exe 或者 Total Commander,再用这个程序去启动 TM2008 安装程序则不会有任何问题。

查看聊天记录时的性能问题

好了,现在不会有什么问题阻挡你了,TM2008 Beta 安装和启动都非常顺利,使用也很稳定。甚至 QQ2009 Preview4 都可以安装并启动,不过使用时很容易崩溃。但是如果你像我这样疯狂地保存了七八年的聊天记录,你会发现:

  • 每次启动 TM2008 后第一次给任何人发消息都会导致 TM2008 失去响应将近 20 秒钟,之后继续发消息则不会有问题。第一次接收到某人的消息也会同样如此。
  • 任何试图查看聊天记录的操作都会导致 TM2008 消耗 100% CPU 并且很长时间没有反应,等待足够长的时间后才可以看到聊天记录。

这是由于 TM2008 的聊天记录数据库使用了 Windows 的 Structured Storage 技术,而其 API 库 ole32.dll 的 Wine 实现还不完善或者可能性能太低。虽然可以用 winetricks 安装 dcom98 来获取一个 Win9x 的 ANSI 版本的 ole32.dll,但它没法让 Unicode 版本的 TM2008 运行起来。搜遍了网络也找不到在 Wine 里使用 Win2k 以上系统的 ole32.dll 的方法。没办法,为了保持聊天记录的一致性,只有两个选择。要么不在 Linux 里看聊天记录;要么把聊天记录数据库 Msg2.0.db 备份并从 QQ Profile 目录(我的文档\QQ Files\QQ 号码)里删除,让 TM2008 自己生成一个空白的 Msg2.0.db,暂时抛弃历史包袱,以后再到 Windows 下把新的记录导出为备份文件后合并到老的数据库里。

 

Linux下清除.svn目录

SVN给开发带来了方便,但在导出的时候,如果选择了checkout,那么目录里是带有.svn目录的,除非是export。

如果您的项目中是checkout的,如果还要export,那就烦了一点。因为export是从服务器上导回来的。如果服务器速度慢(网上的免费SVN服务器),那就更痛苦了。因此直接删除.svn目录是最快的解决方法。

windows下面可以直接搜索.svn,然后delete就行了
linux下面怎么办?

其实更方便,只要一句话
进入项目目录后,运行 find . -name ".svn" | xargs rm -rf
然后你就会发现。.svn目录全没有了。HOHO

 

查了一下google,发现还有另外一个方法:

XML/HTML代码
  1. find -name "CVS" -exec rm -f {} \;  
  2.   
  3. 利用-name和-exec两个参数组合,可以实现批量查找删除指定文件的目的。  
  4.   
  5. 要活用find,它是很强大的。  
  6.   
  7. find [path...] [expression]  
  8.   
  9. -name pattern  
  10.     Base of file name (the path with the leading directories removed) matches shell pattern pattern. The metacharacters (`*', `?', and `[]') do not match a `.' at the start of the base name. To ignore a directory and the files under it, use -prune; see an example in the description of -path.  
  11.   
  12. -exec command ;  
  13.     Execute command; true if 0 status is returned. All following arguments to find are taken to be arguments to the command until an argument consisting of `;' is encountered. The string `{}' is replaced by the current file name being processed everywhere it occurs in the arguments to the command, not just in arguments where it is alone, as in some versions of find. Both of these constructions might need to be escaped (with a `\') or quoted to protect them from expansion by the shell. The command is executed in the starting directory.  

Tags: svn

带你深入了解Web站点数据库的分布存储【转帖】

在Web 2.0时代,网站将会经常面临着快速增加的访问量,但是我们的应用如何满足用户的访问需求,而且基本上我们看到的情况都是性能瓶颈都是在数据库上,这个不 怪数据库,毕竟要满足很大访问量确实对于任何一款数据库都是很大的压力,不论是商业数据库Oracle、MS sql Server、DB2之类,还是开源的MySQL、PostgreSQL,都是很大的挑战,解决的方法很简单,就是把数据分散在不同的数据库上(可以是硬 件上的,也可以是逻辑上的),本文就是主要讨论如何数据库分散存储的的问题。

» 阅读全文

Tags: 架构, 分布式

使用Apache做负载均衡

看到这个标题的时候,我和此文作者是一样的心态,apache也能做这个?我一向以为是nginx之类的才行?
仔细一想,如果可行,那应该是用了apache 的 proxy吧?以前用proxy做过asp的代理,如果负载均衡,估计用这个也应该可以吧?
究竟是不是这样呢?看看原文内容就知道了
原文地址为:http://tech.idv2.com/2009/07/22/loadbalancer-with-apache/
内容如下:

第一次看到这个标题时我也很惊讶,Apache居然还能做负载均衡?真是太强大了。 经过一番调查后发现的确可以,而且功能一点都不差。 这都归功于 mod_proxy 这个模块。 不愧是强大的Apache啊。

废话少说,下面就来解释一下负载均衡的设置方法。

一般来说,负载均衡就是将客户端的请求分流给后端的各个真实服务器, 达到负载均衡的目的。还有一种方式是用两台服务器,一台作为主服务器(Master), 另一台作为热备份(Hot Standby),请求全部分给主服务器,在主服务器当机时, 立即切换到备份服务器,以提高系统的整体可靠性。

负载均衡的设置

Apache可以应对上面这两种需求。先来讨论一下如何做负载均衡。 首先需要启用Apache的几个模块:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_http_module modules/mod_proxy_http.so

mod_proxy提供代理服务器功能,mod_proxy_balancer提供负载均衡功能, mod_proxy_http让代理服务器能支持HTTP协议。如果把mod_proxy_http换成 其他协议模块(如mod_proxy_ftp),或许能支持其他协议的负载均衡, 有兴趣的朋友可以自己尝试一下。

然后要添加以下配置:

ProxyRequests Off
<Proxy balancer://mycluster>
BalancerMember http://node-a.myserver.com:8080
BalancerMember http://node-b.myserver.com:8080
</Proxy>
ProxyPass / balancer://mycluster

# 警告:以下这段配置仅用于调试,绝不要添加到生产环境中!!!
<Location /balancer-manager>
SetHandler balancer-manager
Order Deny,Allow
Deny from all
Allow from localhost
</Location>

从上面的 ProxyRequests Off 这条可以看出,实际上负载均衡器就是一个反向代理, 只不过它的代理转发地址不是某台具体的服务器,而是一个 balancer:// 协议:

ProxyPass / balancer://mycluster

协议地址可以随便定义。然后,在<Proxy>段中设置该balancer协议的内容即可。 BalancerMember指令可以添加负载均衡组中的真实服务器地址。

下面那段<Location /balancer-manager>是用来监视负载均衡的工作情况的, 调试时可以加上(生产环境中禁止使用!),然后访问 http://localhost/balancer-manager/ 即可看到 负载均衡的工作状况。

OK,改完之后重启服务器,访问你的Apache所在服务器的地址,即可看到负载均衡的效果了。 打开 balancer-manager 的界面,可以看到请求是平均分配的。

如果不想平均分配怎么办?给 BalancerMember 加上 loadfactor 参数即可,取值范围为1-100。 比如你有三台服务器,负载分配比例为 7:2:1,只需这样设置:

ProxyRequests Off
<Proxy balancer://mycluster>
BalancerMember http://node-a.myserver.com:8080 loadfactor=7
BalancerMember http://node-b.myserver.com:8080 loadfactor=2
BalancerMember http://node-c.myserver.com:8080 loadfactor=1
</Proxy>
ProxyPass / balancer://mycluster

默认情况下,负载均衡会尽量让各个服务器接受的请求次数满足预设的比例。 如果要改变算法,可以使用 lbmethod 属性。如:

ProxyRequests Off
<Proxy balancer://mycluster>
BalancerMember http://node-a.myserver.com:8080 loadfactor=7
BalancerMember http://node-b.myserver.com:8080 loadfactor=2
BalancerMember http://node-c.myserver.com:8080 loadfactor=1
</Proxy>
ProxyPass / balancer://mycluster
ProxySet lbmethod=bytraffic

lbmethod可能的取值有:

lbmethod=byrequests 按照请求次数均衡(默认)
lbmethod=bytraffic 按照流量均衡
lbmethod=bybusyness 按照繁忙程度均衡(总是分配给活跃请求数最少的服务器)

各种算法的原理请参见Apache的文档

热备份(Hot Standby)

热备份的实现很简单,只需添加 status=+H 属性,就可以把某台服务器指定为备份服务器:

ProxyRequests Off
<Proxy balancer://mycluster>
BalancerMember http://node-a.myserver.com:8080
BalancerMember http://node-b.myserver.com:8080 status=+H
</Proxy>
ProxyPass / balancer://mycluster

从 balancer-manager 界面中可以看到,请求总是流向 node-a ,一旦node-a挂掉, Apache会检测到错误并把请求分流给 node-b。Apache会每隔几分钟检测一下 node-a 的状况, 如果node-a恢复,就继续使用node-a。

Tags: apache, 负载均衡

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

文章很长。请慢慢看,有部分因为没有接触过,所以不懂。。。
当成学习资料吧。。

硬件和操作系统

影响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服务器上执行后处理和其他日志统计操作,以免影响服务器的性能。

如果你使用了任何"Allowfrom domain"或"Denyfrom 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.txtsection 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 -phttpd_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()会(在高负荷的情况下更会)立即并行产生。

本文允许自由使用、分发、转载,但必须保留译者署名;详见:译者声明

______________________________________

本手册是在原来尚未完成的“Apache 2.0手册中文版翻译项目”的基础上,根据新的 Apache 2.2 文档进行修订、更新、补充的结果,没有他们之前辛勤劳动积累的成果,单靠一人之力是很难完成的[目前尚有部分未翻译]。所以首先应当感谢那个项目的各位自 愿者:kajaa 、biAji 、fei 、suncjs 、Daniel 、flytosea 、forehead 。

感谢http://www.15913.com/热心的站长 bingzhou 兄提供网络空间[电信]。

感谢LinuxFans.Org上热心的 sejishikong 兄提供网络空间[网通]。

感谢http://www.haloso.com/热心的站长 harvey 兄提供网络空间[铁通,教育网]。

感谢热心的 乔章池 兄制作 chm 版本。

感谢热心的 刘智丹 兄制作 pdf 版本。