TOMCATE 安装

2010年3月16日 没有评论

      安装Tomcat之前要先安装JDK,可从http://java.sun.com上下载最新版本的JDK。Tomcat可从Apache Jakarta Project站点(http://jakarta.apache.org/site/binindex.cgi)上下载,本书使用的Tomcat版本是5.5.7,它需要安装J2SE 5.0(JDK 1.5)以上的版本才能运行。对于Windows操作系统,Tomcat 5.5.7提供了两种安装文件,一种是jakarta-tomcat-5.5.7.exe,一种是jakarta-tomcat-5.5.7.zip(如果读者使用的是Linux系统,请下载jakarta-tomcat-5.5.7.tar.gz)。jakarta-tomcat-5.5.7.exe是可执行的安装程序,读者只需要双击这个文件,就可以开始安装Tomcat了。在安装过程中,安装程序会自动搜寻JDK和JRE的位置。安装完成后,在Windows系统的“开始”->“程序”菜单下会添加Apache Tomcat 5.5菜单组。jakarta-tomcat-5.5.7.zip是一个压缩包,只需要将它解压到硬盘上就可以了。在这里,我建议读者下载jakarta-tomcat-5.5.7.zip压缩包,通过解压缩的方式安装Tomcat,因为解压缩的方式也适用于其他的操作系统,例如Linux系统。下面我们主要介绍jakarta-tomcat-5.5.7.zip的安装与Tomcat运行环境的设置。

安装Tomcat

使用WinZip或WinRAR等解压缩工具将jakarta-tomcat-5.5.7.zip解压到指定的驱动器和目录中。笔者是在D盘上直接解压,产生了目录jakarta-tomcat-5.5.7,解压后的文件存放于D:\ jakarta-tomcat-5.5.7下。

Tomcat安装后的目录层次结构如图5-2所示。

图5-2 Tomcat 5.5.7目录层次结构

各目录的用途如表5-1所示。

表5-1 Tomcat的目录结构及其用途

/bin

存放启动和关闭Tomcat的脚本文件

/common/lib

存放Tomcat服务器及所有Web应用程序都可以访问的JAR文件

/conf

存放Tomcat服务器的各种配置文件,其中包括server.xmlTomcat的主要配置文件)、tomcat-users.xmlweb.xml等配置文件

/logs

存放Tomcat的日志文件

/server/lib

存放Tomcat服务器运行所需的各种JAR文件

/server/webapps

存放Tomcat的两个Web应用程序:admin应用程序和manager应用程序

/shared/lib

存放所有Web应用程序都可以访问的JAR文件

/temp

存放Tomcat运行时产生的临时文件

/webapps

当发布Web应用程序时,通常把Web应用程序的目录及文件放到这个目录下

/work

TomcatJSP生成的Servlet源文件和字节码文件放到这个目录下

从表5-1中可以看到,/common/lib目录、/server/lib和/shared/lib目录下都可以存放JAR文件,它们的区别在于:

— 在/server/lib目录下的JAR文件只能被Tomcat服务器访问;

— 在/shared/lib目录下的JAR文件可以被所有的Web应用程序访问,但不能被Tomcat服务器访问;

— 在/common/lib目录下的JAR文件可以被Tomcat服务器和所有的Web应用程序访问。

此外,对于后面将要介绍的Java Web应用程序,在它的WEB-INF目录下,也可以建立lib子目录,在lib子目录下可以存放各种JAR文件,这些JAR文件只能被当前Web应用程序所访问。

运行Tomcat

在Tomcat安装目录下的bin子目录中,有一些批处理文件(以.bat作为后缀名的文件),其中的startup.bat就是启动Tomcat的脚本文件,用鼠标双击这个文件,将会看到如图5-3所示的画面。

图5-3 运行Tomcat提示出错信息

笔者以前碰到过很多学员,在初次运行Tomcat时,看到如图5-3所示的信息就不知所措了。有的学员以前还配置过Tomcat,但是再次使用的时候,由于忘记了上次是如何配置的,同样感觉无从下手。

我们在学习软件开发时,一定要养成查看错误提示信息,进而根据错误提示解决问题的良好习惯。笔者第一次配置Tomcat时,就是根据错误提示信息一步一步配置成功的。很多人一看见错误信息,立即单击“确定”按钮,这样就错过了提示信息。当看到错误信息时,首先不要慌张和无所适从,仔细看清楚错误提示,不要着急单击按钮。

查看图5-3中的错误提示信息,可以看到这样一句话“The JAVA_HOME environment variable is not defined”,从画面中可以看到,在执行到“Using JAVA_HOME”这句时出现了错误,由此,我们可以想到,出错的原因可能是因为没有设置JAVA_HOME环境变量。那么JAVA_HOME环境变量的值应该是什么呢?很容易就能想到应该是JDK所在的目录,在笔者的机器上,JDK所在的目录是D:\Java\jdk1.5.0_01。

在Windows 2000操作系统下设置环境变量的步骤如下。

① 在桌面“我的电脑”上单击右键,选择“属性”,出现如图5-4所示的画面。

图5-4 “我的电脑”属性

② 单击“高级”选项卡,选择“环境变量(E)…”,如图5-5和图5-6所示。

图5-5 “高级”选项卡 图5-6 “环境变量”对话框

③ 在“系统变量”下方单击“新建”按钮。在“变量名”中输入“JAVA_HOME”,在变量值中输入JDK所在的目录“D:\Java\jdk1.5.0_01”,然后单击“确定”按钮,如图5-7所示。

图5-7 新建JAVA_HOME环境变量

④ 最后在“环境变量”对话框上单击“确定”按钮,结束JAVA_HOME环境变量的设置。

我们再一次转到D:\ jakarta-tomcat-5.5.7\bin目录下,用鼠标双击startup.bat文件,可以看到如图5-8所示的启动信息。

图5-8 Tomcat启动信息

然后,打开浏览器,在地址栏中输入http://localhost:8080/(localhost表示本地机器,8080是Tomcat默认监听的端口号),将出现如图5-9所示的Tomcat页面。

图5-9 Tomcat的默认主页

注意图5-9中鼠标(小手形状)指向的链接——Tomcat Documentation,单击将进入Tomcat的文档页面,有关Tomcat的帮助信息可以在文档页面中找到;读者也可以直接访问Tomcat的文档,文档首页的位置是Tomcat安装目录下的webapps\tomcat-docs\index.html。如果要关闭Tomcat服务器,可以用鼠标双击D:\ jakarta-tomcat-5.5.7\bin目录下的shutdown.bat文件。

如果你机器上的Tomcat启动失败,有可能是因为TCP的8080端口被其他应用程序所占用,如果你知道是哪一个应用程序占用了8080端口,那么先关闭此程序。如果你不知道或者不想关闭占用8080端口的应用程序,你可以修改Tomcat默认监听的端口号。

前面介绍了,Tomcat安装目录下的conf子目录用于存放Tomcat服务器的各种配置文件,其中的server.xml是Tomcat的主要配置文件,这是一个格式良好的XML文档,在这个文件中可以修改Tomcat默认监听的端口号。用UltraEdit(你可以用记事本程序或其他的文本编辑工具)打开server.xml,找到修改8080端口的地方。读者也许要问了,“这个配置文件,我都不熟悉,怎么知道在哪里修改端口号呢?”对于初次接触server.xml的读者,确实不了解这个文件的结构,但是我们应该有一种开放的思路,既然Tomcat的监听端口号是在server.xml中配置,那么只要我们在这个文件中查找“8080”这些数字字符序列,不就能找到修改端口号的地方了吗!在UltraEdit中,同时按下键盘上的“Ctrl”和“F”键,出现如图5-10所示的查找对话框。

图5-10 UltraEdit查找对话框

然后在“查找内容”中输入“8080”,单击“查找下一个”按钮。重复这个过程,直到找到如图5-11所示的在server.xml中配置端口号位置。

图5-11 server.xml中配置端口号的位置

找到后,如果我们不能确定此处就是修改端口号的地方,也没有关系,可以先尝试着修改一下端口号,然后启动Tomcat,如果启动成功,也就证明了我们修改的地方是正确的。学习时,我们应该养成这种探索并不断实验的精神。在这里,我们可以修改端口号为8000(读者可以根据自己机器的配置选择一个端口号),然后保存。再次启动Tomcat,在Tomcat启动完毕后,打开浏览器,在地址栏中输入http://localhost:8000/(读者根据自己设置的端口号做相应的修改),就可以看到Tomcat的默认主页了。关闭Tomcat服务器时,执行bin目录下的shutdown.bat文件。

