nginx内使用正则表达式(花括号)需要注意的问题

朋友为对付一些乱七八糟网站的盗链,于是就在nginx配置文件内写了一个正则来禁止那些域名,他写的是这个样子:

if ($http_referer ~* .*([0-9]{2,3}[a-z]{3}.com|ffkkk.net|444nnn.net)) {
return   403;
}

但是用nginx -t来测试nginx配置文件语法的时候报错,提示在[0-9]这里有错。我看了看,正则写的没有问题呀,怀疑是[]的问题,我自己以前没有在ngnix内用过[],就简化了下:

if ($http_referer ~* .*[0-9].com) {
return   403;
}

用上面这个配置语法测试通过了。说明[]是可以直接用的,这样试过之后我恍然大悟,确定是{}的问题,因为nginx内{}是用来表示配置段的,直接用肯定有问题。但是在nginx的配置文件内,难道正则就不能用{}了吗?

肯定是可以的,要不然也太弱智了,经过google,终于找到答案:原来如果正则内有用{}的话,只要在正则的两边加上单引号或双引号就行了。

所以写成这样子就通过了:

if ($http_referer ~* "^.*[0-9]{2,3}[a-z]{3}.com") {
return   403;
}

参考:http://www.nginxcn.com/doc/standard/httprewrite.html

注: 对花括号( { 和 } )来说, 他们既能用在重定向的正则表达式里,也是用在配置文件里分割代码块, 为了避免冲突, 正则表达式里带花括号的话,应该用双引号(或者单引号)包围。比如,要将类似以下的url

/photos/123456

重定向到:

/path/to/photos/12/1234/123456.png

可以用以下方法 (注意双引号):

rewrite "/photos/([0-9] {2})([0-9] {2})([0-9] {2})" /path/to/photos/$1/$1$2/$1$2$3.png;

javadoc 在 Java 的注释上做文章(转)

转自:http://tanzek.blog.51cto.com/95629/15808

作者:边城狂人

目录

  前言
一. Java 文档和 javadoc
二. 文档注释的格式
1. 文档注释的格式化
2. 文档注释的三部分
三. 使用 javadoc 标记
1. @see 的使用
2. 使用 @author、@version 说明类
3. 使用 @param、@return 和 @exception 说明方法
四. javadoc 命令

前言

  Java 的语法与 C++ 及为相似,那么,你知道 Java 的注释有几种吗?是两种?

  // 注释一行
/* …… */ 注释若干行

  不完全对,除了以上两种之外,还有第三种,文档注释:

  /** …… */ 注释若干行,并写入 javadoc 文档

  通常这种注释的多行写法如下:

  /**
* ………
* ………
*/

  暂停,暂停!这第三种注释有什么用?javadoc 又是什么东西?

  好,那就让我告诉你——

一. Java 文档和 javadoc

  Java 程序员都应该知道使用 JDK 开发,最好的帮助信息就来自 SUN 发布的 Java 文档。它分包、分类详细的提供了各方法、属性的帮助信息,具有详细的类树信息、索引信息等,并提供了许多相关类之间的关系,如继承、实现接口、引用等。

  Java 文档全是由一些 html 文件组织起来的,在 SUM 的站点上可以下载它们的压缩包。但是你肯定想不到,这些文档我们可以自己生成。——就此打住,再吊一次胃口。

  安装了 JDK 之后,安装目录下有一个 src.jar 文件或者 src.zip 文件,它们都是以 ZIP 格式压缩的,可以使用 WinZip 解压。解压之后,我们就可以看到分目录放的全是 .java 文件。是了,这些就是 Java 运行类的源码了,非常完整,连注释都写得一清二楚……不过,怎么看这些注释都有点似曾相识的感觉?

  这就不奇怪了,我们的迷底也快要揭开了。如果你仔细对比一下 .java 源文件中的文档注释 (/** … */) 和 Java 文档的内容,你会发现它们就是一样的。Java 文档只是还在格式和排版上下了些功夫。再仔细一点,你会发现 .java 源文件中的注释还带有 HTML 标识,如 <B>、<BR>、<Code> 等,在 Java 文档中,该出现这些标识的地方,已经按标识的的定义进行了排版。

  终于真像大白了,原来 Java 文档是来自这些注释。难怪这些注释叫做文档注释呢!不过,是什么工具把这些注释变成文档的呢?

  是该请出 javadoc 的时候了。在 JDK 的 bin 目录下你可以找到 javadoc,如果是 Windows 下的 JDK,它的文件名为 javadoc.exe。使用 javdoc 编译 .java 源文件时,它会读出 .java 源文件中的文档注释,并按照一定的规则与 Java 源程序一起进行编译,生成文档。

  介绍 javadoc 的编译命令之前,还是先了解一下文档注释的格式吧。不过为了能够编译下面提到的若干例子,这里先介绍一条 javadoc 命令:

  javadoc -d 文档存放目录 -author -version 源文件名.java

  这条命令编译一个名为 “源文件名.java”的 java 源文件,并将生成的文档存放在“文档存放目录”指定的目录下,生成的文档中 index.html 就是文档的首页。-author 和 -version 两个选项可以省略。

二. 文档注释的格式

  文档注释可以用于对类、属性、方法等进行说明。写文档注释时除了需要使用 /** …. */ 限定之外,还需要注意注释内部的一些细节问题。

  1. 文档和文档注释的格式化

  生成的文档是 HTML 格式,而这些 HTML 格式的标识符并不是 javadoc 加的,而是我们在写注释的时候写上去的。比如,需要换行时,不是敲入一个回车符,而是写入 <br>,如果要分段,就应该在段前写入 <p>。

  因此,格式化文档,就是在文档注释中添加相应的 HTML 标识。

  文档注释的正文并不是直接复制到输出文件 (文档的 HTML 文件),而是读取每一行后,删掉前导的 * 号及 * 号以前的空格,再输入到文档的。如

  /**
* This is first line. <br>
***** This is second line. <br>
This is third line.
*/

  编译输出后的 HTML 源码则是

  This is first line. <br>
This is second line. <br>
This is third line.

  前导的 * 号允许连续使用多个,其效果和使用一个 * 号一样,但多个 * 号前不能有其它字符分隔,否则分隔符及后面的 * 号都将作为文档的内容。* 号在这里是作为左边界使用,如上例的第一行和第二行;如果没有前导的 * 号,则边界从第一个有效字符开始,而不包括前面的空格,如上例第三行。

  还有一点需要说明,文档注释只说明紧接其后的类、属性或者方法。如下例:

  /** comment for class */
public class Test {

/** comment for a attribute */
int number;

/** comment for a method */
public void myMethod() { …… }

……
}

  上例中的三处注释就是分别对类、属性和方法的文档注释。它们生成的文档分别是说明紧接其后的类、属性、方法的。“紧接”二字尤其重要,如果忽略 了这一点,就很可能造成生成的文档错误。如

  import java.lang.*;

/** commnet for class */

public class Test { …… }

// 此例为正确的例子

  这个文档注释将生成正确的文档。但只需要改变其中两行的位置,变成下例,就会出错:

  /** commnet for class */

import java.lang.*;

public class Test { …… }

// 此例为错误的例子

  这个例子只把上例的 import 语句和文档注释部分交换了位置,结果却大不相同——生成的文档中根本就找不到上述注释的内容了。原因何在?

  “/** commnet for class */”是对 class Test 的说明,把它放在“public class Test { …… }”之前时,其后紧接着 class Test,符合规则,所以生成的文档正确。但是把它和“import java.lang.*;”调换了位置后,其后紧接的就是不 class Test 了,而是一个 import 语句。由于文档注释只能说明类、属性和方法,import 语句不在此列,所以这个文档注释就被当作错误说明省略掉了。

  2. 文档注释的三部分

  根据在文档中显示的效果,文档注释分为三部分。先举例如下,以便说明。

  /**
* show 方法的简述.
* <p>show 方法的详细说明第一行<br>
* show 方法的详细说明第二行
* @param b true 表示显示,false 表示隐藏
* @return 没有返回值
*/
public void show(boolean b) {
frame.show(b);
}

  第一部分是简述。文档中,对于属性和方法都是先有一个列表,然后才在后面一个一个的详细的说明。列表中属性名或者方法名后面那段说明就是简述。 如下图中被红框框选的部分:

  简述部分写在一段文档注释的最前面,第一个点号 (.) 之前 (包括点号)。换句话说,就是用第一个点号分隔文档注释,之前是简述,之后是第二部分和第三部分。如上例中的 “* show 方法的简述.”。

  有时,即使正确地以一个点号作为分隔,javadoc 仍然会出错,把点号后面的部分也做为了第一部分。为了解决这个问题,我们可以使用一个 <p> 标志将第二分部分开为下一段,如上例的“* <p>show 方法的详细说明第一行 ….”。除此之外,我们也可以使用 <br> 来分隔。

  第二部分是详细说明部分。该部分对属性或者方法进行详细的说明,在格式上没有什么特殊的要求,可以包含若干个点号。它在文档中的位置如 下图所示:

  这部分文档在上例中相应的代码是:

  * show 方法的简述.
* <p>show 方法的详细说明第一行<br>
* show 方法的详细说明第二行

  发现什么了?对了,简述也在其中。这一点要记住了,不要画蛇添足——在详细说明部分中再写一次简述哦!

  第三部分是特殊说明部分。这部分包括版本说明、参数说明、返回值说明等。它在文档中的位置:

  第三部分在上例中相应的代码是

  * @param b true 表示显示,false 表示隐藏
* @return 没有返回值

  除了 @param 和 @return 之外,还有其它的一些特殊标记,分别用于对类、属性和方法的说明……不要推我,我马上就说。

三. 使用 javadoc 标记

  javadoc 标记是插入文档注释中的特殊标记,它们用于标识代码中的特殊引用。javadoc 标记由“@”及其后所跟的标记类型和专用注释引用组成。记住了,三个部分——@、标记类型、专用注释引用。不过我宁愿把它分成两部分:@ 和标记类型、专用注释引用。虽然 @ 和 标记类型之间有时可以用空格符分隔,但是我宁愿始终将它们紧挨着写,以减少出错机会。

  javadoc 标记有如下一些:

标记 用于 作用 @author 对类的说明 标明开发该类模块的作者 @version 对类的说明 标明该类模块的版本 @see 对类、属性、方法的说明 参考转向,也就是相关主题 @param 对方法的说明 对方法中某参数的说明 @return 对方法的说明 对方法返回值的说明 @exception 对方法的说明 对方法可能抛出的异常进行说明

  下面详细说明各标记。

  1. @see 的使用

  @see 的句法有三种:

  @see 类名
@see #方法名或属性名
@see 类名#方法名或属性名

  类名,可以根据需要只写出类名 (如 String) 或者写出类全名 (如 java.lang.String)。那么什么时候只需要写出类名,什么时候需要写出类全名呢?

  如果 java 源文件中的 import 语句包含了的类,可以只写出类名,如果没有包含,则需要写出类全名。java.lang 也已经默认被包含了。这和 javac 编译 java 源文件时的规定一样,所以可以简单的用 javac 编译来判断,源程序中 javac 能找到的类,javadoc 也一定能找到;javac 找不到的类,javadoc 也找不到,这就需要使用类全名了。

  方法名或者属性名,如果是属性名,则只需要写出属性名即可;如果是方法名,则需要写出方法名以及参数类型,没有参数的方法,需要写出一 对括号。如

成员类型 成员名称及参数 @see 句法 属性 number @see number 属性 count @see count 方法 count() @see count() 方法 show(boolean b) @see show(boolean) 方法 main(String[] args) @see main(String[])

  有时也可以偷懒:假如上例中,没有 count 这一属性,那么参考方法 count() 就可以简写成 @see count。不过,为了安全起见,还是写全 @see count() 比较好。

  @see 的第二个句法和第三个句法都是转向方法或者属性的参考,它们有什么区别呢?

  第二个句法中没有指出类名,则默认为当前类。所以它定义的参考,都转向本类中的属性或者方法。而第三个句法中指出了类名,则还可以转向 其它类的属性或者方法。

  关于 @see 标记,我们举个例说明。由于 @see 在对类说明、对属性说明、对方法说明时用法都一样,所以这里只以对类说明为例。

  /**
* @see String
* @see java.lang.StringBuffer
* @see #str
* @see #str()
* @see #main(String[])
* @see Object#toString()
*/
public class TestJavaDoc {

}

  生成的文档的相关部分如下图:

  String 和 StringBuffer 都是在 java.lang 包中,由于这个包是默认导入了的,所以这两个类可以直接写类名,也可以写类全名。str、str() 为同名属性和方法,所以方法名需要用 () 区分。main 是带参数的方法,所以在 () 中指明了参数类型。toString() 虽然在本类中也有 (从 Object 继承的),但我们是想参考 Object 类的 toString() 方法,所以使用了 Object#toString()。

  奇怪的是,为什么其中只有 str、str() 和 main(String[]) 变成了链接呢?那是因为编译时没有把 java.lang 包或者 Stirng、StringBuffer、Object 三个类的源文件一起加入编译,所以,生成的文档没有关于那三个类的信息,也就不可以建立链接了。后面讲解 javadoc 编译命令的时候还会详细说明。

  上例中如果去把类中的 str 属性去掉,那么生成的文档又会有什么变化呢?你会发现,原来是 str, str(),而现在变成了 str(), str(),因为 str 属性已经没有了,所以 str 也表示方法 str()。

  2. 使用 @author、@version 说明类

  这两个标记分别用于指明类的作者和版本。缺省情况下 javadoc 将其忽略,但命令行开关 -author 和 -version 可以修改这个功能,使其包含的信息被输出。这两个标记的句法如下:

  @author 作者名
@version 版本号

  其中,@author 可以多次使用,以指明多个作者,生成的文档中每个作者之间使用逗号 (,) 隔开。@version 也可以使用多次,只有第一次有效,生成的文档中只会显示第一次使用 @version 指明的版本号。如下例

  /**
* @author Fancy
* @author Bird
* @version Version 1.00
* @version Version 2.00
*/
public class TestJavaDoc {

}

  生成文档的相关部分如图:

  从生成文档的图示中可以看出,两个 @author 语句都被编译,在文档中生成了作者列表。而两个 @version 语句中只有第一句被编译了,只生成了一个版本号。

  从图上看,作者列表是以逗号分隔的,如果我想分行显示怎么办?另外,如果我想显示两个以上的版本号又该怎么办?

  ——我们可以将上述两条 @author 语句合为一句,把两个 @version 语句也合为一句:

  @author Fancy<br>Bird
