Tag: tomcat
Tomcat 设计模式分析
by Elton on 五.29, 2010, under Java
门面设计模式
门面设计模式在 Tomcat 中有多处使用,在 Request 和 Response 对象封装中、Standard Wrapper 到 ServletConfig 封装中、ApplicationContext 到 ServletContext 封装中等都用到了这种设计模式。
门面设计模式的原理
这么多场合都用到了这种设计模式,那这种设计模式究竟能有什么作用呢?顾名思义,就是将一个东西封装成一个门面好与人家更容易进行交流,就像一个国家的外交部一样。
这种设计模式主要用在一个大的系统中有多个子系统组成时,这多个子系统肯定要涉及到相互通信,但是每个子系统又不能将自己的内部数据过多的暴露给其它系统,不然就没有必要划分子系统了。每个子系统都会设计一个门面,把别的系统感兴趣的数据封装起来,通过这个门面来进行访问。这就是门面设计模式存在的意义。
门面设计模式示意图如下:
图 1. 门面示意图

Client 只能访问到 Façade 中提供的数据是门面设计模式的关键,至于 Client 如何访问 Façade 和 Subsystem 如何提供 Façade 门面设计模式并没有规定死。
Tomcat 的门面设计模式示例
Tomcat 中门面设计模式使用的很多,因为 Tomcat 中有很多不同组件,每个组件要相互交互数据,用门面模式隔离数据是个很好的方法。
下面是 Request 上使用的门面设计模式:
图 2. Request 的门面设计模式类图

从图中可以看出 HttpRequestFacade 类封装了 HttpRequest 接口能够提供数据,通过 HttpRequestFacade 访问到的数据都被代理到 HttpRequest 中,通常被封装的对象都被设为 Private 或者 Protected 访问修饰,以防止在 Façade 中被直接访问。
观察者设计模式
这种设计模式也是常用的设计方法通常也叫发布 – 订阅模式,也就是事件监听机制,通常在某个事件发生的前后会触发一些操作。
观察者模式的原理
观察者模式原理也很简单,就是你在做事的时候旁边总有一个人在盯着你,当你做的事情是它感兴趣的时候,它就会跟着做另外一些事情。但是盯着你的人必须要到你那去登记,不然你无法通知它。观察者模式通常包含下面这几个角色:
Subject 就是抽象主题:它负责管理所有观察者的引用,同时定义主要的事件操作。
ConcreteSubject 具体主题:它实现了抽象主题的所有定义的接口,当自己发生变化时,会通知所有观察者。
Observer 观察者:监听主题发生变化相应的操作接口。
Tomcat 的观察者模式示例
Tomcat 中观察者模式也有多处使用,前面讲的控制组件生命周期的 Lifecycle 就是这种模式的体现,还有对 Servlet 实例的创建、Session 的管理、Container 等都是同样的原理。下面主要看一下 Lifecycle 的具体实现。
Lifecycle 的观察者模式结构图:
图 3. Lifecycle 的观察者模式结构图