Tomcat启动分析

在本节中我们将通过对Tomcat启动过程的分析,来帮助读者更好地理解和掌握Tomcat。

用文本编辑工具打开用于启动Tomcat的批处理文件startup.bat,仔细阅读,可以发现,在这个文件中,首先判断CATALINA_HOME环境变量是否为空,如果为空,就将当前目录设为CATALINA_HOME的值,接着判断当前目录下是否存在bin\catalina.bat,如果文件不存在,将当前目录的父目录设为CATALINA_HOME的值,根据笔者机器上Tomcat安装目录的层次结构,最后CATALINA_HOME的值被设为Tomcat的安装目录。如果环境变量CATALINA_HOME已经存在,则通过这个环境变量调用bin目录下的“catalina.bat start”命令。通过这段分析,我们了解到两个信息,一是Tomcat启动时,需要查找CATALINA_HOME这个环境变量,如果在当前目录下调用startup.bat,Tomcat会自动设置CATALINA_HOME;二是执行startup.bat命令,实际上执行的是“catalina.bat start”命令。

如果我们不是在bin目录作为当前目录时调用startup.bat,就会出现如图5-12所示的错误信息(在bin目录的父目录下调用除外)。

图5-12 在其他目录下启动Tomcat出错

要在其他目录下也能启动Tomcat,就需要设置CATALINA_HOME环境变量,你可以将CATALINA_HOME添加到Windows 2000系统的环境变量中,其值就是Tomcat的安装目录,在笔者的机器上安装目录是D:\jakarta-tomcat-5.5.7,添加环境变量的过程和前述添加JAVA_HOME环境变量的过程是一样的。如果你不想在系统的环境变量中添加,也可以直接在startup.bat文件中进行设置。下面是在startup.bat文件中设置CATALINA_HOME后的文件片段:

……

rem $Id: shutdown.bat,v 1.5 2004/05/27 15:05:01 yoavs Exp $

rem —————————————————————————

set CATALINA_HOME=D:\jakarta-tomcat-5.5.7

rem Guess CATALINA_HOME if not defined

set CURRENT_DIR=%cd%

if not "%CATALINA_HOME%" == "" goto gotHome

set CATALINA_HOME=%CURRENT_DIR%

……

注意以粗体显示的这句话的作用就是设置CATALINA_HOME环境变量。在它的下面就可以判断CATALINA_HOME是否为空了。如果你找不准位置,干脆将设置CATALINA_HOME环境变量的这句话放置到文件的第一行。JAVA_HOME环境变量也可以采用同样的方式进行设置。不过,如果你要在其他目录下,利用shutdown.bat来关闭Tomcat服务器,你也需要在shutdown.bat文件中设置CATALINA_HOME和JAVA_HOME这两个环境变量,设置变量的位置和startup.bat文件一样,都是在判断CATALINA_HOME是否为空之前。当然,为了一劳永逸,避免重装Tomcat后还要进行设置(需要是同一版本的Tomcat安装在同一位置),我们最好还是将CATALINA_HOME和JAVA_HOME这两个环境变量添加到Windows 2000系统的环境变量中。

有的读者可能会对设置Tomcat安装目录的环境变量的名字是CATALINA_HOME而感到奇怪,按照以前设置的环境变量来看,JAVA_HOME表示JDK的安装目录,那么应该用TOMCAT_HOME来表示Tomcat的安装目录,可为什么要使用CATALINA_HOME呢?实际上,在Tomcat 4以前,用的就是TOMCAT_HOME来表示Tomcat的安装目录,在Tomcat 4以后,采用了新的Servlet容器Catalina,所以环境变量的名字也改为了CATALINA_HOME。

提示:在Windows系统下环境变量的名字是与大小写无关的,也就是说JAVA_HOME和java_home是相同的。

了解了startup.bat文件以后,我们再来看看真正负责启动Tomcat服务器的catalina.bat文件。通过分析catalina.bat文件,我们发现它还调用了一个文件setclasspath.bat。在setclasspath.bat文件中,它检查JAVA_HOME环境变量是否存在,并通过设置的环境变量JAVA_HOME,找到java.exe,用于启动Tomcat。在这个文件中,还设置了其他的一些变量,分别表示JDK中的一些工具,有兴趣的读者可以自行分析一下这个文件。在执行完setclasspath.bat之后,catalina.bat剩下的部分就开始了Tomcat服务器的启动进程。

直接执行catalina.bat时,需要带上命令行的参数。读者可以在命令提示符窗口下,执行catalina.bat,就会打印出catalina.bat命令的各种参数及其含义,如图5-13所示。

图5-13 catalina.bat的各参数信息

其中常用的参数是start、run和stop,参数start表示在一个单独的窗口中启动Tomcat服务器,参数run表示在当前窗口中启动Tomcat服务器,参数stop表示关闭Tomcat服务器。我们执行startup.bat,实际上执行的就是“catalina.bat start”命令;执行shutdown.bat,实际上执行的是“catalina.bat stop”命令。“catalina.bat run”命令有时候是非常有用的,特别是当我们需要查看Tomcat的出错信息时。我们在开发JSP程序时,经常会碰到自己机器上的8080端口号被别的应用程序占用,或者在配置server.xml时出现错误,当通过startup.bat(相当于执行“catalina.bat start”)启动Tomcat服务器时,会导致启动失败,因为是在单独的窗口中启动Tomcat服务器,所以一旦启动失败,命令提示符窗口就自动关闭了,程序运行中输出的出错信息也随之消失,而且没有任何的日志信息,这就使得我们没有办法找出错误原因。当出现错误时,我们可以换成“catalina.bat run”命令再次启动,一旦启动失败,仅仅是Tomcat服务器异常终止,但是在当前的命令提示符窗口下仍然保留了启动时的出错信息,这样我们就可以查找启动失败的原因了

Tomcat的体系结构

Tomcat服务器是由一系列可配置的组件构成的,其中核心组件是Catalina Servlet容器,它是所有其他Tomcat组件的顶层容器。Tomcat各组件之间的层次关系如图5-14所示。

图5-14 Tomcat组件之间的层次结构

我们下面简单介绍一下各组件在Tomcat服务器中的作用。

(1)Server

Server表示整个的Catalina Servlet容器。Tomcat提供了Server接口的一个默认实现,这通常不需要用户自己去实现。在Server容器中,可以包含一个或多个Service组件。

(2)Service

Service是存活在Server中的内部组件,它将一个或多个连接器(Connector)组件绑定到一个单独的引擎(Engine)上。在Server中,可以包含一个或多个Service组件。Service也很少由用户定制,Tomcat提供了Service接口的默认实现,而这种实现既简单又能满足应用。

(3)Connector

连接器(Connector)处理与客户端的通信,它负责接收客户请求,以及向客户返回响应结果。在Tomcat中,有多个连接器可以使用。

(4)Engine

在Tomcat中,每个Service只能包含一个Servlet引擎(Engine)。引擎表示一个特定的Service的请求处理流水线。作为一个Service可以有多个连接器,引擎从连接器接收和处理所有的请求,将响应返回给适合的连接器,通过连接器传输给用户。用户可以通过实现Engine接口提供自定义的引擎,但通常不需要这么做。

(5)Host

Host表示一个虚拟主机,一个引擎可以包含多个Host。用户通常不需要创建自定义的Host,因为Tomcat给出的Host接口的实现(类StandardHost)提供了重要的附加功能。

(6)Context

一个Contex表示了一个Web应用程序,运行在特定的虚拟主机中。什么是Web应用程序呢?在Sun公司发布的Java Servlet规范中,对Web应用程序做出了如下的定义:“一个Web应用程序是由一组Servlet、HTML页面、类,以及其他的资源组成的运行在Web服务器上的完整的应用程序。它可以在多个供应商提供的实现了Servlet规范的Web容器中运行”。一个Host可以包含多个Context(代表Web应用程序),每一个Context都有一个惟一的路径。用户通常不需要创建自定义的Context,因为Tomcat给出的Context接口的实现(类StandardContext)提供了重要的附加功能。

下面我们通过图5-15来帮助读者更好地理解Tomcat服务器中各组件的工作流程。

图5-15 Tomcat各组件的工作流程

要了解这些组件的其他信息,可以看下面的页面:

%CATALINA_HOME%\webapps\tomcat-docs\architecture\index.html

我们可以在conf目录下的server.xml文件中对这些组件进行配置,读者打开server.xml文件,就可以看到元素名和元素之间的嵌套关系,与Tomcat服务器的组件是一一对应的,server.xml文件的根元素就是server。关于server.xml配置文件中的各元素及其属性的含义,请参见附录C。

在Tomcat中,提供了各组件的接口及其实现类,如果你要替代Tomcat中的某个组件,只需要根据该组件的接口或类的说明,重写该组件,并进行配置即可。图5-16是Tomcat各组件的类图。

在类图的接口名或类名下面是该接口或该类所在的包,这些接口和类都在%CATALINA_HOME%\ server\lib\catalina.jar文件中。对Tomcat服务器的实现感兴趣的读者,可以从http://tomcat.apache.org/上下载Tomcat的源代码。

提示:由于Apache软件基金会并不是一个商业性的组织,所以文档更新的速度有时候跟不上版本更新的速度。在Tomcat 5.5.7中,就可以发现文档与其源码实现有不一致的地方。在Tomcat 5.5.x中,去掉了org.apache.catalina.Connector接口及其相关的实现类,而直接以org.apache.catalina.connector.Connector类来代替。我们在看Tomcat的文档时,最好结合其API文档一起看,这样才能保证了解的信息是完整的和准确的。

Tomcat提供了两个管理程序:admin和manager。其中admin用于管理和配置Tomcat服务器,manager用于管理部署到Tomcat服务器中的Web应用程序。

admin Web应用程序

admin Web应用程序需要单独下载,与Tomcat在同一个下载页面,链接名是Admin zip,下载后的文件名是jakarta-tomcat-5.5.7-admin.zip,解压缩后,覆盖Tomcat安装目录下的同名目录。admin Web应用程序位于%CATALINA_HOME%\server\webapps\admin目录下。

要访问admin Web应用程序,需要添加具有管理员权限的账号,编辑%CATALINA_HOME%\ conf\tomcat-users.xml文件,在<tomcat-users>元素中添加如下内容:

<user username="admin" password="12345678" roles="admin"/>

其中用户名和密码可以根据自己的喜好设置。

启动Tomcat服务器,打开浏览器,在地址栏中输入:

http://localhost:8080/admin/

将出现如图5-17所示的页面。

图5-17 admin Web应用程序的登录界面

也可以在Tomcat的默认主页的左上方单击“Tomcat Administration”链接,进入admin登录页面。输入用户名admin,密码12345678,单击“Login”按钮,将看到如图5-18所示的页面。

图5-18 admin Web应用程序的主页面

在这个页面中,可以进行Tomcat服务器的各项配置。

5.6.2 manager Web应用程序

manager Web应用程序包含在Tomcat的安装包中。和admin程序一样,需要添加访问manager Web应用程序的管理员账号,编辑%CATALINA_HOME%\conf\tomcat-users.xml文件,在<tomcat-users>元素中添加如下内容:

<user username="manager" password="12345678" roles="manager"/>

其中用户名和密码可以根据自己的喜好设置。

启动Tomcat服务器,打开浏览器,在地址栏中输入:

http://localhost:8080/manager/html/

将出现如图5-19所示的页面。

图5-19 manager Web应用程序的登录界面

也可以在Tomcat的默认主页的左上方单击“Tomcat Manager”链接,访问manager程序。输入用户名manager,密码12345678,单击“确定”按钮,将看到如图5-20所示的页面。

图5-20 manager Web应用程序的主页面

在这个页面中,你可以部署、启动、停止、重新加载、卸载Web应用程序。注意在两个圆角矩形框中的路径“/jsp-examples”和“/servlets-examples”,单击这两个路径,将看到Tomcat提供的JSP和Servlet的例子程序,这些程序可以作为学习JSP和Servlet的参考。不过在这两个路径下,只列出了部分的例子程序,完整的JSP和Servlet例子程序位于下面的两个目录中:

%CATALINA_HOME%\webapps\jsp-examples

%CATALINA_HOME%\webapps\servlets-examples

原文:http://hi.baidu.com/shenlvjing/blog/item/46f39c4a7994ab2408f7ef6e.html

分类: 其他 标签:

一致性 hash 算法( consistent hashing )

2010年3月16日 没有评论

     consistent hashing 算法早在 1997 年就在论文 Consistent hashing and random trees 中被提出,目前在 cache 系统中应用越来越广泛;

1 基本场景

比如你有 N 个 cache 服务器(后面简称 cache ),那么如何将一个对象 object 映射到 N 个 cache 上呢,你很可能会采用类似下面的通用方法计算 object 的 hash 值,然后均匀的映射到到 N 个 cache ;

hash(object)%N

一切都运行正常,再考虑如下的两种情况;

1 一个 cache 服务器 m down 掉了(在实际应用中必须要考虑这种情况),这样所有映射到 cache m 的对象都会失效,怎么办,需要把 cache m 从 cache 中移除,这时候 cache 是 N-1 台,映射公式变成了 hash(object)%(N-1) ;

2 由于访问加重,需要添加 cache ,这时候 cache 是 N+1 台,映射公式变成了 hash(object)%(N+1) ;

1 和 2 意味着什么?这意味着突然之间几乎所有的 cache 都失效了。对于服务器而言,这是一场灾难,洪水般的访问都会直接冲向后台服务器;

再来考虑第三个问题,由于硬件能力越来越强,你可能想让后面添加的节点多做点活,显然上面的 hash 算法也做不到。

有什么方法可以改变这个状况呢,这就是 consistent hashing…

2 hash 算法和单调性

Hash 算法的一个衡量指标是单调性( Monotonicity ),定义如下:

单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。

容易看到,上面的简单 hash 算法 hash(object)%N 难以满足单调性要求。

3 consistent hashing 算法的原理

consistent hashing 是一种 hash 算法,简单的说,在移除 / 添加一个 cache 时,它能够尽可能小的改变已存在 key 映射关系,尽可能的满足单调性的要求。

下面就来按照 5 个步骤简单讲讲 consistent hashing 算法的基本原理。

3.1 环形hash 空间

考虑通常的 hash 算法都是将 value 映射到一个 32 为的 key 值,也即是 0~2^32-1 次方的数值空间;我们可以将这个空间想象成一个首( 0)尾( 2^32-1 )相接的圆环,如下面图 1 所示的那样。

circle space

图 1 环形 hash 空间

3.2 把对象映射到hash 空间

接下来考虑 4 个对象 object1~object4 ,通过 hash 函数计算出的 hash 值 key 在环上的分布如图 2 所示。

hash(object1) = key1;

… …

hash(object4) = key4;

object

图 2 4 个对象的 key 值分布

3.3 把cache 映射到hash 空间

Consistent hashing 的基本思想就是将对象和 cache 都映射到同一个 hash 数值空间中,并且使用相同的 hash 算法。

假设当前有 A,B 和 C 共 3 台 cache ,那么其映射结果将如图 3 所示,他们在 hash 空间中,以对应的 hash 值排列。

hash(cache A) = key A;

… …

hash(cache C) = key C;

cache

图 3 cache 和对象的 key 值分布

说到这里,顺便提一下 cache 的 hash 计算,一般的方法可以使用 cache 机器的 IP 地址或者机器名作为 hash 输入。

3.4 把对象映射到cache

现在 cache 和对象都已经通过同一个 hash 算法映射到 hash 数值空间中了,接下来要考虑的就是如何将对象映射到 cache 上面了。

在这个环形空间中,如果沿着顺时针方向从对象的 key 值出发,直到遇见一个 cache ,那么就将该对象存储在这个 cache 上,因为对象和 cache 的 hash 值是固定的,因此这个 cache 必然是唯一和确定的。这样不就找到了对象和 cache 的映射方法了吗?!

依然继续上面的例子(参见图 3 ),那么根据上面的方法,对象 object1 将被存储到 cache A 上; object2 和 object3 对应到 cache C ; object4 对应到 cache B ;

3.5 考察cache 的变动

前面讲过,通过 hash 然后求余的方法带来的最大问题就在于不能满足单调性,当 cache 有所变动时, cache 会失效,进而对后台服务器造成巨大的冲击,现在就来分析分析 consistent hashing 算法。

3.5.1 移除 cache

考虑假设 cache B 挂掉了,根据上面讲到的映射方法,这时受影响的将仅是那些沿 cache B 逆时针遍历直到下一个 cache ( cache C )之间的对象,也即是本来映射到 cache B 上的那些对象。