@version Version 1.00<br>Version 2.00

  结果如图:

  我们这样做即达到了目的,又没有破坏规则。@author 之后的作者名和 @version 之后的版本号都可以是用户自己定义的任何 HTML 格式,所以我们可以使用 <br> 标记将其分行显示。同时,在一个 @version 中指明两个用 <br> 分隔的版本号,也没有破坏只显示第一个 @version 内容的规则。

  3. 使用 @param、@return 和 @exception 说明方法

  这三个标记都是只用于方法的。@param 描述方法的参数,@return 描述方法的返回值,@exception 描述方法可能抛出的异常。它们的句法如下:

  @param 参数名 参数说明
@return 返回值说明
@exception 异常类名 说明

  每一个 @param 只能描述方法的一个参数,所以,如果方法需要多个参数,就需要多次使用 @param 来描述。

  一个方法中只能用一个 @return,如果文档说明中列了多个 @return,则 javadoc 编译时会发出警告,且只有第一个 @return 在生成的文档中有效。

  方法可能抛出的异常应当用 @exception 描述。由于一个方法可能抛出多个异常,所以可以有多个 @exception。每个 @exception 后面应有简述的异常类名,说明中应指出抛出异常的原因。需要注意的是,异常类名应该根据源文件的 import 语句确定是写出类名还是类全名。   示例如下:

  public class TestJavaDoc {

/**
* @param n a switch
* @param b excrescent parameter
* @return true or false
* @return excrescent return
* @exception java.lang.Exception throw when switch is 1
* @exception NullPointerException throw when parameter n is null
*/
public boolean fun(Integer n) throws Exception {
switch (n.intValue()) {
case 0:
break;
case 1:
throw new Exception("Test Only");
default:
return false;
}
return true;
}

}

  使用 javadoc 编译生成的文档相关部分如下图:

  可以看到,上例中 @param b excrescent parameter 一句是多余的,因为参数只是一个 n,并没有一个 b 但是 javadoc 编译时并没有检查。因此,写文档注释时一定要正确匹配参数表与方法中正式参数表的项目。如果方法参数表中的参数是 a,文档中却给出对参数 x 的解释,或者再多出一个参数 i,就会让人摸不着头脑了。@exceptin 也是一样。

  上例程序中并没有抛出一个 NullPointerException,但是文档注释中为什么要写上这么一句呢,难道又是为了演示?这不是为了演示描述多余的异常也能通过编译,而是 为了说明写异常说明时应考运行时 (RunTime) 异常的可能性。上例程序中,如果参数 n 是给的一个空值 (null),那么程序会在运行的时候抛出一个 NullPointerException,因此,在文档注释中添加了对 NullPointerException 的说明。

  上例中的 @return 语句有两个,但是根据规则,同一个方法中,只有第一个 @return 有效,其余的会被 javadoc 忽略。所以生成的文档中没有出现第二个 @return 的描述。

  讲到这里,该怎么写文档注释你应该已经清楚了,下面就开始讲解 javadoc 的常用命令。

四. javadoc 命令

  运行 javadoc -help 可以看到 javadoc 的用法,这里列举常用参数如下:

用法:
javadoc [options] [packagenames] [sourcefiles]

选项:

  -public 仅显示 public 类和成员   -protected 显示 protected/public 类和成员 (缺省)   -package 显示 package/protected/public 类和成员   -private 显示所有类和成员   -d <directory> 输出文件的目标目录   -version 包含 @version 段   -author 包含 @author 段   -splitindex 将索引分为每个字母对应一个文件   -windowtitle <text> 文档的浏览器窗口标题

  javadoc 编译文档时可以给定包列表,也可以给出源程序文件列表。例如在 CLASSPATH 下有两个包若干类如下:

  fancy.Editor
fancy.Test
fancy.editor.ECommand
fancy.editor.EDocument
fancy.editor.EView

  这里有两个包 (fancy 和 fancy.editor) 和 5 个类。那么编译时 (Windows 环境) 可以使用如下 javadoc 命令:

  javadoc fancyTest.java fancyEditor.java fancyeditorECommand.java fancyeditorEDocument.java fancyeditorEView.java

  这是给出 java 源文件作为编译参数的方法,注意命令中指出的是文件路径,应该根据实际情况改变。也可以是给出包名作为编译参数,如:

  javadoc fancy fancy.editor

  用浏览器打开生成文档的 index.html 文件即可发现两种方式编译结果的不同,如下图:

  用第二条命令生成的文档被框架分成了三部分:包列表、类列表和类说明。在包列表中选择了某个包之后,类列表中就会列出该包中的所有类;在类列表 中选择了某个类之后,类说明部分就会显示出该类的详细文档。而用第一条命令生成的文档只有两部分,类列表和类说明,没有包列表。这就是两种方式生成文档的 最大区别了。

  下面再来细说选项。

  -public、-protected、-package、-private 四个选项,只需要任选其一即可。它们指定的显示类成员的程度。它们显示的成员多少是一个包含的关系,如下表:

-private (显示所有类和成员) -package (显示 package/protected/public 类和成员) -protected (显示 protected/public 类和成员) -public (仅显示 public 类和成员)

  -d 选项允许你定义输出目录。如果不用 -d 定义输出目录,生成的文档文件会放在当前目录下。-d 选项的用法是

  -d 目录名

  目录名为必填项,也就是说,如果你使用了 -d 参数,就一定要为它指定一个目录。这个目录必须已经存在了,如果还不存在,请在运行 javadoc 之前创建该目录。

  -version 和 -author 用于控制生成文档时是否生成 @version 和 @author 指定的内容。不加这两个参数的情况下,生成的文档中不包含版本和作者信息。

  -splitindex 选项将索引分为每个字母对应一个文件。默认情况下,索引文件只有一个,且该文件中包含所有索引内容。当然生成文档内容不多的时候,这样做非常合适,但是, 如果文档内容非常多的时候,这个索引文件将包含非常多的内容,显得过于庞大。使用 -splitindex 会把索引文件按各索引项的第一个字母进行分类,每个字母对应一个文件。这样,就减轻了一个索引文件的负担。

  -windowtitle 选项为文档指定一个标题,该标题会显示在窗口的标题栏上。如果不指定该标题,而默认的文档标题为“生成的文档(无标题)”。该选项的用法是:

  -windowtitle 标题

  标题是一串没有包含空格的文本,因为空格符是用于分隔各参数的,所以不能包含空格。同 -d 类似,如果指定了 -windowtitle 选项,则必须指定标题文本。

  到此为止,Java 文档和 javadoc 就介绍完了。javadoc 真的能让我们在 Java 注释上做文章——生成开发文档。

用Qmail架设高可用邮件服务器的安装及配置

1.    版本历史

Revision    Author(s)    Date    Summary of activity
1.0    罗辉    2007-07-26    创建
1.1    罗辉    2007-8-27    增加9.15(收信超时问题)
1.2    罗辉    2007-9-4    增加二级域名访问WEBMAIL

2.    参考文档

[1] QmailToaster Home
    http://www.qmailtoaster.com
   
3.    前言

本文主要记录了公司E-Mail服务器的安装和配置。公司E-Mail建立在CentOS linux 5平台上,使用Qmail + vpopmail + qmailadmin + squirrelmail + mysql + ezmlm +clamav+ spamassassin建立一个完整的企业邮局平台。已有的功能包括虚拟域,虚拟用户,WEBMAIL,WEB邮局管理,邮件列表,在线时实扫毒,反垃圾邮件等。
Qmail安装有很多种方法,大部份方法都是下载各种源代码,进行编译安装,再进行各个配置。我们采用做好的RPM源码包,编译成RPM安装包进行安装。这种方法的好处再于安装简单,维护方便,功能也齐全。

4.    硬件系统环境
邮件服务器为Dell PowerEdge 2950,CPU为Intel xeon 5150 X2,内存为8G,146G 15000转的SAS硬盘。

5.    软件系统环境
服务器所装操作系统都为Centos linux 5,可在http://www.centos.org 下载。
所用apache及Mysql为操作系统自带版本,也可以在http://www.apache.org 及http://www.mysql.com下载源代码编译安装。
邮件系统用到的所有软件包都可从http://www.qmailtoaster.com下载。

6.    软件包下载
Qmailtoaster可以通过一个脚本下载邮件系统所需的所有安装包,我们先下载这个脚本,再通过执行脚本下载所需的软件包:
#wget http://www.qmailtoaster.com/info/current-download-script.sh
#chmod 755 current-download-script.sh
#./current-download-script.sh
除了上述软件包之外,还需下载一些脚本,用于安装邮件系统:
#wget http://www.qmailtoaster.com/centos/cnt5064/cnt5064-deps.sh
#wget http://www.qmailtoaster.com/centos/cnt5064/cnt5064-djbdns-localcache-install.sh
#wget http://www.qmailtoaster.com/centos/cnt5064/cnt5064-install-script.sh
#wget http://www.qmailtoaster.com/centos/cnt5064/cnt5064-perl.sh
#wget http://www.qmailtoaster.com/centos/cnt5064/cnt5064-svcs.sh
#wget http://www.qmailtoaster.com/centos/cnt5064/mysql-setup.sh
#wget http://www.qmailtoaster.com/centos/cnt5064/very-quick-install.txt

7.    邮件系统安装

以下主备两台服务器都需要安装。
very-quick-install.txt文件说明了安装qmailtoaster的大致步骤,我们在安装时可根据自己的需求,做一些调整。下面介绍安装步骤:

7.1.    安装前期准备工作

# ./cnt5064-deps.sh
运行cnt5064-deps.sh,通过yum安装一些邮件系统所需要的软件包。
# ./cnt5064-perl.sh
运行cnt5064-perl.sh,安装一些perl的库。
# vi mysql-setup.sh
将:
MYSQLPW=YOUR_MYSQL_ROOT_PASSWORD
改成:
MYSQLPW=123456
123456为mysql的root用户密码。
再运行脚本,配置mysql服务:
# ./mysql-setup.sh
配置iptables,开放qmail系统需用到的端口:
# vi /etc/sysconfig/iptables
# Firewall configuration written by system-config-securitylevel
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:RH-Firewall-1-INPUT – [0:0]
-A INPUT -j RH-Firewall-1-INPUT
-A FORWARD -j RH-Firewall-1-INPUT
-A RH-Firewall-1-INPUT -i lo -j ACCEPT
-A RH-Firewall-1-INPUT -i eth0 -j ACCEPT
-A RH-Firewall-1-INPUT -p icmp –icmp-type any -j ACCEPT
-A RH-Firewall-1-INPUT -p 50 -j ACCEPT
-A RH-Firewall-1-INPUT -p 51 -j ACCEPT
-A RH-Firewall-1-INPUT -p udp –dport 5353 -d 224.0.0.251 -j ACCEPT
-A RH-Firewall-1-INPUT -p udp -m udp –dport 631 -j ACCEPT
-A RH-Firewall-1-INPUT -p tcp -m tcp –dport 631 -j ACCEPT
-A RH-Firewall-1-INPUT -m state –state ESTABLISHED,RELATED -j ACCEPT
#Accept ssh port
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 22 -j ACCEPT
#Accept smtp port
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 25 -j ACCEPT
#Accept http port
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT
#Accept pop3 port
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 110 -j ACCEPT
#Accept imap port
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 143 -j ACCEPT
#Accept smtps port
#-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 456 -j ACCEPT
#Accept msp port
#-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 587 -j ACCEPT
#Accept SpamAssassin port
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 783 -j ACCEPT
#Accept imaps port
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 993 -j ACCEPT
#Accept pop3s port
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 995 -j ACCEPT
-A RH-Firewall-1-INPUT -j REJECT –reject-with icmp-host-prohibited
COMMIT
重启服务:
# service iptables restart
建立一个头文件的链接,没有这个等下编译的时候会通不过:
# ln -s /usr/include/et/com_err.h /usr/include/com_err.h

7.2.    安装邮件系统

# ./ cnt5064-install-script.sh
cnt5064-install-script.sh是安装邮件系统的脚本,这个脚本让帮助我们自动编译和安装相关软件包。在每安装一个包时,它都会询问用户Shall we continue? (yes, skip, quit) [y]/s/q:按y安装,s跳过,q退出。
下列软件包选y,这些包我们不需要手动去改什么配置:
daemontools-toaster
ucspi-tcp-toaster
安装了上两个包后,选q退出。对于下面的vpopmail-toaster,我们需要改一些编译的参数:
先安装原码包:
# rpm -ivh vpopmail-toaster-5.4.17-1.3.4.src.rpm
更改编译配置:
# vi /usr/src/redhat/SPECS/vpopmail-toaster.spec
找到:
       –enable-auth-module=mysql
       –enable-log-name=vpopmail
       –disable-mysql-limits
       –enable-valias
       –disable-many-domains
mv %{buildroot}%{vdir}/etc/vpopmail.mysql %{buildroot}%{vdir}/etc/vpopmail.mysql.dist
echo "localhost|0|vpopmail|SsEeCcRrEeTt|vpopmail" > %{buildroot}%{vdir}/etc/vpopmail.mysql
%attr(0644,vpopmail,vchkpw) %config(noreplace) %{vdir}/etc/vpopmail.mysql
%attr(0644,vpopmail,vchkpw) %{vdir}/etc/vpopmail.mysql.dist
更改为:
        –enable-auth-module=cdb
        –enable-log-name=vpopmail
#       –disable-mysql-limits
#       –enable-valias
#       –disable-many-domains
#mv %{buildroot}%{vdir}/etc/vpopmail.mysql %{buildroot}%{vdir}/etc/vpopmail.mysql.dist
#echo "localhost|0|vpopmail|SsEeCcRrEeTt|vpopmail" > %{buildroot}%{vdir}/etc/vpopmail.mysql
#%attr(0644,vpopmail,vchkpw) %config(noreplace) %{vdir}/etc/vpopmail.mysql
#%attr(0644,vpopmail,vchkpw) %{vdir}/etc/vpopmail.mysql.dist
按新配置编译:
# rpmbuild -bb –with cnt5064 /usr/src/redhat/SPECS/vpopmail-toaster.spec
安装编译好的包:
# rpm -ivh /usr/src/redhat/RPMS/x86_64/vpopmail-toaster-5.4.17-1.3.4.x86_64.rpm
/home/vpopmail在/分区,以后所有的邮件都会放在这个目录,/分区空间有限,我们将其移到/web下:
# cp -rfp /home/vpopmail /web
# rm –rf /home/vpopmail
# ln -s /web/vpopmail /home/vpopmail
继续安装其它包:
# ./ cnt5064-install-script.sh
下列软件包选y,这些包我们不需要手动去改什么配置,其它包选s跳过:
libdomainkeys-toaster
libsrs2-toaster
qmail-toaster
courier-authlib-toaster
courier-imap-toaster
autorespond-toaster
control-panel-toaster
ezmlm-toaster
qmailadmin-toaster
qmailmrtg-toaster
maildrop-toaster
isoqlog-toaster
squirrelmail-toaster
spamassassin-toaster
clamav-toaster
ripmime-toaster
simscan-toaster
如果中间没有什么错误的话,到此安装就完成了。