上面的结构图中,LifecycleListener 代表的是抽象观察者,它定义一个 lifecycleEvent 方法,这个方法就是当主题变化时要执行的方法。 ServerLifecycleListener 代表的是具体的观察者,它实现了 LifecycleListener 接口的方法,就是这个具体的观察者具体的实现方式。Lifecycle 接口代表的是抽象主题,它定义了管理观察者的方法和它要所做的其它方法。而 StandardServer 代表的是具体主题,它实现了抽象主题的所有方法。这里 Tomcat 对观察者做了扩展,增加了另外两个类:LifecycleSupport、LifecycleEvent,它们作为辅助类扩展了观察者的功能。LifecycleEvent 使得可以定义事件类别,不同的事件可区别处理,更加灵活。LifecycleSupport 类代理了主题对多观察者的管理,将这个管理抽出来统一实现,以后如果修改只要修改 LifecycleSupport 类就可以了,不需要去修改所有具体主题,因为所有具体主题的对观察者的操作都被代理给 LifecycleSupport 类了。这可以认为是观察者模式的改进版。
LifecycleSupport 调用观察者的方法代码如下:
清单 1. LifecycleSupport 中的 fireLifecycleEvent 方法
1 2 3 4 5 6 7 8 9 | public void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(lifecycle, type, data); LifecycleListener interested[] = null; synchronized (listeners) { interested = (LifecycleListener[]) listeners.clone(); } for (int i = 0; i < interested.length; i++) interested[i].lifecycleEvent(event); } |
主题是怎么通知观察者呢?看下面代码:
清单 2. 容器中的 start 方法
1 2 3 4 5 6 7 8 9 10 11 12 | public void start() throws LifecycleException { lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; synchronized (services) { for (int i = 0; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).start(); } } lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); } |
命令设计模式
前面把 Tomcat 中两个核心组件 Connector 和 Container,比作一对夫妻。男的将接受过来的请求以命令的方式交给女主人。对应到 Connector 和 Container,Connector 也是通过命令模式调用 Container 的。
命令模式的原理
命令模式主要作用就是封装命令,把发出命令的责任和执行命令的责任分开。也是一种功能的分工。不同的模块可以对同一个命令做出不同解释。
下面是命令模式通常包含下面几个角色:
Client:创建一个命令,并决定接受者
Command 命令:命令接口定义一个抽象方法
ConcreteCommand:具体命令,负责调用接受者的相应操作
Invoker 请求者:负责调用命令对象执行请求
Receiver 接受者:负责具体实施和执行一次请求
Tomcat 中的命令模式的示例
Tomcat 中命令模式在 Connector 和 Container 组件之间有体现,Tomcat 作为一个应用服务器,无疑会接受到很多请求,如何分配和执行这些请求是必须的功能。
下面看一下 Tomcat 是如何实现命令模式的,下面是 Tomcat 命令模式的结构图:
图 4. Tomcat 命令模式的结构图

Connector 作为抽象请求者,HttpConnector 作为具体请求者。HttpProcessor 作为命令。Container 作为命令的抽象接受者,ContainerBase 作为具体的接受者。客户端就是应用服务器 Server 组件了。Server 首先创建命令请求者 HttpConnector 对象,然后创建命令 HttpProcessor 命令对象。再把命令对象交给命令接受者 ContainerBase 容器来处理命令。命令的最终是被 Tomcat 的 Container 执行的。命令可以以队列的方式进来,Container 也可以以不同的方式来处理请求,如 HTTP1.0 协议和 HTTP1.1 的处理方式就会不同。
责任链模式
Tomcat 中一个最容易发现的设计模式就是责任链模式,这个设计模式也是 Tomcat 中 Container 设计的基础,整个容器的就是通过一个链连接在一起,这个链一直将请求正确的传递给最终处理请求的那个 Servlet。
责任链模式的原理
责任链模式,就是很多对象有每个对象对其下家的引用而连接起来形成一条链,请求在这条链上传递,直到链上的某个对象处理此请求,或者每个对象都可以处理请求,并传给下一家,直到最终链上每个对象都处理完。这样可以不影响客户端而能够在链上增加任意的处理节点。
通常责任链模式包含下面几个角色:
Handler(抽象处理者):定义一个处理请求的接口
ConcreteHandler(具体处理者):处理请求的具体类,或者传给下家
Tomcat 中责任链模式示例
在 tomcat 中这种设计模式几乎被完整的使用,tomcat 的容器设置就是责任链模式,从 Engine 到 Host 再到 Context 一直到 Wrapper 都是通过一个链传递请求。
Tomcat 中责任链模式的类结构图如下:
图 5. Tomcat 责任链模式的结构图