因此这里仅需要变动对象 object4 ,将其重新映射到 cache C 上即可;参见图 4 。

remove

图 4 Cache B 被移除后的 cache 映射

3.5.2 添加 cache

再考虑添加一台新的 cache D 的情况,假设在这个环形 hash 空间中, cache D 被映射在对象 object2 和 object3 之间。这时受影响的将仅是那些沿 cache D 逆时针遍历直到下一个 cache ( cache B )之间的对象(它们是也本来映射到 cache C 上对象的一部分),将这些对象重新映射到 cache D 上即可。

因此这里仅需要变动对象 object2 ,将其重新映射到 cache D 上;参见图 5 。

add

图 5 添加 cache D 后的映射关系

4 虚拟节点

考量 Hash 算法的另一个指标是平衡性 (Balance) ,定义如下:

平衡性

平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。

hash 算法并不是保证绝对的平衡,如果 cache 较少的话,对象并不能被均匀的映射到 cache 上,比如在上面的例子中,仅部署 cache A 和 cache C 的情况下,在 4 个对象中, cache A 仅存储了 object1 ,而 cache C 则存储了 object2 、 object3 和 object4 ;分布是很不均衡的。

为了解决这种情况, consistent hashing 引入了“虚拟节点”的概念,它可以如下定义:

“虚拟节点”( virtual node )是实际节点在 hash 空间的复制品( replica ),一实际个节点对应了若干个“虚拟节点”,这个对应个数也成为“复制个数”,“虚拟节点”在 hash 空间中以 hash 值排列。

仍以仅部署 cache A 和 cache C 的情况为例,在图 4 中我们已经看到, cache 分布并不均匀。现在我们引入虚拟节点,并设置“复制个数”为2 ,这就意味着一共会存在 4 个“虚拟节点”, cache A1, cache A2 代表了 cache A ; cache C1, cache C2 代表了 cache C ;假设一种比较理想的情况,参见图 6 。

virtual nodes

图 6 引入“虚拟节点”后的映射关系

此时,对象到“虚拟节点”的映射关系为:

objec1->cache A2 ; objec2->cache A1 ; objec3->cache C1 ; objec4->cache C2 ;

因此对象 object1 和 object2 都被映射到了 cache A 上,而 object3 和 object4 映射到了 cache C 上;平衡性有了很大提高。

引入“虚拟节点”后,映射关系就从 { 对象 -> 节点 } 转换到了 { 对象 -> 虚拟节点 } 。查询物体所在 cache 时的映射关系如图 7 所示。

map

图 7 查询对象所在 cache

“虚拟节点”的 hash 计算可以采用对应节点的 IP 地址加数字后缀的方式。例如假设 cache A 的 IP 地址为 202.168.14.241 。

引入“虚拟节点”前,计算 cache A 的 hash 值:

Hash(“202.168.14.241”);

引入“虚拟节点”后,计算“虚拟节”点 cache A1 和 cache A2 的 hash 值:

Hash(“202.168.14.241#1”); // cache A1

Hash(“202.168.14.241#2”); // cache A2

5 小结

Consistent hashing 的基本原理就是这些,具体的分布性等理论分析应该是很复杂的,不过一般也用不到。

http://weblogs.java.net/blog/2007/11/27/consistent-hashing 上面有一个 java 版本的例子,可以参考。

http://blog.csdn.net/mayongzhan/archive/2009/06/25/4298834.aspx 转载了一个 PHP 版的实现代码。

http://www.codeproject.com/KB/recipes/lib-conhash.aspx C语言版本

一些参考资料地址:

http://portal.acm.org/citation.cfm?id=258660

http://en.wikipedia.org/wiki/Consistent_hashing

http://www.spiteful.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing/

http://weblogs.java.net/blog/2007/11/27/consistent-hashing

http://tech.idv2.com/2008/07/24/memcached-004/

http://blog.csdn.net/mayongzhan/archive/2009/06/25/4298834.aspx

分类: 架构 标签:

一致性哈希算法的PHP实现

2010年3月16日 没有评论

       发现了段优秀的代码,读了一下,顺手写了一写注释,防止以后再看的时候忘记。
主要实现了一致性哈希的算法,php界相当优秀的代码。读完心旷神怡。

view plaincopy to clipboardprint?
<?php  
/**
* Flexihash – A simple consistent hashing implementation for PHP.
*  
* The MIT License
*  
* Copyright (c) 2008 Paul Annesley
*  
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*  
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*  
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*  
* @author Paul Annesley
* @link http://paul.annesley.cc/
* @copyright Paul Annesley, 2008
* @comment by MyZ (http://blog.csdn.net/mayongzhan)
*/

/**
* A simple consistent hashing implementation with pluggable hash algorithms.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
class Flexihash  
{  

   /**
    * The number of positions to hash each target to.
    *
    * @var int
    * @comment 虚拟节点数,解决节点分布不均的问题
    */
   private $_replicas = 64;  

   /**
    * The hash algorithm, encapsulated in a Flexihash_Hasher implementation.
    * @var object Flexihash_Hasher
    * @comment 使用的hash方法 : md5,crc32
    */
   private $_hasher;  

   /**
    * Internal counter for current number of targets.
    * @var int
    * @comment 节点记数器
    */
   private $_targetCount = 0;  

   /**
    * Internal map of positions (hash outputs) to targets
    * @var array { position => target, … }
    * @comment 位置对应节点,用于lookup中根据位置确定要访问的节点
    */
   private $_positionToTarget = array();  

   /**
    * Internal map of targets to lists of positions that target is hashed to.
    * @var array { target => [ position, position, … ], … }
    * @comment 节点对应位置,用于删除节点
    */
   private $_targetToPositions = array();  

   /**
    * Whether the internal map of positions to targets is already sorted.
    * @var boolean
    * @comment 是否已排序
    */
   private $_positionToTargetSorted = false;  

   /**
    * Constructor
    * @param object $hasher Flexihash_Hasher
    * @param int $replicas Amount of positions to hash each target to.
    * @comment 构造函数,确定要使用的hash方法和需拟节点数,虚拟节点数越多,分布越均匀,但程序的分布式运算越慢
    */
   public function __construct(Flexihash_Hasher $hasher = null, $replicas = null)  
   {  
       $this->_hasher = $hasher ? $hasher : new Flexihash_Crc32Hasher();  
       if (!emptyempty($replicas)) $this->_replicas = $replicas;  
   }  

   /**
    * Add a target.
    * @param string $target
    * @chainable
    * @comment 添加节点,根据虚拟节点数,将节点分布到多个虚拟位置上
    */
   public function addTarget($target)  
   {  
       if (isset($this->_targetToPositions[$target]))  
       {  
           throw new Flexihash_Exception("Target ‘$target’ already exists.");  
       }  

       $this->_targetToPositions[$target] = array();  

       // hash the target into multiple positions  
       for ($i = 0; $i < $this->_replicas; $i++)  
       {  
           $position = $this->_hasher->hash($target . $i);  
           $this->_positionToTarget[$position] = $target; // lookup  
           $this->_targetToPositions[$target] []= $position; // target removal  
       }  

       $this->_positionToTargetSorted = false;  
       $this->_targetCount++;  

       return $this;  
   }  

   /**
    * Add a list of targets.
    * @param array $targets
    * @chainable
    */
   public function addTargets($targets)  
   {  
       foreach ($targets as $target)  
       {  
           $this->addTarget($target);  
       }  

       return $this;  
   }  

   /**
    * Remove a target.
    * @param string $target
    * @chainable
    */
   public function removeTarget($target)  
   {  
       if (!isset($this->_targetToPositions[$target]))  
       {  
           throw new Flexihash_Exception("Target ‘$target’ does not exist.");  
       }  

       foreach ($this->_targetToPositions[$target] as $position)  
       {  
           unset($this->_positionToTarget[$position]);  
       }  

       unset($this->_targetToPositions[$target]);  

       $this->_targetCount–;  

       return $this;  
   }  

   /**
    * A list of all potential targets
    * @return array
    */
   public function getAllTargets()  
   {  
       return array_keys($this->_targetToPositions);  
   }  

   /**
    * Looks up the target for the given resource.
    * @param string $resource
    * @return string
    */
   public function lookup($resource)  
   {  
       $targets = $this->lookupList($resource, 1);  
       if (emptyempty($targets)) throw new Flexihash_Exception(‘No targets exist’);  
       return $targets[0];  
   }  

   /**
    * Get a list of targets for the resource, in order of precedence.
    * Up to $requestedCount targets are returned, less if there are fewer in total.
    *
    * @param string $resource
    * @param int $requestedCount The length of the list to return
    * @return array List of targets
    * @comment 查找当前的资源对应的节点,
    *         节点为空则返回空,节点只有一个则返回该节点,
    *         对当前资源进行hash,对所有的位置进行排序,在有序的位置列上寻找当前资源的位置
    *         当全部没有找到的时候,将资源的位置确定为有序位置的第一个(形成一个环)
    *         返回所找到的节点
    */
   public function lookupList($resource, $requestedCount)  
   {  
       if (!$requestedCount)  
           throw new Flexihash_Exception(‘Invalid count requested’);  

       // handle no targets  
       if (emptyempty($this->_positionToTarget))  
           return array();  

       // optimize single target  
       if ($this->_targetCount == 1)  
           return array_unique(array_values($this->_positionToTarget));  

       // hash resource to a position  
       $resourcePosition = $this->_hasher->hash($resource);  

       $results = array();  
       $collect = false;  

       $this->_sortPositionTargets();  

       // search values above the resourcePosition  
       foreach ($this->_positionToTarget as $key => $value)  
       {  
           // start collecting targets after passing resource position  
           if (!$collect && $key > $resourcePosition)  
           {  
               $collect = true;  
           }  

           // only collect the first instance of any target  
           if ($collect && !in_array($value, $results))  
           {  
               $results []= $value;  
           }  

           // return when enough results, or list exhausted  
           if (count($results) == $requestedCount || count($results) == $this->_targetCount)  
           {  
               return $results;  
           }  
       }  

       // loop to start – search values below the resourcePosition  
       foreach ($this->_positionToTarget as $key => $value)  
       {  
           if (!in_array($value, $results))  
           {  
               $results []= $value;  
           }  

           // return when enough results, or list exhausted  
           if (count($results) == $requestedCount || count($results) == $this->_targetCount)  
           {  
               return $results;  
           }  
       }  

       // return results after iterating through both "parts"  
       return $results;  
   }  

   public function __toString()  
   {  
       return sprintf(  
           ‘%s{targets:[%s]}’,  
           get_class($this),  
           implode(‘,’, $this->getAllTargets())  
       );  
   }  

   // —————————————-  
   // private methods  

   /**
    * Sorts the internal mapping (positions to targets) by position
    */
   private function _sortPositionTargets()  
   {  
       // sort by key (position) if not already  
       if (!$this->_positionToTargetSorted)  
       {  
           ksort($this->_positionToTarget, SORT_REGULAR);  
           $this->_positionToTargetSorted = true;  
       }  
   }  

}  

/**
* Hashes given values into a sortable fixed size address space.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
interface Flexihash_Hasher  
{  

   /**
    * Hashes the given string into a 32bit address space.
    *
    * Note that the output may be more than 32bits of raw data, for example
    * hexidecimal characters representing a 32bit value.
    *
    * The data must have 0xFFFFFFFF possible values, and be sortable by
    * PHP sort functions using SORT_REGULAR.
    *
    * @param string
    * @return mixed A sortable format with 0xFFFFFFFF possible values
    */
   public function hash($string);  

}  