8.    配置邮件系统

8.1.    增加虚拟邮件域

# /home/vpopmail/bin/vadddomain testqmail.com 123456

用上面的命令就可增加一个虚拟邮件域,域名为testqmail.com,密码为123456。这个命令会产生一个 postmaster@testqmail.com的邮件用户,postmaster用户是testqmail.com的管理用户。通过其可以在 qmailadmin内增加或删除邮件帐号。

8.2.    更改default域名

当初安装操作系统时,这台服务器为tomcat服务器,域名设为tomcat1.testqmail.com。邮件系统的默认域名也就相应成了 tomcat1.testqmail.com,我们需要将其改为testqmail.com,这才是邮件系统的真正域名。
# cd /var/qmail/supervise
# vi imap4/run
# vi imap4-ssl/run
# vi pop3/run
# vi pop3-ssl/run
将上面这些文件内的:
HOSTNAME=`hostname –fqdn`
改成:
HOSTNAME=`cat /var/qmail/control/defaultdomain`
# vi smtp/run
# vi submission/run
将上面这些文件内的:
HOSTNAME=`hostname`
改成:
HOSTNAME=`cat /var/qmail/control/defaultdomain`

8.3.    配置squirrelmail

Squirrelmail是一个WEBMAIL软件,安装后需要配置一下才能正常使用:
配置squirrelmail:
# cd /usr/share/squirrelmail/config/
# ./conf.pl
#选择 “2. Server Settings”=>“A. Update IMAP Settings”=>“8. Server software”改成“courier”;回到主菜单,选择“10. Languages”,将“ Default Language”改成“zh_CN”,“Default Charset”改成“GB2312”。保存退出。

安装多语言:
# wget http://superb-west.dl.sourceforge.net/sourceforge/squirrelmail/all_locales-1.4.9-20070106.tar.gz
# cd /usr/share/squirrelmail
# tar -xvzf all_locales-1.4.9-20070106.tar.gz

8.4.    配置开机启动

qmail需要apache和mysql服务,我们需要同时配置这三个服务开机启动,qmail运行才正常:
# chkconfig –level 2345 httpd on
# chkconfig –level 2345 qmail on(做集群时不用设,heartbeat会启动它)
重启服务器后,qmail就可以正常运行了。

9.    增加二级域名访问WebMail

安装完后,用http://ip/qmailadmin和http://ip/webmail可访问管理页面和webmail,但是不方便,为此,建立两个二级域名:qmailadmin.testqmail.com和webmail.testqmail.com,方便用户记忆。为此,需做以下更改:
#vi /etc/httpd/conf/httpd.conf
将:
#Include /etc/httpd/conf/toaster.conf
#Include /etc/httpd/conf/squirrelmail.conf
更改为:
#Include /etc/httpd/conf/toaster.conf
#Include /etc/httpd/conf/squirrelmail.conf
NameVirtualHost *:80

<VirtualHost *:80>
        DocumentRoot /usr/share/squirrelmail
        ServerName webmail.testqmail.com
        <Directory /usr/share/squirrelmail>
           Options None
           Order allow,deny
           allow from all
        </Directory>
        ErrorLog logs/webmail.testqmail.com.error_log
</VirtualHost>

<VirtualHost *:80>
        DocumentRoot /usr/share/qmailadmin/
        ServerName qmailadmin.testqmail.com
        ScriptAlias /mail/ /usr/share/toaster/cgi-bin/
        Alias /admin-toaster /usr/share/toaster/htdocs/admin/
        Alias /stats-toaster/ /usr/share/toaster/htdocs/mrtg/
        Alias /qlogs-toaster/ /usr/share/toaster/htdocs/isoqlog/
        Alias /images-toaster/ /usr/share/toaster/htdocs/images/
        Alias /scripts/ /usr/share/toaster/htdocs/scripts/
        Alias /qmailadmin /usr/share/qmailadmin/
        <Directory /usr/share/qmailadmin>
            AddHandler cgi-script .cgi
            AddHandler cgi-script qmailadmin
            DirectoryIndex index.cgi qmailadmin index.html
            Options +Indexes FollowSymLinks +ExecCGI
            Order allow,deny
            Allow from all
        </Directory>
        <Directory /usr/share/toaster/htdocs>
            Options -Indexes FollowSymLinks MultiViews
            AllowOverride All
            Order allow,deny
            Allow from all
        </Directory>
        <Directory /usr/share/toaster/htdocs/admin>
            AuthType Basic
            AuthName "Qmail Toaster v. 1.3 Admin"
            AuthUserFile /usr/share/toaster/include/admin.htpasswd
            require valid-user
        </Directory>
        <Directory /usr/share/toaster/htdocs/mrtg>
            AllowOverride All
            Order allow,deny
            Allow from all
            AuthType Basic
            AuthName "Qmail Toaster v. 1.3 Admin"
            AuthUserFile /usr/share/toaster/include/admin.htpasswd
            require valid-user
        </Directory>
        <Directory /usr/share/toaster/htdocs/isoqlog>
            AllowOverride All
            Order allow,deny
            Allow from all
            AuthType Basic
            AuthName "Qmail Toaster v. 1.3 Admin"
            AuthUserFile /usr/share/toaster/include/admin.htpasswd
            require valid-user
        </Directory>
        <Directory /usr/share/toaster/cgi-bin/vqadmin>
            AllowOverride All
            Options ExecCGI
            Order allow,deny
            Allow from all
            AuthType Basic
            AuthName "Qmail Toaster v. 1.3 Admin"
            AuthUserFile /usr/share/toaster/include/admin.htpasswd
            require valid-user
        </Directory>
        <Directory /usr/share/toaster/cgi-bin>
            AllowOverride All
            Options ExecCGI
            Order allow,deny
            Allow from all
        </Directory>
        ErrorLog logs/qmailadmin.testqmail.com.error_log
</VirtualHost>

在重启apache服务:
# service httpd restart
便可用http://webmail.testqmail.com和http://qmailadmin.testqmail.com访问了。

10.    NFS配置

10.1.    配置Nfs服务

NFS服务器IP:192.168.1.204
#vi /etc/exports
/home/vpopmail 192.168.1.*(rw,async,anonuid=89,anongid=89)
#vi /etc/passwd
vpopmail:x:89:89:Vpopmail User:/home/vpopmail:/sbin/nologin
#vi /etc/group
vchkpw:x:89:
#service portmap start
#service nfs start

10.2.    挂载nfs

#cp –rfp /home/vpopmail /home/vpopmailbak
#rm –rf /home/vpopmail/*
#mount –t nfs 192.168.1.204:/home/vpopmail /home/vpopmail
#cp –rfp /home/vpopmailbak/* /homev/vpopmail/

在qmail服务器上挂载

11.    heartbeat安装及配置

11.1.    heartbeat安装

#yum install heartbeat*

11.2.    heartbeat配置

#cp /usr/share/doc/heartbeat-2.1.*/ha.cf /etc/ha.d/
#cp /usr/share/doc/heartbeat-2.1.*/authkeys /etc/ha.d/
#cp /usr/share/doc/heartbeat-2.1.*/haresources /etc/ha.d/
#vi /etc/hosts
192.168.1.6      qmail6
192.168.1.5      qmail5
#vi /etc/sysconfig/network
HOSTNAME=qmail5
#vi /etc/ha.d/authkeys
auth 1
1 crc
#chmod 600 /etc/ha.d/authkeys
#vi /etc/ha.d/ha.cf
debugfile /var/log/ha-debug
logfile /var/log/ha-log
logfacility     local0
keepalive 2
说明:心跳频率,自己设定。1:表示1秒;200ms:表示200毫秒
deadtime 30
说明:节点死亡时间阀值,就是从节点在过了30后还没有收到心跳就认为主节点死亡,自己设定
udpport 694
说明:心跳信息传递的udp端口,自己设定
#bcast eth0
#说明:采用eth0的udp广播用来发送心跳信息,建议在副节点不只一台时使用
ucast eth0 192.168.0.6
#说明:采用网卡eth0的udp单播来通知心跳,ip应为对方IP(未验证)
#mcast eth0 225.0.0.1 694 1 0
#说明:采用udp多播播来通知心跳,建议在副节点不只一台时使用
#注:广播,单播,多播,以上三种任选其一即可
auto_failback on
说明:主节点重启成功后,资源是自动拿回到主节点还是等到副节点down调后拿回资源
node qmail6
node qmail5
说明:节点名称,与uname –n保持一致。

ping 192.168.1.6

respawn 指令在运行时指定某个程序的运行和监控。如果此程序退出时的退出码不是 100,程序将自动重启。
respawn root /usr/lib/heartbeat/ipfail
apiauth ipfail gid=root uid=root

apiauth         mgmtd   uid=root
respawn         root    /usr/lib/heartbeat/mgmtd –v
上面两行是配置图型界面的管理服务hb_gui,可要可不要。

crm 指令指定了 Heartbeat 应运行 1.x-style 集群管理器或 2.x-style 集群管理器,后者支持 2 个以上的节点。
crm on

#vi /etc/ha.d/haresources
qmail5 IPaddr::192.168.1.7 Filesystem::192.168.1.205:/home/vpopmail::/home/vpopmail::nfs::rw qmail

生成/var/lib/heartbeat/crm/cib.xml
#/usr/lib/heartbeat/haresources2cib.py /etc/ha.d/haresources
设置图型界面hb_gui的管理密码
#passwd hacluster
#vi /etc/ha.d/haresources
qmail5 IPaddr::192.168.1.7 Filesystem::192.168.1.205:/home/vpopmail::/home/vpopmail::nfs::rw qmail

qmail5为主服务器,主备服务器在此设置。两台机器haresources都设成上面这样。

11.3.    开机启动

#chkconfig –level 2345 heartbeat on

12.    Q&A

12.1.    如何增加及删除用户

用户管理有两种方式,一是用WEB管理:http://qmailadmin.testqmail.com。二是用vadduser和vdeluser添加新用户和删除用户

12.2.    如何使用webmail

用浏览器打开:http://webmail.testqmail.com。输入带域名和用户名(如:cary.luo@testqmail.com)和密码登陆便可。

12.3.    使用客户端收发邮件的相关参数是什么

POP3 Server:pop.testqmail.com
SMTP Server:smtp.testqmail.com
IMAP Server:imap.testqmail.com
邮件系统同时支持pop3,pop3s,imap,imaps协议。可选其中任何一种协议收邮件,建议使用pop3s和imaps,它们的安全性更高。
发送邮件服务器需要用户认证。
设置用户名时请用全名,如:cary.luo@testqmail.com

12.4.    邮箱警告邮件能设为中文吗

可以。
设置邮箱容量达到90%的警告信息
# vi /home/vpopmail/domains/.quotawarn.msg

From: 邮箱管理员
Reply-To: postmaster@testqmail.com
To: 邮箱用户
Subject: 邮箱空间警告
Mime-Version: 1.0
Content-Type: text/html; charset=gb2312
Content-Transfer-Encoding: base64

您的邮箱空间已经达到90%.如果想继续使用,请删除一些信件.

如果需要帮助,请联系邮箱管理员:

Email : postmaster@testqmail.com

设置邮箱已满的警告信息

# echo "邮件被拒绝,用户的邮箱空间已满." > /home/vpopmail/domains/.over-quota.msg

12.5.    如何清除邮件队列

12.5.1.    处理队列中的邮件:

如想在队列中的邮件马上传递,可以
# kill -HUP qmail-send

要删除队列中的邮件
# service qmail stop
# mv /var/qmail/queue/lock /var/qmail/
# find /var/qmail/queue/ -type f -exec rm {} ;
# mv /var/qmail/lock /var/qmail/queue/
# service qmail start
队列中的邮件包含在以下目录中 /var/qmail/queue/{info,mess,remote,local}/hash/#number

12.5.2.    在邮件队列中快速删除从一个地方发过来但又不存在的邮件

假设bad.jite.com是这个域。可以键入以下命令
# echo # > ~alias/.qmail-baddomain-default
# echo bad.jite.com:alias-baddomain>> /var/qmail/control/virtualdomains
# echo bad.jite.com:127.0.0.1 >> /var/qmail/control/smtproutes
然后运行/var/qmail/bin/qmail-tcpok,给qmail-send 一个HUP信号

12.5.3.    删除队列中的大量广告信息

# service qmail stop
# cd /var/qmail/queue/mess
# find /var/qmail/queue/mess -type f -exec grep "^Subject:dss" {} ; -print -exec rm {} ;
运行queue-fix清除相关文件
# service qmail start

12.6.    我怎么更新邮件系统内的杀毒软件

邮件系统的杀毒模块用的是clamav,本身会有一个进程freshclam自动升级病毒码。有时系统会有如下错误在/var/log/clamav /freshclam.log内:
freshclam daemon 0.90.1 (OS: linux-gnu, ARCH: x86_64, CPU: x86_64)
ClamAV update process started at Mon Jul 16 10:06:06 2007
WARNING: Your ClamAV installation is OUTDATED!
WARNING: Local version: 0.90.1 Recommended version: 0.91
DON’T PANIC! Read http://www.clamav.net/support/faq
这是因为目前系统上的版本低于最新的版本造成的,可通过升级clamav解决:
# wget http://nchc.dl.sourceforge.net/sourceforge/clamav/clamav-0.91.1.tar.gz
# tar –xvzf clamav-0.91.1.tar.gz
# cd clamav-0.91.1
按自己的架构生成Makefile,服务器上是x86_64:
#i386
# ./configure –build=i686-redhat-linux-gnu –host=i686-redhat-linux-gnu –target=i386-redhat-linux-gnu –program-prefix= –prefix=/usr –exec-prefix=/usr –bindir=/usr/bin –sbindir=/usr/sbin –sysconfdir=/etc –datadir=/usr/share –includedir=/usr/include –libdir=/usr/lib –libexecdir=/usr/libexec –localstatedir=/var –sharedstatedir=/usr/com –mandir=/usr/share/man –infodir=/usr/share/info –enable-experimental /usr/bin/make
#x86_64
# ./configure –build=x86_64-redhat-linux-gnu –host=x86_64-redhat-linux-gnu –target=x86_64-redhat-linux-gnu –program-prefix= –prefix=/usr –exec-prefix=/usr –bindir=/usr/bin –sbindir=/usr/sbin –sysconfdir=/etc –datadir=/usr/share –includedir=/usr/include –libdir=/usr/lib64 –libexecdir=/usr/libexec –localstatedir=/var –sharedstatedir=/usr/com –mandir=/usr/share/man –infodir=/usr/share/info –enable-experimental /usr/bin/make
# make
# make install

12.7.    spamd日志内出错