上图基本描述了四个子容器使用责任链模式的类结构图,对应的责任链模式的角色,Container 扮演抽象处理者角色,具体处理者由 StandardEngine 等子容器扮演。与标准的责任链不同的是,这里引入了 Pipeline 和 Valve 接口。他们有什么作用呢?
实际上 Pipeline 和 Valve 是扩展了这个链的功能,使得在链往下传递过程中,能够接受外界的干预。Pipeline 就是连接每个子容器的管子,里面传递的 Request 和 Response 对象好比管子里流的水,而 Valve 就是这个管子上开的一个个小口子,让你有机会能够接触到里面的水,做一些额外的事情。
为了防止水被引出来而不能流到下一个容器中,每一段管子最后总有一个节点保证它一定能流到下一个子容器,所以每个容器都有一个 StandardXXXValve。只要涉及到这种有链式是处理流程这是一个非常值得借鉴的模式。
CentOS环境中lighttpd+php+fastcgi+eAccelerator安装配置
by Elton on 六.21, 2009, under Linux, PHP
Lighttpd 作为新一代的web server,以小巧(不到1M的大小)、快速而著称,因为服务器上安装了rails、java,并以lighttpd为前端代理服务器,不想再部署apache了,所以直接使用lighttpd来部署,顺便看一下性能如何。
本文主要介绍在CentOS下,配置一套用lighttp作为web server的php环境
· 安装Lighttpd
从http://www.lighttpd.net/download/下载源码
安装前先检查pcre是否安装,需要pcre和pcre-devel两个包。 用yum search pcre*检查,如果都是installed就是都安装了。否则安装缺少的包。
1 2 3 4 5 | yum install pcre-devel tar xzvf lighttpd-1.4.23.tar.gz cd lighttpd-1.4.23 ./configure --prefix=/usr/local/lighttpd |
configure完毕以后,会给出一个激活的模块和没有激活模块的清单,可以检查一下,是否自己需要的模块都已经激活,在enable的模块中一定要有“mod_rewrite”这一项,否则重新检查pcre是否安装。然后编译安装:
1 | make && make install |
编译后配置:
1 2 3 | cp doc/sysconfig.lighttpd /etc/sysconfig/lighttpd mkdir /etc/lighttpd cp doc/lighttpd.conf /etc/lighttpd/lighttpd.conf |
如果你的Linux是RedHat/CentOS,那么:
1 | cp doc/rc.lighttpd.redhat /etc/init.d/lighttpd |
如果你的Linux是SuSE,那么:
1 | cp doc/rc.lighttpd /etc/init.d/lighttpd |
其他Linux发行版本可以自行参考该文件内容进行修改。然后修改/etc/init.d/lighttpd,把
1 | lighttpd="/usr/sbin/lighttpd" |
改为
1 | lighttpd="/usr/local/lighttpd/sbin/lighttpd" |
此脚本用来控制lighttpd的启动关闭和重起:
1 2 3 | /etc/init.d/lighttpd start /etc/init.d/lighttpd stop /etc/init.d/lighttpd restart |
如果你希望服务器启动的时候就启动lighttpd,那么:
1 | chkconfig lighttpd on |
这样lighttpd就安装好了,接下来需要配置lighttpd。
配置Lighttpd
修改/etc/lighttpd/lighttpd.conf
1)server.modules
取消需要用到模块的注释,mod_rewrite,mod_access,mod_fastcgi,mod_simple_vhost,mod_cgi,mod_compress,mod_accesslog是一般需要用到的。
2)server.document-root, server.error-log,accesslog.filename需要指定相应的目录
3)用什么权限来运行lighttpd
server.username = “nobody”
server.groupname = “nobody”
从安全角度来说,不建议用root权限运行web server,可以自行指定普通用户权限。
4)静态文件压缩
compress.cache-dir = “/tmp/lighttpd/cache/compress”
compress.filetype = (“text/plain”, “text/html”,”text/javascript”,”text/css”)
可以指定某些静态资源类型使用压缩方式传输,节省带宽,对于大量AJAX应用来说,可以极大提高页面加载速度。
5)配置ruby on rails
最简单的配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $HTTP["host"] == "www.xxx.com" { server.document-root = "/yourrails/public" server.error-handler-404 = "/dispatch.fcgi" fastcgi.server = (".fcgi" => ("localhost" => ("min-procs" => 10, "max-procs" => 10, "socket" => "/tmp/lighttpd/socket/rails.socket", "bin-path" => "/yourrails/public/dispatch.fcgi", "bin-environment" => ("RAILS_ENV" => "production") ) ) ) } |
即由lighttpd启动10个FCGI进程,lighttpd和FCGI之间使用本机Unix Socket通信。
如果想指定www.abc.com以及所有二级域名,则需要把第一行改为
$HTTP[”host”] =~ “(^|.)abc.com” {
…
}
如果要设置代理,比如lighttpd和tomcat整合,tomcat放在lighttpd后面,则需要通过代理访问tomcat
$HTTP["host"] =~ “www.domain.cn” {
proxy.server = ( “” => ( “localhost” => ( “host”=> “127.0.0.1″, “port”=> 8080 ) ) )
}
则www.domain.cn为主机的网址都交给tomcat处理,tomcat的端口号为8080. 在tomcat的虚拟主机中,需要捕获www.domain.cn这个主机名,设置这个虚拟主机。这里的host都是跟tomcat里面的虚拟主机对应的。
· 安装支持fastcgi的PHP
安装PHP所需的相关类库
curl
1 2 3 4 5 6 7 | wget http://curl.cs.pu.edu.tw/download/curl-7.19.5.tar.bz2 tar xvjf curl-7.19.5.tar.bz2 cd curl-7.19.5 ./configure --prefix=/usr/local/curl make make install |
gettext
1 2 3 4 5 6 | wget ftp://ftp.ntu.edu.tw/pub/gnu/gnu/gettext/gettext-0.17.tar.gz tar xvzf gettext-0.17.tar.gz cd gettext-0.17 ./configure --prefix=/usr/local/gettext make make install |
zlib
1 2 3 4 5 | wget http://kent.dl.sourceforge.net/sourceforge/libpng/zlib-1.2.3.tar.gz tar xvzf zlib-1.2.3.tar.gz cd zlib-1.2.3 ./configure --prefix=/usr/local/zlib make && make install |
libpng
1 2 3 4 5 | wget http://www.mirrorservice.org/sites/download.sourceforge.net/pub/sourceforge/l/li/libpng/libpng-1.2.9.tar.gz tar xvzf libpng-1.2.9.tar.gz cd libpng-1.2.9 ./configure --prefix=/usr/local/libpng make && make install |
jpeg
1 2 3 4 5 6 7 8 9 10 11 12 13 | wget http://www.ijg.org/files/jpegsrc.v6b.tar.gz tar xvzf jpegsrc.v6b.tar.gz cd jpeg-6b/ ./configure --prefix=/usr/local/jpeg6 make mkdir /usr/local/jpeg6/bin mkdir -p /usr/local/jpeg6/bin mkdir -p /usr/local/jpeg6/man/man1 mkdir -p /usr/local/jpeg6/lib mkdir -p /usr/local/jpeg6/include make install-lib make install |
freetype
1 2 3 4 5 6 | wget http://download.savannah.gnu.org/releases/freetype/freetype-2.3.9.tar.gz tar xvzf freetype-2.3.9.tar.gz cd freetype-2.3.9 ./configure --prefix=/usr/local/freetype2 make make install |
gd
1 2 3 4 5 6 7 | wget http://www.libgd.org/releases/gd-2.0.35.tar.gz tar xvzf gd-2.0.35.tar.gz cd gd-2.0.35 ./configure --prefix=/usr/local/gd2 --with-zlib=/usr/local/zlib/ --with-png=/usr/local/libpng/ --with-jpeg=/usr/local/jpeg6/ --with-freetype=/usr/local/freetype2/ make 如果第一次make出错,试着再make一次,我就是这样,第二次就对了。 make install |
PHP
1 2 3 4 5 6 7 8 | tar xvzf php-5.2.10.tar.gz cd php-5.2.10 ./configure --prefix=/usr/local/php --with-mysql=/usr/local/mysql --with-pdo-mysql=/usr/local/mysql --with-jpeg-dir=/usr/local/jpeg6/ --with-png-dir=/usr/local/libpng/ --with-gd=/usr/local/gd2/ --with-freetype-dir=/usr/local/freetype2/ --with-zlib-dir=/usr/local/zlib --with-curl=/usr/local/curl --with-gettext=/usr/local/gettext --enable-fastcgi --enable-zend-multibyte --with-config-file-path=/etc --enable-discard-path --enable-force-cgi-redirect make make install cp php.ini-dist /etc/php.ini 可以使用php -m查看你安装的模块 |
eAccelerator
eAccelerator是一个开源的PHP加速器
1 2 3 4 5 6 7 8 | wget http://bart.eaccelerator.net/source/0.9.5.3/eaccelerator-0.9.5.3.tar.bz2 tar xjvf eaccelerator-0.9.5.3.tar.bz2 cd eaccelerator-0.9.5.3 export PHP_PREFIX="/usr/local/php" $PHP_PREFIX/bin/phpize ./configure --enable-eaccelerator=shared --with-php-config=$PHP_PREFIX/bin/php-config make make install |
执行好后,会提示安装到的路径,下面会用到,如我的被安装到这里
/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613
编辑php.ini中的内容
vim /etc/php.ini
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | cgi.fix_pathinfo = 1 zend_extension="/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/eaccelerator.so" eaccelerator.shm_size="16" eaccelerator.cache_dir="/tmp/eaccelerator" eaccelerator.enable="1" eaccelerator.optimizer="1" eaccelerator.check_mtime="1" eaccelerator.debug="0" eaccelerator.filter="" eaccelerator.shm_max="0" eaccelerator.shm_ttl="0" eaccelerator.shm_prune_period="0" eaccelerator.shm_only="0" eaccelerator.compress="1" eaccelerator.compress_level="9" |
如果一切顺利,你可以通过下面命令来验证是否安装成功
1 2 3 4 5 | $ php -v PHP 5.2.10 (cli) (built: Jun 20 2009 23:32:09) Copyright (c) 1997-2009 The PHP Group Zend Engine v2.2.0, Copyright (c) 1998-2009 Zend Technologies with eAccelerator v0.9.5.3, Copyright (c) 2004-2006 eAccelerator, by eAccelerator |
修改/etc/lighttpd/lighttpd.conf文件,添加下面的配置
vim /etc/lighttpd/lighttpd.conf
1 2 3 4 5 6 7 8 | fastcgi.server = ( ".php" => ( "localhost" => ( "socket" => "/tmp/php-fastcgi.socket", "bin-path" => "/usr/local/php/bin/php-cgi" ) ) ) |
重启lighttpd
1 | /etc/init.d/lighttpd restart |
写一个php测试文件在lighttpd的网站目录里,测试php是否安装成功
Tomcat 启动gzip压缩输出页面大小及其他优化
by Elton on 六.20, 2009, under Java
在输出的页头中加入
1 | Content-Encoding: gzip |
可以有效的减少页面的大小,一般可以减小1/3左右。
对于tomcat来说修改起来也很见到, 在tomcat下的conf/server.xml文件中,修改
1 2 3 4 5 6 | <connector port="8080" maxHttpHeaderSize="8192" useBodyEncodingForURI="true" maxThreads="1000" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" compression="on" compressionMinSize="2048" compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain" connectionTimeout="20000" disableUploadTimeout="true" URIEncoding="UTF-8"/> |
就可以对html,xml,css,javascript和纯文本进行压缩。
其中
- maxHttpHeaderSize:Http的Header的最大限制
- maxThreads:Tomcat可创建的最大的线程数
- minSpareThreads:初始化创建的线程数
- maxSpareThreads:一旦创建的线程超过这个数,Tomcat就将关闭不再需要的Socket线程
- enableLookups:使用允许DNS查询,通常情况下设置为false
- acceptCount:当所有可以使用的处理请求的线程树都被使用时,可以放到请求队列中的请求数,超过这个数的请求将不予处理。其实,该属性与ServerSocket(int port,int backlog)中的backlog参数意义相同,具体可参考ServerSocket的JDK API
- connectionTimeout:网络连接超时,单位毫秒。设置为0表示永不超时