/**
* Uses CRC32 to hash a value into a signed 32bit int address space.
* Under 32bit PHP this (safely) overflows into negatives ints.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
class Flexihash_Crc32Hasher  
   implements Flexihash_Hasher  
{  

   /* (non-phpdoc)
    * @see Flexihash_Hasher::hash()
    */
   public function hash($string)  
   {  
       return crc32($string);  
   }  

}  

/**
* Uses CRC32 to hash a value into a 32bit binary string data address space.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
class Flexihash_Md5Hasher  
   implements Flexihash_Hasher  
{  

   /* (non-phpdoc)
    * @see Flexihash_Hasher::hash()
    */
   public function hash($string)  
   {  
       return substr(md5($string), 0, 8); // 8 hexits = 32bit  

       // 4 bytes of binary md5 data could also be used, but  
       // performance seems to be the same.  
   }  

}  

/**
* An exception thrown by Flexihash.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
class Flexihash_Exception extends Exception  
{  
}
<?php
/**
* Flexihash – A simple consistent hashing implementation for PHP.
*
* The MIT License
*
* Copyright (c) 2008 Paul Annesley
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author Paul Annesley
* @link http://paul.annesley.cc/
* @copyright Paul Annesley, 2008
* @comment by MyZ (http://blog.csdn.net/mayongzhan)
*/

/**
* A simple consistent hashing implementation with pluggable hash algorithms.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
class Flexihash
{

/**
* The number of positions to hash each target to.
*
* @var int
* @comment 虚拟节点数,解决节点分布不均的问题
*/
private $_replicas = 64;

/**
* The hash algorithm, encapsulated in a Flexihash_Hasher implementation.
* @var object Flexihash_Hasher
* @comment 使用的hash方法 : md5,crc32
*/
private $_hasher;

/**
* Internal counter for current number of targets.
* @var int
* @comment 节点记数器
*/
private $_targetCount = 0;

/**
* Internal map of positions (hash outputs) to targets
* @var array { position => target, … }
* @comment 位置对应节点,用于lookup中根据位置确定要访问的节点
*/
private $_positionToTarget = array();

/**
* Internal map of targets to lists of positions that target is hashed to.
* @var array { target => [ position, position, … ], … }
* @comment 节点对应位置,用于删除节点
*/
private $_targetToPositions = array();

/**
* Whether the internal map of positions to targets is already sorted.
* @var boolean
* @comment 是否已排序
*/
private $_positionToTargetSorted = false;

/**
* Constructor
* @param object $hasher Flexihash_Hasher
* @param int $replicas Amount of positions to hash each target to.
* @comment 构造函数,确定要使用的hash方法和需拟节点数,虚拟节点数越多,分布越均匀,但程序的分布式运算越慢
*/
public function __construct(Flexihash_Hasher $hasher = null, $replicas = null)
{
   $this->_hasher = $hasher ? $hasher : new Flexihash_Crc32Hasher();
   if (!empty($replicas)) $this->_replicas = $replicas;
}

/**
* Add a target.
* @param string $target
* @chainable
* @comment 添加节点,根据虚拟节点数,将节点分布到多个虚拟位置上
*/
public function addTarget($target)
{
   if (isset($this->_targetToPositions[$target]))
   {
    throw new Flexihash_Exception("Target ‘$target’ already exists.");
   }

   $this->_targetToPositions[$target] = array();

   // hash the target into multiple positions
   for ($i = 0; $i < $this->_replicas; $i++)
   {
    $position = $this->_hasher->hash($target . $i);
    $this->_positionToTarget[$position] = $target; // lookup
    $this->_targetToPositions[$target] []= $position; // target removal
   }

   $this->_positionToTargetSorted = false;
   $this->_targetCount++;

   return $this;
}

/**
* Add a list of targets.
* @param array $targets
* @chainable
*/
public function addTargets($targets)
{
   foreach ($targets as $target)
   {
    $this->addTarget($target);
   }

   return $this;
}

/**
* Remove a target.
* @param string $target
* @chainable
*/
public function removeTarget($target)
{
   if (!isset($this->_targetToPositions[$target]))
   {
    throw new Flexihash_Exception("Target ‘$target’ does not exist.");
   }

   foreach ($this->_targetToPositions[$target] as $position)
   {
    unset($this->_positionToTarget[$position]);
   }

   unset($this->_targetToPositions[$target]);

   $this->_targetCount–;

   return $this;
}

/**
* A list of all potential targets
* @return array
*/
public function getAllTargets()
{
   return array_keys($this->_targetToPositions);
}

/**
* Looks up the target for the given resource.
* @param string $resource
* @return string
*/
public function lookup($resource)
{
   $targets = $this->lookupList($resource, 1);
   if (empty($targets)) throw new Flexihash_Exception(‘No targets exist’);
   return $targets[0];
}