smamd有下面的错误:
@40000000469ad2e71c1bcef4 [18428] error: Can’t locate Crypt/OpenSSL/Bignum.pm in @INC (@INC contains: ../lib /usr/lib/perl5/vendor_perl/5.8.8 /usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi /usr/lib64/perl5/site_perl/5.8.7/x86_64-linux-thread-multi /usr/lib64/perl5/site_perl/5.8.6/x86_64-linux-thread-multi /usr/lib64/perl5/site_perl/5.8.5/x86_64-linux-thread-multi /usr/lib/perl5/site_perl/5.8.8 /usr/lib/perl5/site_perl/5.8.7 /usr/lib/perl5/site_perl/5.8.6 /usr/lib/perl5/site_perl/5.8.5 /usr/lib/perl5/site_perl /usr/lib64/perl5/vendor_perl/5.8.8/x86_64-linux-thread-multi /usr/lib64/perl5/vendor_perl/5.8.7/x86_64-linux-thread-multi /usr/lib64/perl5/vendor_perl/5.8.6/x86_64-linux-thread-multi /usr/lib64/perl5/vendor_perl/5.8.5/x86_64-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.7 /usr/lib/perl5/vendor_perl/5.8.6 /usr/lib/perl5/vendor_perl/5.8.5 /usr/lib/perl5/vendor_perl /usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi /usr/lib/perl5/5.8.8) at /usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi/Crypt/OpenSSL/RSA.pm line 17.

原因是少了一个prel库,通过下面的安装解决:
# perl -MCPAN -e shell
cpan> install Crypt::OpenSSL::Bignum
cpan> quit

12.8.    收发邮件时出错

收发邮件时出错如下错误:
登录邮件服务器时出错。“密码”被拒绝。 帐户: ‘192.168.1.223’, 服务器: ‘192.168.1.223’, 协议: POP3, 服务器响应: ‘/home/vpopmail/bin/vchkpw: error while loading shared libraries: libgssapi_krb5.so.2: failed to map segment from shared object: Cannot allocate memory’, 端口: 110, 安全(SSL): 否, 服务器错误: 0x800CCC90, 错误号: 0x800CCC92

原因为相关进程给的内存太少,需要增加相应的内存:
vi /var/qmail/supervise/pop3/run
vi /var/qmail/supervise/pop3-ssl/run
vi /var/qmail/supervise/imap4/run
vi /var/qmail/supervise/imap4-ssl/run
将softlimit -m 24000000改为softlimit -m 40000000 (24M内存改为40M)

12.9.    怎么按时间看qmail日志

qmail日志中的时间转换(如:@400000003f1f409e2256080c 转换成2003-07-24 10:12:36.576063500 ):
# tai64nlocal < /var/log/qmail/smtp/current
可将日志中的时间戳转化为标准格式,而其他信息不变

12.10.    怎么限制邮件的大小

在/var/qmail/control/databytes 文件中设置附件的大小,可以实现对qmail邮件大小的限制,一般安装完成后,这个文件是没有的,可以自己创建,smtp方式是以qmail的 databytes作限制的,如要创建databytes并设置邮件最大为4m:
# echo 4000000 >/var/qmail/control/databyte

12.11.    如何禁止垃圾帐号发信过来

# vi /var/qmail/control/badmailfrom
这个控制文件实现拒收邮件功能的,每一个地址要单独一行。而且,不用重起qmail就能生效。例如:
peng@96633.net —-限制一个特定的用户
@sina.com —-限制整个域中的邮件

12.12.    如何限制单域和用户的默认邮箱数量及容量

# vi /home/vpopmail/etc/vlimits.default
#限制单域的容量和总邮件数
#这里设的是单域最大容量90G,最大邮件数5000000封
quota                   90000
maxmsgcount             5000000

# 限制新用户默认的容量和邮件数
# 这里设的是200M和10000封
default_quota           209715200
default_maxmsgcount     10000

如果想修改个别的用户容量可通过http://ip/qmailadmin管理.

12.13.    怎么配置SMTP发送的最大重试时间

# vi /var/qmail/control/queuelifetime

新增上面这个文件,这个文件用来指定一个邮件在队列中的最大存活时间,默认是604800秒,当一个邮件达到这个时间线后,qmail将最后一次尝试发送这封邮件,如果依然失败,会将其从队列中删除。我们可将其改成3600。

12.14.    如何查询qmail的送队列

# /var/qmail/bin/qmail-qstat
messages in queue: 63         //队列内有63份邮件
messages in queue but not yet preprocessed: 0
# /var/qmail/bin/qmail-qread //查看队列内的外发邮件

12.15.    用outlook收信老超时连不上

这个问题和域名反解有关,每当去服务器收信时,服务器都要对客户IP进行解析,当DNS不好或网络慢时,就会发生超时现象了。
# vi /var/qmail/supervise/pop3/run
#!/bin/sh

PASSPROG="/home/vpopmail/bin/vchkpw"
#HOSTNAME=`hostname –fqdn`
HOSTNAME=`cat /var/qmail/control/defaultdomain`

exec /usr/bin/softlimit -m 40000000
/usr/bin/tcpserver -v -R -c 200 0 110
/var/qmail/bin/qmail-popup $HOSTNAME
$PASSPROG /var/qmail/bin/qmail-pop3d Maildir 2>&1
改成:
#!/bin/sh

PASSPROG="/home/vpopmail/bin/vchkpw"
#HOSTNAME=`hostname –fqdn`
HOSTNAME=`cat /var/qmail/control/defaultdomain`

exec /usr/bin/softlimit -m 40000000
/usr/bin/tcpserver -v -R -H -c 200 -l 0 0 110
/var/qmail/bin/qmail-popup $HOSTNAME
$PASSPROG /var/qmail/bin/qmail-pop3d Maildir 2>&1
再重启Qmail。-H为不对远程机器做反解,-l 0 为不对本机做反解

13.    结论

邮件服务器还有些小问题需要以后解决:
邮件杀毒模块不能对RAR的压缩文件进行解压杀毒。目前也没有找到免费的杀毒模块有此功能。
目前的邮件系统使用的反垃圾软件为spamassassin,效果并不是很理想。

Nagios监控系统安装及配置文档

1. 版本历史

Revision    Author(s)    Date    Summary of activity
1.0            罗辉        2008-11-19    创建本文档

2. 参考文档

[1] http://www.nagiosexchange.org
[2] http://www.nagios.org/

3. 前言

做为系统管理员,管理着几十台或几百台服务器在运行。一个非常迫切的需求就是希望了解服务器及服务器上运行的服务的运行状况,在服务器或服务出现当机或停止的时候能够第一时间知道,及时处理。以便最小的减少由此带来的影响和损失。Nagios就是用来解决这个问题的,在目前的一些监控软件中,Nagios以其良好的稳定性,强大的功能等,已成为业界监控软件的首选。

Nagios官方网站的描述:
Nagios is an open source host, service and network monitoring program. Who uses it? Lots of people, including many big companies and organizations:Nagios是一个用来监控主机、服务和网络的开放源码软件,很多大的公司或组织都在使用它。

4. Nagios监控原理

上图为Nagios监控原理图。Nagios监控可以使用主动模式(Action)和被动模式 (Passive)。

主动模式主要是自身插件或结合Nrpe实现,由Nagios在定义的时间去主动监测被监控端的服务器或服务是否正常。被动模式结合Naca实现,由Nsca定时监控服务器或服务,再由Nasa把结果传至Nagios。

被动模式适合大规模服务器(一般在最少100台以上)需要监控的情况,可有效减少监控服务器的压力。在服务器数量比较少的情况下用主动模式比较方便,因为主要的配置在监控主机的设置就好了,无需在被监控端做过多设置。

我们的监控是使用Nagios结合Nrpe的主动模式。

5. Nagios的安装

5.1. YUM方式安装

我们的监控服务器是Centos linux 4.8,可用yum方式安装:

# wget http://dag.wieers.com/rpm/packages/rpmforge-release/rpmforge-release-0.3.6-1.el4.rf.i386.rpm
# rpm –ivh rpmforge-release-0.3.6-1.el4.rf.i386.rpm
# yum install nagios*
# chkconfig –level 2345 nagios on
# service nagios start

注:本文后面的配置都是按yum安装后的环境来配置的。

5.2. 源代码方式安装

1)安装Nagios

# wget http://jaist.dl.sourceforge.net/sourceforge/nagios/nagios-3.0.6.tar.gz
# tar zxvf nagios-3.0.6.tar.gz
# cd nagios-3.06
# ./configure -prefix=/usr/local/nagios
# make install                  //安装主要的程序、CGI及HTML文件
# make install-commandmode      //给外部命令访问nagios配置文件的权限
# make install-config           //把配置文件的例子复制到nagios的安装目录
# make install-init             //把nagios做成一个运行脚本,使nagios随系统开机启动
或者上面四步可用一行令代替:
# make all

2) 安装nagios的插件

# wget http://nchc.dl.sourceforge.net/sourceforge/nagiosplug/nagios-plugins-1.4.13.tar.gz
# cd nagios-plugins-1.4.13
# tar zxvf nagios-3.0.6.tar.gz
# ./configure -prefix=/usr/local/nagios
# make
# make install

6. Nagios配置文件

其实Nagios只有一个配置文件,就是/etc/nagios/nagios.cfg,其它的配置文件都是以include的方式包括进nagios.cfg的。如:
# You can specify individual object config files as shown below:
cfg_file=/etc/nagios/objects/commands.cfg
cfg_file=/etc/nagios/objects/contacts.cfg
cfg_file=/etc/nagios/objects/timeperiods.cfg
cfg_file=/etc/nagios/objects/templates.cfg

# Definitions for monitoring the local (Linux) host
cfg_file=/etc/nagios/objects/hosts.cfg
cfg_file=/etc/nagios/objects/services.cfg

commands.cfg是监控命令的配置文件,contacts.cfg是监控报警联系人的配置文件,timeperiods.cfg是时间定义配置文件,templates.cfg是模板配置文件,这里面定义了一些模板以方便用户使用。Hosts.cfg是被监控主机的配置文件,services.cfg是被监控服务的配置文件。
唯一的例外是cgi.cfg,这个文件是与WEB相关的。

6.1. Nagios的WEB配置

# htpasswd -c /etc/nagios/htpasswd.users nagiosadmin 123456
建一个WEB访问的用户之后,在浏览器输入http://ip/nagios/,输入相应的用户和密码就可以就看到Nagios的web界面了。

WEB用到的配置文件是/etc/nagios/cgi.cfg,更改配置可修改这个配置文件。
# vi /etc/nagios/cgi.cfg
use_authentication=1                         #使用用户认证
authorized_for_system_information=nagiosadmin
authorized_for_configuration_information=nagiosadmin
authorized_for_system_commands=nagiosadmin #多个用户之间用逗号隔开
authorized_for_all_services=nagiosadmin
authorized_for_all_hosts=nagiosadmin
authorized_for_all_service_commands=nagiosadmin
authorized_for_all_host_commands=nagiosadmin

6.2.  hosts.cfg

define host{            #这段是用来定义一个被监控的主机。
host_name             #这一项是用来定义标识主机的名字。我们用这个名字在host group和service里标识这个主机。一个主机能定义多个服务。使用适当时,宏$HOSTNAME$里存放了这一项的值。
alias                 #这一项用来定义主机的一个完整名字或描述。主要是和使你能理容易的标识一个主机。使用适当时,宏$HOSTALIAS$里存放了这一项的值。
address               #这一项是用来定义主机的地址。一般而言是主机的IP。当然,你也能够使用一个FQDN来标识你的主机,在没有可访问DNS服务器服务的情况下这种方法会引起问题。使用适当时,宏$ HOSTADDRESS $里存放了这一项的值。
max_check_attempts    #这一项用来定义在检测返回结果不是OK时,nagios重试检测命令的次数。设置这个值为1会导致nagios一次也不重试就报警。
check_period          #这一项用一个time period项的名字来定义在哪段时间内激活对这台主机的主动检测。time period是定义在别的文件里的配置项,我们可以在这里用名字来引用她。
contact_groups        #这是一个联系组列表。我们用联系组的名字来引用她们。多个联系组间用“,”来分隔。
notification_interval #这一项用来定义当一个服务仍然down或unreachable时,我们间隔多久重发一次通知给联系组。
notification_period   #这一项用一个time period定义来标识什么时间段内给联系组送通知。这里我们用time period定义的名字来引用她。
notification_options  #这一项用来决定发送通知的时机。选项有:d = 当有down状态时发送通知,u = 当有unreachable状态时发送通知, r = 当有服务recoveries时发送通知,f = 当主机启动或停机时发送通知。如果你给一个n选项,那么永远不会发送通知。
}

define hostgroup{     #这段是用来定义一个被监控的主机组。
hostgroup_name  #主机组名称,通常定义得较短
alias           #主机组别名,通常定义得较长
members         #主机组成员
}

6.3. services.cfg

define service{                      #这段是用来定义一个被监控的服务。
host_name             #主机名称
service_description   #服务描述
check_command         #执行命令
max_check_attempts    #最大失败尝试次数,值为1时只报警不重新检测
normal_check_interval #常规检测间隔时间,默认为60分钟(常规检测是指无论服务状态是否正常,检测次数达到“最大次数”时)
retry_check_interval  #失败尝试间隔时间,默认为60分钟(失败尝试是指服务状态不正常,检查次数达到“最大次数”时)
check_period          #检测时间段
notification_interval #当服务状态不正常时通知联系人的间隔时间,值为0时不通知联系人
notification_period   #通知联系人时间段
notification_options  #通知联系人选项,w警告,u未知,c危急,f启动和停止,n不发送通知
contact_groups        #联系人组
}

define servicegroup{                 #这段是用来定义一个被监控的服务组。
servicegroup_name     #服务组名称,通常定义得较短       
alias                 #服务组别名,通常定义得较长
members               #服务组成员
}

6.4. contacts.cfg

define contact{              #这段是用来定义一个联系人。
contact_name                 #这个指令用来定义一个联系人的简称。他会在定义contactgroup时被引用到。在相应的环境中,宏定义$CONTACTNAME$会包含这个值。
alias                        #这个指令是为了定义一个联系人的具体的描述。在相应的环境中,宏定义$CONTACTALIAS$会包含这个值。
host_notification_period     #这个指令是为了定义,能够通知Contact中定义的那个简称联系人,关于主机有问题或者恢复正常状态的时间段。你可以把他想象成能够通知Contact关于主机的在线时间。
service_notification_period  #这个指令是为了定义,能够通知Contact中定义的那个简称联系人,关于服务的问题或恢复正常的时间段。
host_notification_options    #这个指令为了定义主机在什么状态下会给联系人发通知。各个参数的描述如下:d=当主机的状态处于down时,发送通知;f=当主机状态处于stop时发送通知。r=当主机恢复up状态时发送通知。n=什么状态下都不发送通知(w-warning , u-unknown,c-critical,r-recovery;d-down,u-unreachable)。
service_notification_options #这个指令为了定义服务在什么状态下会给联系人发通知。各个参数的描述如下:w=当服务处于警告状态时发送通知 u=当服务的状态处于unknown时,发送通知;f=当服务状态处于启动和停止时发送通知。c=当服务处于Critical状态时发送通知。n=什么状态下都不发送通知。
host_notification_commands   #这个指令是为了定义一个通知联系人关于主机问题或恢复正常的联系手段的一个列表。多个手段之间用逗号隔开。
service_notification_commands#这个指令是为了定义一个通知联系人关于服务问题或恢复正常的联系手段的一个列表。多个手段之间用逗号隔开。
email                        #这个指令是为了定义联系人的email地址。这个将取决于你是如何定义你的notification commands.它可以用来给联系人发送紧急邮件。在相应的环境中。宏定义$CONTACTEMAIL$将会包含它的值。
}

define contactgroup{         #这段是用来定义一个联系人组。
contactgroup_name   #联系组名称,通常定义得较短
alias               #联系组别名,通常定义得较长
members             #联系组成员
}

6.5. timeperiods.cfg

define timeperiod{
timeperiod_name  #时间段名称,通常定义得较短
alias            #时间段别名,通常定义得较长
sunday           #星期日时间段
monday           #星期一时间段
tuesday          #星期二时间段
wednesday        #星期三时间段
thursday         #星期四时间段
friday           #星期五时间段
saturday         #星期六时间段
}

6.6. commands.cfg

define command{
command_name        #定义命令的简称
command_line        #定义当服务进行时Nagios要执行的动作。在命令执行以前,所有合法的宏都要被他们的值代替。
}

7. 用Nrpe监控Linux主机

7.1. 安装Nrpe

# wget http://dag.wieers.com/rpm/packages/rpmforge-release/rpmforge-release-0.3.6-1.el4.rf.i386.rpm
# rpm –ivh rpmforge-release-0.3.6-1.el4.rf.i386.rpm
# yum -y install nagios-nrpe
# chkconfig –level 2345 nrpe on
# service nrpe start

7.2. 配置Nrpe

修改Nrpe配置文件:

#  vi /etc/nagios/nrpe.cfg

command开头只保留两行:
command[check_load]=/usr/lib/nagios/plugins/check_load -w 15,10,5 -c 30,25,20
command[check_disk]=/usr/lib/nagios/plugins/check_disk -w 20 -c 10 -p /

第一行是监控系统负载,第二行是监控磁盘空间的。
command[check_load]内的check_load是定义的Nrpe命令,在监控端的Nrpe插件可用这个命令来取得执行结果。

7.3. 配置nagios

1) 增加nagios命令

# vi commands.cfg
最后增加:
define command{
command_name check_nrpe
command_line /usr/local/nagios/libexec/check_nrpe -H $HOSTADDRESS$ -c $ARG1$
}

2) 增加被监控服务器

# vi /etc/nagios/objects/hosts.cfg
define host{                   
use                     linux-server
host_name               mysql.server
alias                   10.11.12.80
address                 10.11.12.80
}

3) 增加被监控服务

# vi /etc/nagios/objects/services.cfg
define service{
use                     local-service
host_name               mysql.server
service_description     nrpe_disk
check_command           check_nrpe!check_disk
notifications_enabled   1
}

define service{
use                     local-service
host_name               mysql.server
service_description     nrpe_load
check_command           check_nrpe!check_load
notifications_enabled   1
}
上面配置内的check_load和check_disk是被监控端Npre的配置文件(command[check_load])内定义好的命令。最后使配置生效:
# nagios –v /etc/nagios/nagios.cfg  #配置文件的语法检查
# service nagios reload

8. 监控Web及Tomcat服务

监控Web用Tomcat服务可用nagios自带的插件check_http。
# vi commands.cfg
增加:
# ‘check_tomcat’ command definition
define command{
command_name    check_tomcat
command_line    $USER1$/check_http -I $HOSTADDRESS$ -p 8080 $ARG1$
}
# ‘check_http’ command definition
define command{
command_name    check_http
command_line    $USER1$/check_http -I $HOSTADDRESS$ -H $HOSTADDRESS$ $ARG1$
}

# vi services.cfg
增加:
define service{
use                     local-service
host_name               web1.ihompy.com
hostgroup_name          web-servers
service_description     check-http
check_command           check_http
max_check_attempts      3
normal_check_interval   3
retry_check_interval    1
check_period            24×7
notification_interval   60
notification_period     24×7
notification_options    w,u,c,r
}
define service{
use                     local-service
host_name               l7ejb,l7admin,l7web,l7ds
#        hostgroup_name          l7-servers
service_description     check-tomcat
check_command           check_tomcat
max_check_attempts      3
normal_check_interval   3
retry_check_interval    1
check_period            24×7
notification_interval   60
notification_period     24×7
notification_options    w,u,c,r
}
被监控端无需配置,让nagios使修改后的配置生效便可。


9. 监控squid

9.1. 下载squid检测脚本

# wget http://workaround.org/squid/nagios-plugin/check_squid
# chmod  755 check_squid
# cp check_squid /usr/lib/nagios/plugins/
这个脚本我用的时候有点问题,出现:
Parsing of undecoded UTF-8 will give garbage when decoding entities at /usr/lib/perl5/vendor_perl/5.8.5/LWP/Protocol.pm line 114.
原因是:
HTML::HeadParser模块在使用parse()方法时,对没有编码的UTF-8会弄混,要保证在传值之前进行适当的编码。
参考:http://www.xinjiezuo.com/blog/?p=43
解决方式是在my $ua = new LWP::UserAgent;下面加入一行:
$ua->parse_head(0);

跳过去就好了。

9.2. 修改配置文件

# vi commands.cfg
增加:
# ‘squid’ command definition
define command {
command_name check_squid
command_line $USER1$/check_squid ‘$ARG1$’ ‘$ARG2$’ ‘$ARG3$’ $HOSTADDRESS$ ‘$ARG4$’ ‘$ARG5$’ ‘$ARG6$’ ‘$ARG7$’
}

# vi services.cfg
增加:
define service {
use                     local-service
host_name               squid1.ihompy.com
service_description     check-squid
check_command           check_squid!http://www.ihompy.com!-!-!80!-!-!2
max_check_attempts      3
normal_check_interval   3
retry_check_interval    1
check_period            24×7
notification_interval   60
notification_period     24×7
notification_options    w,u,c,r
}
被监控端无需配置,让nagios使修改后的配置生效便可。

10. 监控mysql及mssql服务

监控mysql服务可用nagios自带的插件check_mysql。Nagios也带有一个check_mssql用来监控sql server,不过要先安装freetds。
# yum install freetds
# vi commands.cfg
增加:
# ‘mysql’  command definition
define command{
command_name check_mysql
command_line $USER1$/check_mysql -H $HOSTADDRESS$ -u $ARG1$ -p $ARG2$
}
# ‘check_mssql’ command definition
define command{
command_name    check_mssql
command_line    $USER2$/check_mssql.sh $HOSTADDRESS$ $ARG1$ $ARG2$ $ARG3$
}

# vi services.cfg
增加:

define service{
use                     local-service
host_name               mysql.ihompy.com
hostgroup_name          mysql-servers
service_description     check-mysql
check_command           check_mysql!root!1qaz2wsx
max_check_attempts      3
normal_check_interval   3
retry_check_interval    1
check_period            24×7
notification_interval   60
notification_period     24×7
notification_options    w,u,c,r
}
define service{
use                     local-service
host_name               sqlserver
#        hostgroup_name          backup-servers
service_description     check_sqlserver
check_command           check_mssql!sa!”!2000
notifications_enabled   1
}
被监控端无需配置,让nagios使修改后的配置生效便可。

11. 配置报警方式及联系人

Nagios可以有很多报警方式,比如:E-mail,短信,MSN等。
目前短信方式主要是用中国移动的飞信客户端及购买短信猫两种方式。前者目前是免费的,后者需一点费用买短信猫及手机卡,不过代价也不高,一共不到200RMB。
MSN因为其自身的原因,不太稳定。
我们使用的是一个折中的方式,在Nagios上配置的是E-mail方式,但使用的是中国移动的139邮箱,139邮箱在收到邮件后可免费发短信给用户。这样就邮箱和短信就都有了,目前观察下来稳定性还不错。

11.1. 配置联系人

# vi contacts.cfg
define contact{
contact_name                    luohui         
use                             generic-contact
alias                           Nagios Admin   
email                           farmer.luo@139.com 
pager                           13761802324324
address1                        huilinux@hotmail.com
}

define contact{
contact_name                    xuyong       
use                             generic-contact
alias                           Nagios Admin   
email                           xuyong76@139.com
pager                           133434323443
address1                        xuyong@newsky.sh
}
define contactgroup{
contactgroup_name       admins
alias                   Nagios Administrators
members                 luohui,xuyong
}
先定义两个联系人luohui和xuyong,再把它们加入到联系人组admins。

11.2 配置报警命令

默认已经配置好了,在command.cfg中的以下这几行:

# ‘notify-host-by-email’ command definition
define command{
command_name    notify-host-by-email
command_line    /usr/bin/printf "%b" "***** Nagios *****nnNotification Type: $NOTIFICATIONTYPE$nHost: $HOSTNAME$n
State: $HOSTSTATE$nAddress: $HOSTADDRESS$nInfo: $HOSTOUTPUT$nnDate/Time: $LONGDATETIME$n" | /bin/mail -s "** $NOTIFICATI
ONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$
}

# ‘notify-service-by-email’ command definition
define command{
command_name    notify-service-by-email
command_line    /usr/bin/printf "%b" "***** Nagios *****nnNotification Type: $NOTIFICATIONTYPE$nnService: $SERVIC
EDESC$nHost: $HOSTALIAS$nAddress: $HOSTADDRESS$nState: $SERVICESTATE$nnDate/Time: $LONGDATETIME$nnAdditional Info:nn
$SERVICEOUTPUT$" | /bin/mail -s "** $NOTIFICATIONTYPE$ Service Alert: $HOSTALIAS$/$SERVICEDESC$ is $SERVICESTATE$ **" $CONTAC
TEMAIL$
}

11.3. 在主机和服务中启用E-mail报警

因为我们配置的主机及服务都是使用了模板方式的,所以只要改了模板文件的配置,所有的主机和服务就都改了。
# vi templates.cfg
define contact{
name                            generic-contact          
service_notification_period     24×7               
host_notification_period        24×7                   
service_notification_options    w,u,c,r,f,s            
host_notification_options       d,u,r,f,s              
service_notification_commands   notify-service-by-email
host_notification_commands      notify-host-by-email   
register                        0                      
}

在主机及服务模板内,把contact_groups都改成联系人组admins:
contact_groups                  admins 

12. 结语

上面只列举了几个常用的服务做为例子说明。Nagios可监控的服务很多,如dns,pop,smtp等等。也可以自己写脚本检测一些特殊的服务。
目前用Nagios监控系统监控了近40台服务器,100多项服务。运行有半个月左右,已经成功报过几次警,用起来很顺手,非常不错。

GlusterFS分布式集群文件系统安装、配置及性能测试

前几天和天涯的刘天斯在讨论分布式文件系统,才想起电脑内还有一篇一年前写的文档,现在帖在这里,给有需要的朋友看看,因为当时是用word写的,帖在这边排版不是很好。大家凑合着看吧。

1.    版本历史

Revision    Author(s)    Date    Summary of activity
1.0             罗辉         2009-6-1    创建本文档

2.    参考文档

[1] http:// www.gluster.org
[2] http://wenzizone.cn/?p=8

3.    前言

Glusterfs是一个具有可以扩展到几个PB数量级的分布式集群文件系统。它可以把多个不同类型的存储块通过Infiniband RDMA或者TCP/IP汇聚成一个大的并行网络文件系统。
考虑到公司图片服务器后期的升级,我们对Glusterfs进行了比较详细的技术测试。

4.    测试环境

我们采用八台老的至强服务器组成了测试环境,配置为内存1-2G不等,每台有两块以上的73G SCSI硬盘。
同时每服务器配有两块网卡,连接至两个100M以太网交换机上。192.168.1.x段连接的是cisco 2950,另一个段是一个d-link交换机,服务器间的传输主要是通过cisco 2950,以保证网络的稳定性。

IP地址分别为:192.168.1.11~192.168.1.18 及 192.168.190.11~192.168.190~18。

所有服务器的操作系统都是Centos linux 5.3,安装DAG RPM Repository的更新包。DAG RPM Repository下载页面为:http://dag.wieers.com/rpm/packages/rpmforge-release/。
安装方式:

# wget http://dag.wieers.com/rpm/packages/rpmforge-release/rpmforge-release-0.3.6-1.el5.rf.i386.rpm
# rpm –ivh rpmforge-release-0.3.6-1.el5.rf.i386.rpm

5.    GlusterFS的安装

5.1.    服务器端安装

我们通过rpm编译方式来安装GlusterFS,因为做为群集文件系统,可能需要在至少10台以上的服务器上安装GlusterFS。每台去源码编译安装太费功夫,缺乏效率。在一台编译为rpm包,再复制到其它的服务器上安装是最好的选择。

GlusterFS需要fuse支持库,需先安装:
# yum -y install fuse fuse-devel httpd-devel libibverbs-devel

下载GlusterFS源码编译rpm包。
# wget http://ftp.gluster.com/pub/gluster/glusterfs/2.0/LATEST/glusterfs-2.0.0.tar.gz
# tar -xvzf glusterfs-2.0.0.tar.gz
# cp glusterfs-2.0.0.tar.gz /usr/src/redhat/SOURCES/
# rpmbuild -bb glusterfs-2.0.0/glusterfs.spec
# cp /usr/src/redhat/RPMS/i386/glusterfs* .
# rm glusterfs-debuginfo-2.0.0-1.i386.rpm
# rpm -ivh glusterfs-*.rpm
安装完成,并把编译好的rpm包复制到其它服务器上安装。

5.2.    客户端安装

客户端和服务器有一点点不同,特别需要注意的是在客户端这边,不但需要fuse库,并且需要一个fuse内核模块。好在DAG RPM Repository内已有用DKMS方式编译好的内核模块包,我们直接安装便可。
DKMS(Dynamic Kernel Module Support)是dell发起的一个项目,目的是希望能在不编译内核的情况下,动态的更新内核模块,最重要的是,通过DKMS方式编译的内核模块,由于是由DKMS管理的,在内核升级后,无需重新编译,仍旧可用。这种方式可大大方便以后的内核更新。
GlusterFS可直接用上面rpm编译后的包安装:

# yum -y install dkms dkms-fuse fuse fuse-devel httpd-devel libibverbs-devel
# rpm -ivh glusterfs-*.rpm

6.    GlusterFS的典型架构图