/**
* Get a list of targets for the resource, in order of precedence.
* Up to $requestedCount targets are returned, less if there are fewer in total.
*
* @param string $resource
* @param int $requestedCount The length of the list to return
* @return array List of targets
* @comment 查找当前的资源对应的节点,
*         节点为空则返回空,节点只有一个则返回该节点,
*         对当前资源进行hash,对所有的位置进行排序,在有序的位置列上寻找当前资源的位置
*         当全部没有找到的时候,将资源的位置确定为有序位置的第一个(形成一个环)
*         返回所找到的节点
*/
public function lookupList($resource, $requestedCount)
{
   if (!$requestedCount)
    throw new Flexihash_Exception(‘Invalid count requested’);

   // handle no targets
   if (empty($this->_positionToTarget))
    return array();

   // optimize single target
   if ($this->_targetCount == 1)
    return array_unique(array_values($this->_positionToTarget));

   // hash resource to a position
   $resourcePosition = $this->_hasher->hash($resource);

   $results = array();
   $collect = false;

   $this->_sortPositionTargets();

   // search values above the resourcePosition
   foreach ($this->_positionToTarget as $key => $value)
   {
    // start collecting targets after passing resource position
    if (!$collect && $key > $resourcePosition)
    {
     $collect = true;
    }

    // only collect the first instance of any target
    if ($collect && !in_array($value, $results))
    {
     $results []= $value;
    }

    // return when enough results, or list exhausted
    if (count($results) == $requestedCount || count($results) == $this->_targetCount)
    {
     return $results;
    }
   }

   // loop to start – search values below the resourcePosition
   foreach ($this->_positionToTarget as $key => $value)
   {
    if (!in_array($value, $results))
    {
     $results []= $value;
    }

    // return when enough results, or list exhausted
    if (count($results) == $requestedCount || count($results) == $this->_targetCount)
    {
     return $results;
    }
   }

   // return results after iterating through both "parts"
   return $results;
}

public function __toString()
{
   return sprintf(
    ‘%s{targets:[%s]}’,
    get_class($this),
    implode(‘,’, $this->getAllTargets())
   );
}

// —————————————-
// private methods

/**
* Sorts the internal mapping (positions to targets) by position
*/
private function _sortPositionTargets()
{
   // sort by key (position) if not already
   if (!$this->_positionToTargetSorted)
   {
    ksort($this->_positionToTarget, SORT_REGULAR);
    $this->_positionToTargetSorted = true;
   }
}

}

/**
* Hashes given values into a sortable fixed size address space.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
interface Flexihash_Hasher
{

/**
* Hashes the given string into a 32bit address space.
*
* Note that the output may be more than 32bits of raw data, for example
* hexidecimal characters representing a 32bit value.
*
* The data must have 0xFFFFFFFF possible values, and be sortable by
* PHP sort functions using SORT_REGULAR.
*
* @param string
* @return mixed A sortable format with 0xFFFFFFFF possible values
*/
public function hash($string);

}

/**
* Uses CRC32 to hash a value into a signed 32bit int address space.
* Under 32bit PHP this (safely) overflows into negatives ints.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
class Flexihash_Crc32Hasher
implements Flexihash_Hasher
{

/* (non-phpdoc)
* @see Flexihash_Hasher::hash()
*/
public function hash($string)
{
   return crc32($string);
}

}

/**
* Uses CRC32 to hash a value into a 32bit binary string data address space.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
class Flexihash_Md5Hasher
implements Flexihash_Hasher
{

/* (non-phpdoc)
* @see Flexihash_Hasher::hash()
*/
public function hash($string)
{
   return substr(md5($string), 0, 8); // 8 hexits = 32bit

   // 4 bytes of binary md5 data could also be used, but
   // performance seems to be the same.
}

}

/**
* An exception thrown by Flexihash.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
class Flexihash_Exception extends Exception
{
}

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/mayongzhan/archive/2009/06/25/4298834.aspx

分类: PHP 标签:

生产环境下的服务器的Crontab写法

2010年3月15日 没有评论

      虽然关于 Crontab 的介绍到处都是,详细读了一遍这个词条,收获还是有的。Crontab 这个名字来自“chronos”,一个古希腊语, “时间”的意思(以下用法 在生产环境下的服务器非常有用,抚琴煮酒强烈推荐)
常见陷阱

每个SA、DBA 或者是普通的 Unix 用户,在第一次使用 Crontab 的时候都会遇到问题. 运行Crontab 的常见错误包括如下几种:1) 出于测试目的新创建了一条 Cron JOB, 时间间隔必须超过两分钟,否则 JOB将调度不到。如果必须忽略这两分钟的载入配置时间差,可以通过重新启动 Cron Daemon 做到。

2) 从 Crontab 中启动 X Window 程序需要注意的事项:所以要么在程序前初始化“DISPLAY=:0.0″, 要么在应用程序后面追加参数 Cdisplay :0.0

3) 命令中的 % 必须做转义处理: \%.我个人的意见是不要在命令行里带这个参数,干脆写到脚本里,然后调度该脚本即可。

其实我倒是认为使用 Crontab最常见的一个问题往往是因为环境变量不对。经常会看到论坛里有人问:为什么我的 Crontab 创建了不执行? 准备创建一条 Cron JOB的时候,很多人都喜欢在命令行下运行一遍,因为这个时候环境变量是随着 Shell 自动带进来,在 Crontab中则可能因为找不到正确的环境变量,JOB 就不能执行。这个小问题就像出天花,一次教训之后就都记得了。
必须使用的一则技巧

每条 JOB执行完毕之后,系统会自动将输出发送邮件给当前系统用户。日积月累,非常的多,甚至会撑爆整个系统。所以每条 JOB命令后面进行重定向处理是非常必要的: >>/dev/null 2>&1 。前提是对 Job中的命令需要正常输出已经作了一定的处理, 比如追加到某个特定日志文件。附: Crontab 的格式说明如下:

* 逗号(’,’) 指定列表值。如: “1,3,4,7,8″
* 中横线(’-‘) 指定范围值 如 “1-6″, 代表 “1,2,3,4,5,6″
* 星号 (’*’) 代表所有可能的值

Linux(开源系统似乎都可以)下还有个 “/” 可以用. 在 Minute 字段上,*/15 表示每 15分钟执行一次. 而这个特性在商业 Unix ,比如 AIX 上就没有.

## Use the hash sign to prefix a comment
# +—————- minute (0 – 59)
# | +————- hour (0 – 23)
# | | +———- day of month (1 – 31)
# | | | +——- month (1 – 12)
# | | | | +—- day of week (0 – 7) (Sunday=0 or 7)
# | | | | |
# * * * * * command to be executed
推 荐记忆为分 时 日 月 星期


cron来源于希腊单词chronos(意为“时间”),是linux系统下一个自动执行指定任务的程序。例如,你想在每晚睡觉期间创建某些文件或文件夹 的备份,就可以用cron来自动执行。

服务的启动和停止
cron服务是linux的内置服务,但它不会开机自动启动。可以用以下命令启动和停止服务:

/sbin/service crond start
/sbin/service crond stop
/sbin/service crond restart
/sbin/service crond reload
以上1-4行分别为启动、停止、重启服务和重新加载配置。

要把cron设为在开机的时候自动启动,在 /etc/rc.d/rc.local 脚本中加入/sbin/service crond start 即可。

查看、编辑和删除
cron把命令行保存在crontab(cron table)文件里,这个文件通常在 /etc目录下。每个系统用户都可以有自己的crontab(在 /var/spool/cron/ 下)。要查看当前用户的crontab,输入crontab -l;要编辑crontab,输入 crontab -e;要删除crontab,输入 crontab-r。如当前是root身份,要查看/编辑/删除/某用户的crontab,只需在相应的命令后加上 -u USERNAME(如 crontab -e-u USERNAME)即可。crontab文件的默认编辑器是vi,可以输入 export VISUAL=’editor’ 更改默认编辑器。

cron服务每分钟不仅要读一次 /var/spool/cron 目录内的所有文件,还需要读一次/etc/crontab 文件。配置这个文件也能让cron执行任务。使用crontab命令是对用户级任务的配置,而编辑 /etc/crontab文件是对系统级任务的配置。

语法说明
以下是两个cron语句的例子(在 /etc/crontab 文件里)。前者用来晚间备份 /etc目录,后者运行Analog程序处理服务器的统计信息。

12 3 * * * root tar czf/usr/local/backups/daily/etc.tar.gz /etc >> /dev/null 2>&1
52 5 * * * root /usr/local/src/analog-5.32-lh/analog >> /dev/null2>&1
以下是cron语句中的字段与字段说明:

字段 说明
1 分钟(0-59)(有没有办法精确到秒呢?*/60?,知道的告诉我一声呵~~)
2 小时(2-24)
3 日期(1-31)
4 月份(1-12;或英文缩写Jan、Feb等)
5 周几(0-6,0为周日;或单词缩写Sun、Mon等)
6 用户名(执行命令时以此用户的身份)
7 要执行的命令(路径)

现在来看第一行:

12 3 * * * root tar czf/usr/local/backups/daily/etc.tar.gz /etc >> /dev/null 2>&1
这条语句将在每天的凌晨3点12分(03:12)运行 tar czf /usr/local/backups/daily/etc.tar.gz/etc 命令。>> /dev/null 2>&1 表示把所有标准输出发送到/dev/null(linux的回收站),把标准错误输出(2)发送到和标准输出(1)同样的地方(即/dev/null)。运行这行命令将不会产生任何输出。

这条语句可以变得稍微复杂一点:

30 15 13 6 1 * root tar czf/usr/local/backups/daily/etc.tar.gz /etc >> /dev/null 2>&1
它将在6月13日周一的15:30运行 tar czf /usr/local/backups/daily/etc.tar.gz /etc 命令。

以下语句可以达到同样的效果:

30 15 13 Jun Mon * root tar czf/usr/local/backups/daily/etc.tar.gz /etc >> /dev/null 2>&1
如果你想以用户joey的身份每小时的第15分钟运行某个程序,可以使用:

15 * * * * joey /usr/bin/somecommand >>/dev/null 2>&1
其中的星号(*)是通配符,表示cron将忽略这个字段。

如果你想每两小时就运行某个程序,可以在小时字段里使用*/2。它将会在2点,4点,6点……22点,24点运行。具体语句如下:

0 */2 * * * joey /usr/bin/somecommand >>/dev/null 2>&1
cron语句中还可以使用逗号(,)来指定多个时间。例如你想在每小时的15分和30分运行某个程序,可以在分钟字段使用 15,30:

15,30 * * * * joey /usr/bin/somecommand >>/dev/null 2>&1
如果你想在每月的第一周(即1号到7号)每天的指定时间运行某个程序,可以在日期字段使用 1-7:

15,30 */2 1-7 * * joey /usr/bin/somecommand >>/dev/null 2>&1
这条语句将在每月的第1-7日每两小时的15分和30分(02:15,02:30……22: 15,22:30等)运行/usr/bin/somecommand 命令。

如果你想在每天的16:18执行一个脚本集合,可以把所有要执行的脚本放到一个目录中(如/home/username/cron),可以使用:

18 16 * * * root run-parts /home/username/cron>> /dev/null 2>&1
如果你想保存某个程序的输出结果, 可以把 >> /dev/null 2>&1 替换为 >>/home/user/somecommand.log 2>&1 。

总结
查看当前用户的cron配置,使用 crontab -l
编辑当前用户的cron配置,使用 crontab -e
删除当前用户的cron配置,使用 crontab -r
以root身份查看/编辑/删除某用户的cron配置,在命令后加上 -u USERNAME
配置系统级的任务,编辑 /etc/crontab 文件

大多数cron的实现都支持通过 MAIL 环境变量定义邮件接收地址,如果不想要邮件,在cron job file开始 MAIL=”” 就可以了,不过这是个很糟的习惯。(MAILTO或MAIL)


原文:http://hi.baidu.com/yuhongchun027/blog/item/d81bb4a73711f09dd143580f.html

分类: Linux 标签:

网站架构相关PPT、文章整理

2010年3月15日 没有评论

       在这篇blog中放置了我收集的一些网站架构相关的PPT和文章,提供给大家下载,如果大家有相关的好的PPT、文章的话,也欢迎推荐给我,非常感 谢,:),这篇blog的内容也会随着我收集的东西增加而变化,同时也会增加我对于这些PPT、文章的看法和评价。

1、amazon
Amazon的分布式key-value存储系统(dynamo)的论文