7.    GlusterFS常用translators(中继)

7.1.1.    storage/posix

type storage/posix
storage/posix的作用是指定一个本地目录给GlusterFS内的一个卷使用。
配置例子:
volume posix-example
type storage/posix
option directory /sda4
end-volume

7.1.2.    protocol/server (服务器)

type protocol/server
服务器中继(protocol/server)表示本节点在GlusterFS中为服务器模式。
配置例子:
volume server-example
type protocol/server
option transport-type tcp
subvolumes brick                #定义好的卷
option auth.addr.brick.allow *  #指定可访问本卷的访问者,*为所有,可对访问者做限制,如192.168.1.*
end-volume

7.1.3.    protocol/client (客户端)

type protocol/client
客户端中继(protocol/server)用于客户端连接服务器时使用。
配置例子:
volume client-example
type protocol/client
option transport-type tcp
option remote-host 192.168.1.13    #连接的服务器
option remote-subvolume brick      #连接的服务器卷名
end-volume

7.1.4.    cluster/replicate(复制)

type cluster/replicate
复制中继(cluster/replicate,前身是AFR)为GlusterFS提供了类似RAID-1的功能。
Replicate会复制文件或者文件夹到各个subvolumes里。如一个卷(volume)内有两个子卷(subvolume),那就会有两份文件或文件夹的复本。
Replicate只时还有高可用的功能,如果两个子卷中有一个子卷挂了,卷依然可以正常工作。当这个子卷重新启用时,会自动更新丢失的文件或文件夹,不过更新是通过客户端进行的。
配置例子:
volume replicate-example
type cluster/replicate
subvolumes brick3 brick4
end-volume

7.1.5.    cluster/distribute (分布式)

type cluster/distribute
分布式中继(cluster/distribute,前身是unify)为GlusterFS提供了类似RAID-0的功能。
Distribute可把两个卷或子卷组成一个大卷,实现多存储空间的聚合
配置例子:
volume distribute-example
type cluster/distribute
subvolumes repl1 repl2
end-volume

7.1.6.    features/locks (锁)

type features/locks
锁中继(features/locks)只能用于服务器端的posix中继之上,表示给这个卷提供加锁(fcntl locking)的功能。
配置例子:
volume locks-example
type features/locks
subvolumes posix-example
end-volume

7.1.7.    performance/read-ahead (预读)

type performance/read-ahead
预读中继(performance/read-ahead)属于性能调整中继的一种,用预读的方式提高读取的性能。
读取操作前就预先抓取数据。这个有利于应用频繁持续性的访问文件,当应用完成当前数据块读取的时候,下一个数据块就已经准备好了。
额外的,预读中继也可以扮演读聚合器,许多小的读操作被绑定起来,当成一个大的读请求发送给服务器。
预读处理有page-size和page-count来定义,page-size定义了,一次预读取的数据块大小,page-count定义的是被预读取的块的数量
不过官方网站上说这个中继在以太网上没有必要,一般都能跑满带宽。主要是在IB-verbs或10G的以太网上用。
配置例子:
volume readahead-example
type performance/read-ahead
option page-size  256         # 每次预读取的数据块大小
option page-count 4           # 每次预读取数据块的数量
option force-atime-update off #是否强制在每次读操作时更新文件的访问时间,不设置这个,访问时间将有些不精确,这个将影响预读转换器读取数据时的那一时刻而不是应用真实读到数据的那一时刻。
subvolumes <x>
end-volume

7.1.8.    performance/write-behind (回写)

type performance/write-behind
回写中继(performance/read-ahead)属于性能调整中继的一种,作用是在写数据时,先写入缓存内,再写入硬盘。以提高写入的性能。
回写中继改善了了写操作的延时。它会先把写操作发送到后端存储,同时返回给应用写操作完毕,而实际上写的操作还正在执行。使用后写转换器就可以像流水线一样把写请求持续发送。这个后写操作模块更适合使用在client端,以期减少应用的写延迟。
回写中继同样可以聚合写请求。如果aggregate-size选项设置了的话,当连续的写入大小累积起来达到了设定的值,就通过一个写操作写入到存储上。这个操作模式适合应用在服务器端,以为这个可以在多个文件并行被写入磁盘时降低磁头动作。
配置例子:
volume write-behind-example
type performance/write-behind
option cache-size 3MB    # 缓存大小,当累积达到这个值才进行实际的写操作
option flush-behind on   # 这个参数调整close()/flush()太多的情况,适用于大量小文件的情况
subvolumes <x>
end-volume

7.1.9.    performance/io-threads (IO线程)

type performance/io-threads
IO线程中继(performance/io-threads)属于性能调整中继的一种,作用是增加IO的并发线程,以提高IO性能。
IO线程中继试图增加服务器后台进程对文件元数据读写I/O的处理能力。由于GlusterFS服务是单线程的,使用IO线程转换器可以较大的提高性能。这个转换器最好是被用于服务器端,而且是在服务器协议转换器后面被加载。
IO线程操作会将读和写操作分成不同的线程。同一时刻存在的总线程是恒定的并且是可以配置的。
配置例子:
volume iothreads
type performance/io-threads
option thread-count 32 # 线程使用的数量
subvolumes <x>
end-volume

7.1.10.    performance/io-cache (IO缓存)

type performance/io-cache
IO缓存中继(performance/io-threads)属于性能调整中继的一种,作用是缓存住已经被读过的数据,以提高IO性能。
IO缓存中继可以缓存住已经被读过的数据。这个对于多个应用对同一个数据多次访问,并且如果读的操作远远大于写的操作的话是很有用的(比如,IO缓存很适合用于提供web服务的环境,大量的客户端只会进行简单的读取文件的操作,只有很少一部分会去写文件)。
当IO缓存中继检测到有写操作的时候,它就会把相应的文件从缓存中删除。
IO缓存中继会定期的根据文件的修改时间来验证缓存中相应文件的一致性。验证超时时间是可以配置的。
配置例子:
volume iothreads
type performance/ io-cache
option cache-size 32MB  #可以缓存的最大数据量
option cache-timeout 1  #验证超时时间,单位秒
option priority   *:0   #文件匹配列表及其设置的优先级
subvolumes <x>
end-volume

7.1.11.    其它中继

其它中继还有
cluster/nufa(非均匀文件存取)
cluster/stripe(条带,用于大文件,分块存储在不用服务器)
cluster/ha(集群)
features/filter(过滤)
features/trash(回收站)
path-converter
quota
老的还有:
cluster/unify(和distribute,可定义不同的调度器,以不同方式写入数据)

8.    GlusterFS配置

8.1.    服务器端配置

服务器为6台,IP分别是192.168.1.11~192.168.1.16。配置为:
# vi /etc/glusterfs/glusterfsd.vol
volume posix
type storage/posix
option directory /sda4
end-volume

volume locks
type features/locks
subvolumes posix
end-volume

volume brick
type performance/io-threads
option thread-count 8
subvolumes locks
end-volume

volume server
type protocol/server
option transport-type tcp
subvolumes brick
option auth.addr.brick.allow *
end-volume
保存后启动GlusterFS:
# service glusterfsd start

8.2.    客户端配置

服务器为192.168.1.17和192.168.1.18:
# vi /etc/glusterfs/glusterfs.vol
volume brick1
type protocol/client
option transport-type tcp
end-volume

volume brick2
type protocol/client
option transport-type tcp
option remote-host 192.168.1.12
option remote-subvolume brick
end-volume

volume brick3
type protocol/client
option transport-type tcp
option remote-host 192.168.1.13
option remote-subvolume brick
end-volume

volume brick4
type protocol/client
option transport-type tcp
option remote-host 192.168.1.14
option remote-subvolume brick
end-volume

volume brick5
type protocol/client
option transport-type tcp
option remote-host 192.168.1.15
option remote-subvolume brick
end-volume

volume brick6
type protocol/client
option transport-type tcp
option remote-host 192.168.1.16
option remote-subvolume brick
end-volume

volume afr1
type cluster/replicate
subvolumes brick1 brick2
end-volume

volume afr2
type cluster/replicate
subvolumes brick3 brick4
end-volume

volume afr3
type cluster/replicate
subvolumes brick5 brick6
end-volume

volume unify
type cluster/distribute
subvolumes afr1 afr2 afr3
end-volume

GlusterFS的主要配置都在客户端上,上面配置文件的意思是把6台服务器分成3个replicate卷,再用这3个replicate卷做成一个distribute,提供应用程序使用。

8.3.    GlusterFS挂载

GlusterFS挂载为在客户端上执行:
# glusterfs -f /etc/glusterfs/glusterfs.vol /gmnt/ -l /var/log/glusterfs/glusterfs.log
-f /etc/glusterfs/glusterfs.vol为指定GlusterFS的配置文件
/gmnt是挂载点
-l /var/log/glusterfs/glusterfs.log为日志
另外,GlusterFS也可以结果fstab或autofs方式开机挂载。挂载后就可以在/gmnt内读写文件了,用法与读写本地硬盘一样。

9.    GlusterFS性能测试

9.1.    单客户端测试

测试1:复制大约2.5G容量 /usr目录至GlusterFS(大部分都是小文件)
测试结果:
glusterfs    1361KB/s
本地硬盘   2533KB/s

测试2: 复制一个3.8G的文件至GlusterFS
测试结果:
glusterfs     2270KB/s
本地硬盘    10198KB/s

测试3:读取测试2复制的大文件(cat xxx.iso > /dev/null)
测试结果:
glusterfs     11.2MB/s(基本跑满100M带宽)
本地硬盘    45.6MB/s

9.2.    双客户端测试

测试1:在两个客户端上同时复制大约2.5G容量 /usr目录至GlusterFS(大部分都是小文件)
测试结果:
192.168.1.17:glusterfs   1438KB/s
192.168.1.18:glusterfs   1296KB/s

测试2: 在两个客户端上同时复制一个3.8G的文件至GlusterFS
测试结果:
192.168.1.17:glusterfs    2269KB/s
192.168.1.18:glusterfs    2320KB/s

9.3.    配置回写功能后的测试

9.3.1.    服务器配置

volume posix
type storage/posix
option directory /sda4
end-volume

volume locks
type features/locks
subvolumes posix
end-volume

volume writebehind
type performance/write-behind
option cache-size   16MB
option flush-behind on
subvolumes locks
end-volume

volume brick
type performance/io-threads
option thread-count 64
subvolumes writebehind
end-volume

volume server
type protocol/server
option transport-type tcp
option auth.addr.brick.allow * # Allow access to "brick" volume
end-volume

9.3.2.    客户端配置

volume brick1
type protocol/client
option transport-type tcp
option remote-host 192.168.1.11      # IP address of the remote brick
option remote-subvolume brick        # name of the remote volume
end-volume

volume brick2
type protocol/client
option transport-type tcp
option remote-host 192.168.1.12
option remote-subvolume brick
end-volume

volume brick3
type protocol/client
option transport-type tcp
option remote-host 192.168.1.13
option remote-subvolume brick
end-volume

volume brick4
type protocol/client
option transport-type tcp
option remote-host 192.168.1.14
option remote-subvolume brick
end-volume

volume brick5
type protocol/client
option transport-type tcp
option remote-host 192.168.1.15
option remote-subvolume brick
end-volume

volume brick6
type protocol/client
option transport-type tcp
option remote-host 192.168.1.16
option remote-subvolume brick
end-volume

volume afr1
type cluster/replicate
subvolumes brick1 brick2
end-volume

volume afr2
type cluster/replicate
subvolumes brick3 brick4
end-volume

volume afr3
type cluster/replicate
subvolumes brick5 brick6
end-volume

volume wb1
type performance/write-behind
option cache-size 2MB
option flush-behind on
subvolumes afr1
end-volume

volume wb2
type performance/write-behind
option cache-size 2MB
option flush-behind on
subvolumes afr2
end-volume

volume wb3
type performance/write-behind
option cache-size 2MB
option flush-behind on
subvolumes afr3
end-volume

volume unify
type cluster/distribute
subvolumes wb1 wb2 wb3
end-volume

9.3.3.    测试

测试:在两个客户端上同时复制大约2.5G容量 /usr目录至GlusterFS(大部分都是小文件)
测试结果:
192.168.1.17:glusterfs   979KB/s
192.168.1.18:glusterfs   1056KB/s

10.    结语

从测试结果看,小文件的写入速度只有1M多,速度过低,好在在多客户端的情况下,写入速度还算平稳。大文件的写入也只有2M。对于做图片服务器来说,只能算勉强够用。
另外在性能调优方面,在我们加上回写后,速度反而有下降。当然也有可能是配置参数不当的原因。
经测试,GlusterFS在高可用方面比较稳定的,基本能达到要求。不过由于在复制模式的更新是通过客户端进行的,当客户端和replicate内的一台服务器同时挂时,会造成数据不同步的情况。需要手动做个列表的动作(ls)才会更新。
GlusterFS作为正式运营环境使用时,还缺乏一些功能,如GlusterFS没有对整个集群的监控和管理程序等。

MongoDB update数据语法

在前面的文章“mongodb 查询的语法”里,我介绍了Mongodb的常用查询语法,Mongodb的update操作也有点复杂,我结合自己的使用经验,在这里介绍一下,给用mongodb的朋友看看,也方便以后自己用到的时候查阅:

注:在这篇文章及上篇文章内讲的语法介绍都是在mongodb shell环境内的,和真正运用语言编程(如java,php等)使用时,在使用方法上会有一些差别,但语法(如查询条件,$in,$inc等)是一样的。

本文是参考官方文档来介绍的,之所以有官方文档还要在这介绍,一方面是就当翻译,毕竟每次要用时去看英文文档比较累,第二是官方文档讲解比较简单,有时光看官方文档不好理解,我在实际操作的情况下可以做些补充。

好了,不多说了,下面正式开始:

mongodb更新有两个命令:

1).update()命令

db.collection.update( criteria, objNew, upsert, multi )

criteria : update的查询条件,类似sql update查询内where后面的
objNew   : update的对象和一些更新的操作符(如$,$inc…)等,也可以理解为sql update查询内set后面的
upsert   : 这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi    : mongodb默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。

例:
db.test0.update( { "count" : { $gt : 1 } } , { $set : { "test2" : "OK"} } ); 只更新了第一条记录
db.test0.update( { "count" : { $gt : 3 } } , { $set : { "test2" : "OK"} },false,true ); 全更新了
db.test0.update( { "count" : { $gt : 4 } } , { $set : { "test5" : "OK"} },true,false ); 只加进去了第一条
db.test0.update( { "count" : { $gt : 5 } } , { $set : { "test5" : "OK"} },true,true ); 全加进去了
db.test0.update( { "count" : { $gt : 15 } } , { $inc : { "count" : 1} },false,true );全更新了
db.test0.update( { "count" : { $gt : 10 } } , { $inc : { "count" : 1} },false,false );只更新了第一条

2).save()命令

db.collection.save( x )

x就是要更新的对象,只能是单条记录。

如果在collection内已经存在一个和x对象相同的"_id"的记录。mongodb就会把x对象替换collection内已经存在的记录,否则将会插入x对象,如果x内没有_id,系统会自动生成一个再插入。相当于上面update语句的upsert=true,multi=false的情况。

例:
db.test0.save({count:40,test1:"OK"}); #_id系统会生成
db.test0.save({_id:40,count:40,test1:"OK"}); #如果test0内有_id等于40的,会替换,否则插入。

mongodb的更新操作符:

1) $inc

用法:{ $inc : { field : value } }

意思对一个数字字段field增加value,例:

> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 16, "test1" : "TESTTEST", "test2" : "OK", "test3" : "TESTTEST", "test4" : "OK", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $inc : { "count" : 1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 17, "test1" : "TESTTEST", "test2" : "OK", "test3" : "TESTTEST", "test4" : "OK", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $inc : { "count" : 2 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 19, "test1" : "TESTTEST", "test2" : "OK", "test3" : "TESTTEST", "test4" : "OK", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $inc : { "count" : -1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : "TESTTEST", "test2" : "OK", "test3" : "TESTTEST", "test4" : "OK", "test5" : "OK" }

2) $set

用法:{ $set : { field : value } }

就是相当于sql的set field = value,全部数据类型都支持$set。例:
> db.test0.update( { "_id" : 15 } , { $set : { "test1" : "testv1","test2" : "testv2","test3" : "testv3","test4" : "testv4" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : "testv1", "test2" : "testv2", "test3" : "testv3", "test4" : "testv4", "test5" : "OK" }

3) $unset

用法:{ $unset : { field : 1} }

顾名思义,就是删除字段了。例:
> db.test0.update( { "_id" : 15 } , { $unset : { "test1":1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test2" : "testv2", "test3" : "testv3", "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $unset : { "test2": 0 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test3" : "testv3", "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $unset : { "test3":asdfasf } } );
Fri May 14 16:17:38 JS Error: ReferenceError: asdfasf is not defined (shell):0

> db.test0.update( { "_id" : 15 } , { $unset : { "test3":"test" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test4" : "testv4", "test5" : "OK" }

没看出field : 1里面的1是干什么用的,反正只要有东西就行。

4) $push

用法:{ $push : { field : value } }

把value追加到field里面去,field一定要是数组类型才行,如果field不存在,会新增一个数组类型加进去。例:

> db.test0.update( { "_id" : 15 } , { $set : { "test1" : ["aaa","bbb"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb" ], "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $push : { "test1": "ccc" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc" ], "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $push : { "test2": "ccc" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc" ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $push : { "test1": ["ddd","eee"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc", [ "ddd", "eee" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }5) $pushAll

用法:{ $pushAll : { field : value_array } }

同$push,只是一次可以追加多个值到一个数组字段内。例:

> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc", [ "ddd", "eee" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $pushAll : { "test1": ["fff","ggg"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc", [ "ddd", "eee" ], "fff", "ggg" ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

6)  $addToSet

用法:{ $addToSet : { field : value } }

增加一个值到数组内,而且只有当这个值不在数组内才增加。例:
> db.test0.update( { "_id" : 15 } , { $addToSet : { "test1": {$each : ["444","555"] } } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"aaa",
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444",
"555"
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $addToSet : { "test1": {$each : ["444","555"] } } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"aaa",
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444",
"555"
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $addToSet : { "test1": ["444","555"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"aaa",
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444",
"555",
[
"444",
"555"
]
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $addToSet : { "test1": ["444","555"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"aaa",
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444",
"555",
[
"444",
"555"
]
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

7) $pop

删除数组内的一个值

用法:
删除最后一个值:{ $pop : { field : 1 } }删除第一个值:{ $pop : { field : -1 } }

注意,只能删除一个值,也就是说只能用1或-1,而不能用2或-2来删除两条。mongodb 1.1及以后的版本才可以用,例:
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444"
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $pop : { "test1": -1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444"
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $pop : { "test1": 1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "ccc", [ "ddd", "eee" ], "fff", "ggg", [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4",
"test5" : "OK" }

8) $pull

用法:$pull : { field : value } }

从数组field内删除一个等于value值。例:
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "ccc", [ "ddd", "eee" ], "fff", "ggg", [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4",
"test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $pull : { "test1": "ggg" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "ccc", [ "ddd", "eee" ], "fff", [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5"
: "OK" }

9) $pullAll

用法:{ $pullAll : { field : value_array } }

同$pull,可以一次删除数组内的多个值。例:
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "ccc", [ "ddd", "eee" ], "fff", [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5"
: "OK" }

> db.test0.update( { "_id" : 15 } , { $pullAll : { "test1": [ "ccc" , "fff" ] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ [ "ddd", "eee" ], [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

10) $ 操作符

$是他自己的意思,代表按条件找出的数组里面某项他自己。呵呵,比较坳口。看一下官方的例子:

> t.find()
{ "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] }

> t.update( {‘comments.by’:’joe’}, {$inc:{‘comments.$.votes’:1}}, false, true )

> t.find()
{ "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }

需要注意的是,$只会应用找到的第一条数组项,后面的就不管了。还是看例子:

> t.find();
{ "_id" : ObjectId("4b9e4a1fc583fa1c76198319"), "x" : [ 1, 2, 3, 2 ] }
> t.update({x: 2}, {$inc: {"x.$": 1}}, false, true);
> t.find();

还有注意的是$配合$unset使用的时候,会留下一个null的数组项,不过可以用{$pull:{x:null}}删除全部是null的数组项。例:
> t.insert({x: [1,2,3,4,3,2,3,4]})
> t.find()
{ "_id" : ObjectId("4bde2ad3755d00000000710e"), "x" : [ 1, 2, 3, 4, 3, 2, 3, 4 ] }
> t.update({x:3}, {$unset:{"x.$":1}})
> t.find()
{ "_id" : ObjectId("4bde2ad3755d00000000710e"), "x" : [ 1, 2, null, 4, 3, 2, 3, 4 ] }

{ "_id" : ObjectId("4b9e4a1fc583fa1c76198319"), "x" : [ 1, 3, 3, 2 ] }

自己写了一个perl脚本检测redis(nagios插件)

放在我的code库内了,http://farmerluo.googlecode.com/files/check_redis.pl

介绍下怎么安装:

脚本用到了perl的Redis库,需要先安装这个:
# perl -MCPAN -e shell
# install Redis

wget http://farmerluo.googlecode.com/files/check_redis.pl

cp check_redis.pl  /etc/nagios/command/

chown cacti.nagios check_redis.pl

在nagios内加入这个插件:
vi /etc/nagios/objects/command.cfg

# ‘check_redis’ command definition
define command{
command_name    check_redis
command_line    /etc/nagios/command/check_redis.pl -h $HOSTADDRESS$ $ARG1$
}

加入一个服务:
vi /etc/nagios/objects/linuxhost.cfg

define service{
use                             generic-service         ; Name of service template to use
host_name                       memcached.ha2,memcached.web2
service_description             redis
check_command                   check_redis
notifications_enabled           1
}

检查下nagios配置是否正解:
nagios -v  /etc/nagios/nagios.cfg

Nagios Core 3.2.1
Copyright (c) 2009-2010 Nagios Core Development Team and Community Contributors
Copyright (c) 1999-2009 Ethan Galstad
Last Modified: 03-09-2010
License: GPL

Website: http://www.nagios.org
Reading configuration data…
Read main config file okay…
Processing object config file ‘/etc/nagios/objects/commands.cfg’…
Processing object config file ‘/etc/nagios/objects/contacts.cfg’…
Processing object config file ‘/etc/nagios/objects/timeperiods.cfg’…
Processing object config file ‘/etc/nagios/objects/templates.cfg’…
Processing object config file ‘/etc/nagios/objects/linuxhosts.cfg’…
Processing object config file ‘/etc/nagios/objects/windows.cfg’…
Read object config files okay…

Running pre-flight check on configuration data…

Checking services…
Checked 32 services.
Checking hosts…
Checked 14 hosts.
Checking host groups…
Checked 4 host groups.
Checking service groups…
Checked 0 service groups.
Checking contacts…
Checked 3 contacts.
Checking contact groups…
Checked 2 contact groups.
Checking service escalations…
Checked 0 service escalations.
Checking service dependencies…
Checked 0 service dependencies.
Checking host escalations…
Checked 0 host escalations.
Checking host dependencies…
Checked 0 host dependencies.
Checking commands…
Checked 29 commands.
Checking time periods…
Checked 5 time periods.
Checking for circular paths between hosts…
Checking for circular host and service dependencies…
Checking global event handlers…
Checking obsessive compulsive processor commands…
Checking misc settings…

Total Warnings: 0
Total Errors:   0

Things look okay – No serious problems were detected during the pre-flight check

没问题,我们重新载入配置。

service nagios reload

再介绍一下用perl写nagios插件需要注意的地方:

  1. 总是要生成一些输出内容;
  2. 加上引用’use utils’并引用些通用模块来输出($TIMEOUT %ERRORS &print_revision &支持等);
  3. 总 是知道一些Perl插件的标准习惯,如:
    1. 退出时总是exit带 着$ERRORS{CRITICAL}、$ERRORS{OK}等;
    2. 使用getopt函数来处理命令行;
    3. 程序处理超 时问题;
    4. 当没有命令参数时要给出可调用print_usage;
    5. 使用标准的命令行选项开关(象-H ‘host’、-V ‘version’等)。

check_redisl.pl代码:

#!/usr/bin/perl

# nagios: -epn

################################################################################
# check_redis – Nagios Plugin for Redis checks.
#
# @author  farmer.luo at gmail.com
# @date    2012-01-15
# @license GPL v2
#
# check_nagios.pl -h <redis host> -p <redis port> -w <warning time> -c <critica time>
#
# Run the script need:
#
# perl -MCPAN -e shell
# install Redis
#
################################################################################

use strict;
use warnings;
use Redis;
use File::Basename;
use utils qw($TIMEOUT %ERRORS &print_revision &support);
use Time::Local;
use vars qw($opt_h); # Redis

                              use vars qw($opt_p); # Redis

                                                            use vars qw($opt_w); # ʱuse vars qw($opt_c); # ʱuse Getopt::Std;

$opt_h = "";
$opt_p = "6379";
$opt_w = 5;
$opt_c = 10;
my $r = "";
my $role = "master";

getopt(‘hpwcd’);

if ( $opt_h eq "" ) {
        help();
        exit(1);
}

my $start = time();

redis_connect();

# print $@;
if ( $@ ) {
        print "UNKNOWN – cann’t connect to redis server:" . $opt_h . ".";
        exit $ERRORS{"UNKNOWN"};
}

#sleep(3);
my $stop = time();

my $run = $stop – $start;

if ( $run > $opt_c ) {

        print "CRITICAL – redis server(" . $opt_h . ") run for " . $run . " seconds!";
        exit $ERRORS{"CRITICAL"};

} elsif ( $run > $opt_w ) {

        print "WARNING – redis server(" . $opt_h . ") run for " . $run . " seconds!";
        exit $ERRORS{"WARNING"};

} else {

        redis_info();

#       print "role = " . $role;

        if ( $role eq "master" ){

                if ( redis_set() ) {
                       print "WARNING – redis server:" . $opt_h . ",set key error.";
                       exit $ERRORS{"WARNING"};
                }

                if ( redis_get() ) {
                       print "WARNING – redis server:" . $opt_h . ",get key error.";
                       exit $ERRORS{"WARNING"};
                }

                if ( redis_del() ) {
                       print "WARNING – redis server:" . $opt_h . ",del key error.";
                       exit $ERRORS{"WARNING"};
                }

        }

        redis_quit();
        exit $ERRORS{"OK"};

}

sub help{

        die "Usage:n" , basename( $0 ) ,  " -h hostname -p port -w warning time -c critical time -d down timen"

}

sub redis_connect{

        my $redis_hp = $opt_h . ":" . $opt_p;

        eval{ $r = Redis->new( server => $redis_hp ); };

}

sub redis_set{

        $r->set( redis_nagios_key => ‘test’ ) || return 1;

        return 0;
}

sub redis_get{

        my $value = $r->get( ‘redis_nagios_key’ ) || return 1;

        return 0;
}

sub redis_del{

        $r->del( ‘redis_nagios_key’ ) || return 1;

        return 0;
}

sub redis_info{

        my $info_hash = $r->info;

        print "OK – redis server(" . $opt_h . ") info:";

        while ( my ($key, $value) = each(%$info_hash) ) {
            print "$key => $value, ";
        }

        my %info = %$info_hash;

        $role = $info{"role"};
}

sub redis_quit{

        $r->quit();

}

参考:
http://nagios-cn.sourceforge.net/nagios-cn/develope.html

如何编写 Nagios 插件

很早就用上了redis,当时没有找到nagios监控redis的插件,好在redis也算稳定,基本没出过什么问题。

最近在网上找,还是没找到,准备自己写个了。过两天再发上来,其实nagios plugin也很简单,只要注意一下程序退出的状态码就行了。

Nagios 每次在查询一个服务的状态时,产生一个子进程,并且它使用来自该命令的输出和退出代码来确定具体的状态。退出状态代码的含义如下所示:

  • OK —退出代码 0—表示服务正常地工作。
  • WARNING —退出代码 1—表示服务处于警告状态。
  • CRITICAL —退出代码 2—表示服务处于危险状态。
  • UNKNOWN —退出代码 3—表示服务处于未知状态。

最后一种状态通常表示该插件无法确定服务的状态。例如,可能出现了内部错误。

下面提供了一个 Python 示例脚本,用于检查 UNIX® 平均负载。它假定 2.0 以上的级别表示警告状态,而 5.0 以上的级别表示危险状态。这些值都采用了硬编码的方式,并且始终使用最近一分钟的平均负载。

清单 5. Python 插件—示例工作插件

#!/usr/bin/env python

import os,sys

(d1, d2, d3) = os.getloadavg()

if d1 >= 5.0:
print "GETLOADAVG CRITICAL: Load average is %.2f" % (d1)
sys.exit(2)
elif d1 >= 2.0:
print "GETLOADAVG WARNING: Load average is %.2f" % (d1)
sys.exit(1)
else:
print "GETLOADAVG OK: Load average is %.2f" % (d1)
sys.exit(0)
参考:http://www.ibm.com/developerworks/cn/aix/library/au-nagios/index.html

MySpace 的六次重构经历(转)

非常好的文章,可以说算是一个比较经典的架构扩展案例,虽然他们在不同时期的做法可能还有提高的地方,值得一读:

在每个里程碑,站点负担都会超过底层系统部分组件的最大载荷,特别是 数据库和存储系统。接着,功能出现问题,用户失声尖叫。最后,技术团队必须为此修订系统策略。

虽然自2005年早期,站点账户数超过7百万后,系统架构到目前为止保持了相对稳 定,但MySpace仍然在为SQL Server支持的同时连接数等方面继续攻坚,Benedetto说,"我们已经尽可能把事情做到最好"。

里程碑一:50万账户

按Benedetto 的说法,MySpace最初的系统很小,只有两台Web服务器和一个数据库服务器。那时使用的是Dell双CPU、4G内存的系统。

单个数据库就意味着所有数据都存储在一个地方,再由两台Web服务器分担处理用户 请求的工作量。但就像MySpace后来的几次底层系统修订时的情况一样,三服务器架构很快不堪重负。此后一个时期内,MySpace基本是通过添置更多 Web服务器来对付用户暴增问题的。

但到在2004年早期,MySpace用户数增长到50万后,数据库服务器也已开 始汗流浃背。

但和Web服务器不同,增加数据库可没那么简单。如果一个站点由多个数据库支持, 设计者必须考虑的是,如何在保证数据一致性的前提下,让多个数据库分担压力。

在第二代架构中,MySpace运行在3个SQL Server数据库服务器上——一个为主,所有的新数据都向它提交,然后由它复制到其他两个;另两个全力向用户供给数据,用以在博客和个人资料栏显示。这 种方式在一段时间内效果很好——只要增加数据库服务器,加大硬盘,就可以应对用户数和访问量的增加。

里程碑二:1-2百万账户

MySpace注册数到达1百万至2百万区间后,数据库服务器开始受制于I/O容 量——即它们存取数据的速度。而当时才是2004年中,距离上次数据库系统调整不过数月。用户的提交请求被阻塞,就像千人乐迷要挤进只能容纳几百人的夜总 会,站点开始遭遇"主要矛盾",Benedetto说,这意味着MySpace永远都会轻度落后于用户需求。

"有人花5分钟都无法完成留言,因此用户总是抱怨说网站已经完蛋了。"他补充道。

这一次的数据库架构按照垂直分割模式设计,不同的数据库服务于站点的不同功能,如 登录、用户资料和博客。于是,站点的扩展性问题看似又可以告一段落了,可以歇一阵子。

垂直分割策略利于多个数据库分担访问压力,当用户要求增加新功能 时,MySpace将投入新的数据库予以支持它。账户到达2百万后,MySpace还从存储设备与数据库服务器直接交互的方式切换到 SAN(Storage Area Network,存储区域网络)——用高带宽、专门设计的网络将大量磁盘存储设备连接在一起,而数据库连接到SAN。这项措施极大提升了系统性能、正常运 行时间和可靠性,Benedetto说。

里程碑三:3百万账户

当用户继续增加到3百万后,垂直分割策略也开始难以为继。尽管站点的各个应用被设 计得高度独立,但有些信息必须共享。在这个架构里,每个数据库必须有各自的用户表副本——MySpace授权用户的电子花名册。这就意味着一个用户注册 时,该条账户记录必须在9个不同数据库上分别创建。但在个别情况下,如果其中某台数据库服务器临时不可到达,对应事务就会失败,从而造成账户非完全创建, 最终导致此用户的该项服务无效。

另外一个问题是,个别应用如博客增长太快,那么专门为它服务的数据库就有巨大压 力。

2004年中,MySpace面临Web开发者称之为"向上扩展"对"向外扩展" (译者注:Scale Up和Scale Out,也称硬件扩展和软件扩展)的抉择——要么扩展到更大更强、也更昂贵的服务器上,要么部署大量相对便宜的服务器来分担数据库压力。一般来说,大型站 点倾向于向外扩展,因为这将让它们得以保留通过增加服务器以提升系统能力的后路。

但成功地向外扩展架构必须解决复杂的分布式计算问题,大型站点如Google、 Yahoo和Amazon.com,都必须自行研发大量相关技术。以Google为例,它构建了自己的分布式文件系统。

另外,向外扩展策略还需要大量重写原来软件,以保证系统能在分布式服务器上运 行。"搞不好,开发人员的所有工作都将白费",Benedetto说。

因此,MySpace首先将重点放在了向上扩展上,花费了大约1个半月时间研究升 级到32CPU服务器以管理更大数据库的问题。Benedetto说,"那时候,这个方案看似可能解决一切问题。"如稳定性,更棒的是对现有软件几乎没有 改动要求。

糟糕的是,高端服务器极其昂贵,是购置同样处理能力和内存速度的多台服务器总和的 很多倍。而且,站点架构师预测,从长期来看,即便是巨型数据库,最后也会不堪重负,Benedetto说,"换句话讲,只要增长趋势存在,我们最后无论如 何都要走上向外扩展的道路。"

因此,MySpace最终将目光移到分布式计算架构——它在物理上分布的众多服务 器,整体必须逻辑上等同于单台机器。拿数据库来说,就不能再像过去那样将应用拆分,再以不同数据库分别支持,而必须将整个站点看作一个应用。现在,数据库 模型里只有一个用户表,支持博客、个人资料和其他核心功能的数据都存储在相同数据库。

既然所有的核心数据逻辑上都组织到一个数据库,那么MySpace必须找到新的办 法以分担负荷——显然,运行在普通硬件上的单个数据库服务器是无能为力的。这次,不再按站点功能和应用分割数据库,MySpace开始将它的用户按每百万 一组分割,然后将各组的全部数据分别存入独立的SQL Server实例。目前,MySpace的每台数据库服务器实际运行两个SQL Server实例,也就是说每台服务器服务大约2百万用户。Benedetto指出,以后还可以按照这种模式以更小粒度划分架构,从而优化负荷分担。

当然,还是有一个特殊数据库保存了所有账户的名称和密码。用户登录后,保存了他们 其他数据的数据库再接管服务。特殊数据库的用户表虽然庞大,但它只负责用户登录,功能单一,所以负荷还是比较容易控制的。

里程碑四:9百万到1千7百万账户

2005年早期,账户达到9百万后,MySpace开始用Microsoft的 C#编写ASP.NET程序。C#是C语言的最新派生语言,吸收了C++和Java的优点,依托于Microsoft .NET框架(Microsoft为软件组件化和分布式计算而设计的模型架构)。ASP.NET则由编写Web站点脚本的ASP技术演化而来,是 Microsoft目前主推的Web站点编程环境。

可以说是立竿见影, MySpace马上就发现ASP.NET程序运行更有效率,与ColdFusion相比,完成同样任务需消耗的处理器能力更小。据技术总监 Whitcomb说,新代码需要150台服务器完成的工作,如果用ColdFusion则需要246台。Benedetto还指出,性能上升的另一个原因 可能是在变换软件平台,并用新语言重写代码的过程中,程序员复审并优化了一些功能流程。

最终,MySpace开始大规模迁移到ASP.NET。即便剩余的少部分 ColdFusion代码,也从Cold-Fusion服务器搬到了ASP.NET,因为他们得到了BlueDragon.NET(乔治亚州阿尔法利塔 New Atlanta Communications公司的产品,它能将ColdFusion代码自动重新编译到Microsoft平台)的帮助。

账户达到1千万时,MySpace再次遭遇存储瓶颈问题。SAN的引入解决了早期 一些性能问题,但站点目前的要求已经开始周期性超越SAN的I/O容量——即它从磁盘存储系统读写数据的极限速度。

原因之一是每数据库1百万账户的分割策略,通常情况下的确可以将压力均分到各台服 务器,但现实并非一成不变。比如第七台账户数据库上线后,仅仅7天就被塞满了,主要原因是佛罗里达一个乐队的歌迷疯狂注册。

某个数据库可能因为任何原因,在任何时候遭遇主要负荷,这时,SAN中绑定到该数 据库的磁盘存储设备簇就可能过载。"SAN让磁盘I/O能力大幅提升了,但将它们绑定到特定数据库的做法是错误的。"Benedetto说。

最初,MySpace通过定期重新分配SAN中数据,以让其更为均衡的方法基本解 决了这个问题,但这是一个人工过程,"大概需要两个人全职工作。"Benedetto说。

长期解决方案是迁移到虚拟存储体系上,这样,整个SAN被当作一个巨型存储池,不 再要求每个磁盘为特定应用服务。MySpace目前采用了一种新型SAN设备——来自加利福尼亚州弗里蒙特的3PARdata。

在3PAR的系统里,仍能在逻辑上按容量划分数据存储,但它不再被绑定到特定磁盘 或磁盘簇,而是散布于大量磁盘。这就使均分数据访问负荷成为可能。当数据库需要写入一组数据时,任何空闲磁盘都可以马上完成这项工作,而不再像以前那样阻 塞在可能已经过载的磁盘阵列处。而且,因为多个磁盘都有数据副本,读取数据时,也不会使SAN的任何组件过载。

当2005年春天账户数达到1千7百万时,MySpace又启用了新的策略以减轻 存储系统压力,即增加数据缓存层——位于Web服务器和数据库服务器之间,其唯一职能是在内存中建立被频繁请求数据对象的副本,如此一来,不访问数据库也 可以向Web应用供给数据。换句话说,100个用户请求同一份资料,以前需要查询数据库100次,而现在只需1次,其余都可从缓存数据中获得。当然如果页 面变化,缓存的数据必须从内存擦除,然后重新从数据库获取——但在此之前,数据库的压力已经大大减轻,整个站点的性能得到提升。

缓存区还为那些不需要记入数据库的数据提供了驿站,比如为跟踪用户会话而创建的临 时文件——Benedetto坦言他需要在这方面补课,"我是数据库存储狂热分子,因此我总是想着将万事万物都存到数据库。"但将像会话跟踪这类的数据也 存到数据库,站点将陷入泥沼。

增加缓存服务器是"一开始就应该做的事情,但我们成长太快,以致于没有时间坐下来 好好研究这件事情。"Benedetto补充道。

里程碑五:2千6百万账户

2005年中期,服务账户数达到2千6百万时,MySpace切换到了还处于 beta测试的SQL Server 2005。转换何太急?主流看法是2005版支持64位处理器。但Benedetto说,"这不是主要原因,尽管这也很重要;主要还是因为我们对内存的渴 求。"支持64位的数据库可以管理更多内存。

更多内存就意味着更高的性能和更大的容量。原来运行32位版本的SQL Server服务器,能同时使用的内存最多只有4G。切换到64位,就好像加粗了输水管的直径。升级到SQL Server 2005和64位Windows Server 2003后,MySpace每台服务器配备了32G内存,后于2006年再次将配置标准提升到64G。

意外错误

如果没有对系统架构的历次修改与升级,MySpace根本不可能走到今天。但是, 为什么系统还经常吃撑着了?很多用户抱怨的"意外错误"是怎么引起的呢?

原因之一是MySpace对Microsoft的Web技术的应用已经进入连 Microsoft自己也才刚刚开始探索的领域。比如11月,超出SQL Server最大同时连接数,MySpace系统崩溃。Benedetto说,这类可能引发系统崩溃的情况大概三天才会出现一次,但仍然过于频繁了,以致 惹人恼怒。一旦数据库罢工,"无论这种情况什么时候发生,未缓存的数据都不能从SQL Server获得,那么你就必然看到一个’意外错误’提示。"他解释说。

去年夏天,MySpace的Windows 2003多次自动停止服务。后来发现是操作系统一个内置功能惹的祸——预防分布式拒绝服务攻击(黑客使用很多客户机向服务器发起大量连接请求,以致服务器 瘫痪)。MySpace和其他很多顶级大站点一样,肯定会经常遭受攻击,但它应该从网络级而不是依靠Windows本身的功能来解决问题——否则,大量 MySpace合法用户连接时也会引起服务器反击。

"我们花了大约一个月时间寻找Windows 2003服务器自动停止的原因。"Benedetto说。最后,通过Microsoft的帮助,他们才知道该怎么通知服务器:"别开枪,是友军。"

紧接着是在去年7月某个周日晚上,MySpace总部所在地洛杉矶停电,造成整个 系统停运12小时。大型Web站点通常要在地理上分布配置多个数据中心以预防单点故障。本来,MySpace还有其他两个数据中心以应对突发事件,但 Web服务器都依赖于部署在洛杉矶的SAN。没有洛杉矶的SAN,Web服务器除了恳求你耐心等待,不能提供任何服务。

Benedetto说,主数据中心的可靠性通过下列措施保证:可接入两张不同电 网,另有后备电源和一台储备有30天燃料的发电机。但在这次事故中,不仅两张电网失效,而且在切换到备份电源的过程中,操作员烧掉了主动力线路。

2007年中,MySpace在另两个后备站点上也建设了SAN。这对分担负荷大 有帮助——正常情况下,每个SAN都能负担三分之一的数据访问量。而在紧急情况下,任何一个站点都可以独立支撑整个服务,Benedetto说。

MySpace仍然在为提高稳定性奋斗,虽然很多用户表示了足够信任且能原谅偶现 的错误页面。

"作为开发人员,我憎恶Bug,它太气人了。"Dan Tanner这个31岁的德克萨斯软件工程师说,他通过MySpace重新联系到了高中和大学同学。"不过,MySpace对我们的用处很大,因此我们可 以原谅偶发的故障和错误。" Tanner说,如果站点某天出现故障甚至崩溃,恢复以后他还是会继续使用。

这就是为什么Drew在论坛里咆哮时,大部分用户都告诉他应该保持平静,如果等几 分钟,问题就会解决的原因。Drew无法平静,他写道,"我已经两次给MySpace发邮件,而它说一小时前还是正常的,现在出了点问题……完全是一堆废 话。"另一个用户回复说,"毕竟它是免费的。"Benedetto坦承100%的可靠性不是他的目标。"它不是银行,而是一个免费的服务。"他说。

换句话说,MySpace的偶发故障可能造成某人最后更新的个人资料丢失,但并不 意味着网站弄丢了用户的钱财。"关键是要认识到,与保证站点性能相比,丢失少许数据的故障是可接受的。"Benedetto说。所以,MySpace甘冒 丢失2分钟到2小时内任意点数据的危险,在SQL Server配置里延长了"checkpoint"操作——它将待更新数据永久记录到磁盘——的间隔时间,因为这样做可以加快数据库的运行。

Benedetto说,同样,开发人员还经常在几个小时内就完成构思、编码、测试 和发布全过程。这有引入Bug的风险,但这样做可以更快实现新功能。而且,因为进行大规模真实测试不具可行性,他们的测试通常是在仅以部分活跃用户为对 象,且用户对软件新功能和改进不知就里的情况下进行的。因为事实上不可能做真实的加载测试,他们做的测试通常都是针对站点。

"我们犯过大量错误,"Benedetto说,"但到头来,我认为我们做对的还是 比做错的多。"

MySpace Tech Roster

January 16, 2007

By David F. Carr