2、ebay(我对于eBay这几个PPT的一些看法和评价: http://www.blogjava.net/BlueDavy/archive/2009/07/24/288055.html
ebay架构演变历程(The eBay Architecture)

ebay架构原则(eBay architecture principles)

ebay的自动化(Teaching machines to fish)

3、facebook
facebook的缓存系统

facebook的架构

facebook百亿相片的高效存储

4、fotolog
扩展世界上最大的图片blog社区

5、google
GFS 介绍

GFS 论文

Mapreduce介绍

Mapreduce论文

Google在web前端方面的经验(even faster websites)

建设大型可扩展的IRS系统的挑战(challenges in building large-scaleIRS)

松耦合分布式系统中的锁服务(lock service for loosly-coupleddistributed system)

“滚木移石”不停机升级策略论文(modular software upgrades fordistributed program)

Googlewave的架构

6、linkedin
linkedin远程通讯架构

7、livejournal
livejournal架构演变历程

8、myspace
myspace架构

9、wikipedia
wikipedia架构

10、yahoo
yahoo定制的apache–yapache

11、youtube
scaling youtube

12、Twitter
Designing a Scalable Twitter

13、豆瓣
技术演变历程(QCon 2009北京)

14、freewheel
架构(QCon 2009北京)

15、优酷
架构(QCon 2009北京)

16、淘宝
技术演变历程(QCon 2009北京)

17、twitter
Improving the Performance and Scalability of Twitter

原文:http://www.blogjava.net/BlueDavy/archive/2009/04/28/267970.html

分类: 架构 标签:

分布式key-value数据库LightCloud的设计原理

2010年3月12日 没有评论

LightCloud是最近看到的一个比较轻巧的分布式key-value数据库,尽管这类软件已经让人觉得审美疲劳,但我仍然觉得它的设计思路值 得一提。

特色

除开其项目主页上列出来的特点不提,我觉得还能数得上的特色有:

    1. 理论上可以用任意key-value数据库做为底层存储,现在支持以tokyo tyrant或者redis作为底层的存储,如果使用redis可以获得更好的 性能(大概提升30%~50%)
    2. 没有定制服务器端,基本上靠客户端语言来实现键值查找。优点是部署起来比较简单,缺点也是显而易见的,效率会有损失。
    3. 可以很方便的移植到其它语言上,我已经在github上找到一个ruby版本,甚至还有个php版本的实现。
    4. 可以方便的增加节点。
    5. 结构简单,方便hack

LightCloud的设计原理

Hash ring

LightCloud不能免俗的使用了一致性hash算法(ConsistentHashing),这是为了避免新增数据节点时发生集体拆迁事件。Consistent Hashing算法的原理请参考这里

last.fm的工作人员写的ketama算法算是比较常见的一致性算法,在libmemcached里大量使用。而LightCloud的作者当 时还没发现合适的ketamapython版,所以干脆自己捋起袖子写了个python版本的hash_ring,不到50行。这个是量身定制的,所以效率也还过得去,但是兼容 ketama就别想了。

献上hash圈圈一个以明志:

LightCloud的hash环有什么与众不同?

其它分布式key-value数据库采用的办法是复制数据到多个节点上,例如AmazonDynamo的复制策略图:

Dynamo用了许多办法解决consistent问题,系统相当复杂。而LightCloud直接使用tokyotyrant的master-master复制功能,大幅简化了这部分的逻辑。所以在它的hash环上,单个节点其实是一对master-master的 tokyo tyrant,焦不离孟不离焦。

在新增数据节点时,如果没有路由服务找到正确的服务器,可能会损失数据。那么LightCloud继续采用流氓手段解决这个问题,他又给上了个环, 保证不会发生意外。这两个hash环里的节点仍然是之前提到的tokyo tyrant双人组,一个环叫lookup, 记录了每一个key保存在哪个storage节点上;另外一个环叫storage,这是真正存放数据的地方。于是它的 结构图变成了下面这个样子:

这部分比较难以理解,试着就上图阐述一下:

    1. 一个名叫A的家伙,住在storage_SB地区(storage ring)。同时,我们还告诉记性好的lookup_B(lookupring),A君的地址为storage_SB。
    2. 很不幸或很幸运,咱们的数据膨胀到需要扩容了,于是新增了一个违章建筑storge_X,这个该死的建筑正好影响了我们找到A君。这时候,我们还 可以问起记性好的lookup_B,A君住在哪个角落里啊? ―― lookup_B日道:他就住在sotrage_SB一带~
    3. lookup这群家伙记性虽然好,但是也架不住人多,再也记不住这么多人的住址,所以又新来了几个记性好的lookup。这个会影响咱们找到 storage住哪吗?答案是不会,因为没有新增别的违章storage建筑,咱们不需要问路也能找着人。

按照以上推论,一定要避免出现同时增加lookup和storage节点,这很可能会损失数据。

参考网页

分类: NoSQL 标签:

启用memcached压缩注意事项

2010年3月12日 没有评论

      在php开发中,开启memcache的数据压缩存储是一件很简单的事情。在多数情况下,压缩数据不仅不会降低程序的执行效率,反倒会因为网络传输的开销降低,带来速度提升。看看最常用的Memcache::set方法:
bool Memcache::set ( string $key , mixed $var [, int $flag [, int $expire ]] )

在这个方法中,将$flag设置为MEMCACHE_COMPRESSED即可启用memcache压缩存储。
这样做有什么弊端?

如果没有做额外判断,每一次写入memcache都会启用压缩,不管数据的大小。对应的,每次获得数据都需要做一次解压缩的操作,这是典型的一刀切手法。实际上在数据很小的情况下,不需要压缩,在这个基础上压缩省不了多少空间。
更好的压缩策略?

好了,我的想法是在数据超过一定大小(比如2k)的情况下,才开启压缩。这个好办,捋起袖子就干,在调用Memcache::set方法之前,首先判断一下数据的大小,一个strlen就搞定了,再简单不过了。
PLAIN TEXT
PHP:
$memcache = new Memcache;
$memcache->connect(‘localhost’, 11211);
$flag = strlen($data)> 2048 ? MEMCACHE_COMPRESSED : 0;
$memcache->set(‘mykey’, $data, $flag);

有人可能会问了,array和object怎么办,这玩意可不能用strlen判断长度。

这还真能难住我一阵子,要知道把array/object写入memcache的时候,php会自动做serialize,再把它当作字符串插入memcache。
PLAIN TEXT
PHP:
$flag = strlen(serialize($data))> 2048 ? MEMCACHE_COMPRESSED : 0;

谁会采用这段代码?看起来非常山寨,而且serialize也不快,赔本买卖。
更好的办法!

上面的文字都是废话,直接看这段就好。Memcache::setCompressThreshold方法可以包办之前所有的逻辑。

Memcache::setCompressThreshold ― Enable automatic compression of large values

bool Memcache::setCompressThreshold ( int $threshold [, float $min_savings ] )

举个例子,下面这段会自动启用压缩策略,当数据大于2k时,以0.2的压缩比进行zlib。
PLAIN TEXT
PHP:
$memcache->setCompressThreshold(2000, 0.2);

根据我的测试结果,setCompressThreshold方法会忽略Memcache::set的flag参数。

引用:http://www.ooso.net/archives/475

分类: PHP 标签:

在生产环境中使用php性能测试工具xhprof

2010年3月12日 没有评论

       xhprof是facebook开源出来的一个php性能测试工具,也可以称之为profile工具,这个词不知道怎么翻译才比较达意。跟之前一直使用的xdebug相比,有很多类似之处。以前对xdebug有一些记录还可以供参考,但是它的缺点是对性能影响太大,即便是开启了profiler_enable_trigger参数,用在生产环境中也是惨不忍睹,cpu立刻就飙到high。

而xhprof就显得很轻量,是否记录profile可以由程序控制,因此,用在生产环境中也就成为一种可能。在它的文档上可以看到这样一种用法:

以万分之一的几率启用xhprof,平时悄悄的不打枪。
PLAIN TEXT
PHP:
if (mt_rand(1, 10000) == 1) {
xhprof_enable(XHPROF_FLAGS_MEMORY);
$xhprof_on = true;
}

在程序结尾处调用方法保存profile
PLAIN TEXT
PHP:
if ($xhprof_on) {
// stop profiler
$xhprof_data = xhprof_disable();

// save $xhprof_data somewhere (say a central DB)

}

也可以用register_shutdown_function方法指定在程序结束时保存xhprof信息,这样就免去了结尾处判断,给个改写的不完整例子:
PLAIN TEXT
PHP:
if (mt_rand(1, 10000) == 1) {
xhprof_enable(XHPROF_FLAGS_MEMORY);
register_shutdown_function(create_funcion(”, "$xhprof_data = xhprof_disable(); save $xhprof_data;"));
}

至于日志,我暂时用的是最土的文件形式保存,定期清除即可。

BTW:xhprof生成的图形方式profile真是酷毙了,哪段代码成为瓶颈,一目了然。

引用:http://www.ooso.net/archives/522

分类: PHP 标签:

memcache的几个旁支

2010年3月11日 没有评论

最近留意了一下,memcache出现了几个旁支项目,很有一点意思,也许在日后的项目中可以用的上。

memcached-tag

给memcache增加了tag功能,新增的命令如下:

  • tag_add <tag> <key>
  • tag_delete <tag>

Memcached is a high-performance, distributed memory object cachingsystem.

We add “Tag Function” for memcached. Propose is remove several keys with thesame tag in one operation. This function will help the API programmers (such asphp) do the delete operation easily, and reduce the network load. We use hashand splay tree, make the “tag_add” and “tag_delete” commands very quickly, andsave memory as much as possible.

memcached forWin32

顾名思义,这个是一个在win32下跑的memcached,某些人用的上吧,也许…

This is a port of memcached to the win32 architecture by Kronuz
The win32version of memcached can be run both as a NT Service or from the commandline.

memcachedb

新浪互动社区技术团队提供的一个memcache版本。我们知道memcache是将数据保存在内存里的,要是停机或重启,biu的一下,就什么都没了,虽然这能满足cache的需要,但是如果有一个key-value的持久化存储db,也是不错的。

memcachedb就是这样一个基于memcache + berkeley db的持久存储,仍然可以使用之前的memcache clientapi,比如这些方法:

  • get(also mutiple get)
  • set, add, replace
  • incr, decr
  • delete
  • stats

我测试了一下,还不支持flush奥,但是没有关系,问题不大。测试的时候,读写速度相当快。据memcachedb的主页上说,这个不是拿来作为一个cache的解决方案的,而是一个快速的key-value持久存储db。

Memcachedb is a distributed key-value storage system designed for persistent.It is NOT a cache solution, but a persistent storage engine for fast andreliable key-value based object storage and retrieval. It conforms to memcacheprotocol(not completed, see below), so any memcached client can haveconnectivity with it. Memcachedb uses Berkeley DB as a storing backend, so lotsof features including transaction and replication aresupported.

memcache_engine

memcache_engine是一个MySQL数据库的存储引擎,目前只支持MySQL5.1数据库,他能够把memcachedb作为MySQL数据库的一个存储引擎和MySQL集成起来,让用户通过标准的SQL查询语句访问memcachedb中存放的数据.

The memcache_engine allows memcache to work as a storage engine to MySQL.This means that you can SELECT/UPDATE/INSERTE/DELETE from it as though it is atable in MySQL.

转自: http://www.ooso.net/archives/395

分类: 架构 标签:

memcache连接慢又一例

2010年3月11日 没有评论

       继上次解决memcache连接慢问题以来,好长一段时间没在这个问题上翻过跟头。这一次我又在生产环境观察到php和memcache的连接时间经常会在50ms以上。

作为一个cache,占用了这么长的执行时间,天理何在?

实际的运行环境如下:

    • apache + mod_php
    • php-memcache扩展版本为2.2.5
    • memcache的并发连接数在400左右,相当少

这次memcache扩展用的是最新的稳定版,无可挑剔。所以刚开始我认为是网络环境的问题,于是直接采用telnet工具直连memcache进行测试,发现速度飞快!一点便秘感都没有!所以把目光仍然放回到memcache扩展上来,集中对比较慢的addServer方法各项参数进行排查。

Memcache::addServer方法

bool Memcache::addServer ( string $host [, int $port = 11211 [, bool$persistent [, int $weight [, int $timeout [, int $retry_interval [, bool$status [, callback $failure_callback [, int $timeoutms ]]]]]]]] )

比对结果表明,$weight参数对memcache的连接时间有显著的影响,$weight的默认值为1,一旦设置为别的数值,连接时间便会由毫秒级变成50ms左右,立竿见影。

鉴于php-memcache扩展一贯恶劣的表现,俺不得不痛下决心迁移到新的memcached扩展上。memcached扩展基于libmemcached开发,而且提供了丰富的接口方法,应该是更好的选择。

转自:http://www.ooso.net/archives/524

分类: PHP 标签: