聊聊 Tomcat 的单机多实例

Tomcat 从何而来?

先说 Tomcat 这一单词解释,如果你不是一个开发者,当然它在美国口语中并非是褒义词;如果你是开发者,那你一定听过 Web 应用服务器、Sun 公司和 Tomcat 。如你所知道那样,牛逼的公司总是推动这个世界的发展,并建立一个又一个标准,当然,在软件界 Sun 公司绝对算牛逼中的其一。
在贵的离谱的商用服务器充斥着市场的时候,Sun 公司推出了第一个 Java servlet container(Servlet 容器) 名字叫:Java Web Server(JWS),物美价廉,这简直是业界的一股清流,但市场并没有像他们想象的那么喜欢 JWS,一家商业公司如果产品卖不出去,那真是令人极其伤感的,但对于 Sun 公司来说这不重要,因为他们是 Java servlet 这个最初的标准的制定者,应该没有什么比这个更令人兴奋的了。随着标准的推出,直接推动了当时许多自由的、免费的 Java servlet container 的出现,像 Jetty 、JServ 等这些容器,好像所有人都喜欢免费,当然同时期还有一些商用的如 WebLogic 、JRun 这些容器存在。到这里好像还并没有出现 Tomcat ,别着急,Sun 公司其实比你更着急,因为 JSP 还没有出现。
可能由于 Sun 公司在 Servlet 容器市场的低迷表现,他们转头又愤而推出了迷你型 servlet coutainer 并支持 Web 的工具包,他们称之为 JavaServer Pages(JSP),这个工具包(JSDK)任何人都可以下载,接着随着 Sun 接着制定了新版的 JSP 规范,JSDK 也升级到了 2.1 版本后,注意,这时候大神出现了。在 Sun 公司上班的 James Duncan Davidson 没有使用任何原来代码的情况下写出了一个全新的 servlet contaniner ,从此取代了 JSDK 2.1 版本,因次这也是为什么 Tomcat 的版本是从 3.0 开始的而不是 1.0 。
当然,接下来的加入 Apache 基金会和开源也算是 Sun 公司为软件界的贡献,这其中肯定有商业上的考虑,但你何必在意呢。

Tomcat 的基本组成

了解一个事物的本质是现在就用它。不废话,直接先说一下 Tomcat 的安装和使用,之前写过 Tomcat 在 CentOS 、Windows 和配合 Nginx 在做负载均衡这三篇文章,用到的可以简单看下:

安装好之后,进入安装目录看一眼结构:

Tomcat 目录

简单介绍一下各个文件夹及文件:

  • bin:主要存放脚本文件,例如比较常用的windows和linux系统中启动和关闭脚本
  • conf:主要存放配置文件,其中最重要的两个配置文件是server.xml和web.xml
  • lib:主要存放tomcat运行所依赖的包
  • LICENSE:版权许可证,软件版权信息及使用范围等信息
  • logs:主要存放运行时产生的日志文件,例如catalina.out(曾经掉过一个大坑)、catalina.{date}.log等
  • NOTICE:通知信息,一些软件的所属信息和地址什么的
  • RELEASE-NOTES:发布说明,包含一些版本升级功能点
  • RUNNING.txt:运行说明,必需的运行环境等信息
  • temp:存放tomcat运行时产生的临时文件,例如开启了hibernate缓存的应用程序,会在该目录下生成一些文件
  • webapps:部署web应用程序的默认目录,也就是 war 包所在默认目录
  • work:主要存放由JSP文件生成的servlet(java文件以及最终编译生成的class文件)

上面是一个安装后的 Tomcat 的全部组成部分,如果你要启动,进入bin目录执行startup.sh就可以了,接着就可以在浏览器输入http://localhost:8080/访问了。那么问题来了:当你有了三个、五个以及十个应用服务需要同时部署到同一台服务器上时,你的 Tomcat 服务正确启动方式是什么?是把上面文件全部复制出 N 多个目录么?还是有其他处理方式呢?

Tomcat 常见的几种部署场景

通常,我们在同一台服务器上对 Tomcat 部署需求可以分为以下几种:单实例单应用,单实例多应用,多实例单应用,多实例多应用。实例的概念可以理解为上面说的一个 Tomcat 目录。

  • 单实例单应用:比较常用的一种方式,只需要把你打好的 war 包丢在 webapps目录下,执行启动 Tomcat 的脚本就行了。
  • 单实例多应用:有两个不同的 Web 项目 war 包,还是只需要丢在webapps目录下,执行启动 Tomcat 的脚本,访问不同项目加上不同的虚拟目录。这种方式要慎用在生产环境,因为重启或挂掉 Tomcat 后会影响另外一个应用的访问。
  • 多实例单应用:多个 Tomcat 部署同一个项目,端口号不同,可以利用 Nginx 这么做负载均衡,当然意义不大。
  • 多实例多应用:多个 Tomcat 部署多个不同的项目。这种模式在服务器资源有限,或者对服务器要求并不是很高的情况下,可以实现多个不同项目部署在同一台服务器上的需求,来实现资源使用的最大化。-

这次其实要说的就是这种方式,但多个 Tomcat 就是简单的复制出一个新的 Tomcat 目录后改一下端口么?这样做也太 Low 了点吧?哈哈,其实并不是低端没技术含量的问题,当你同一台服务器部署了多个不同基于 Tomcat 的 Web 服务时,会迎来下面几个极其现实的问题。

  • 当你需要对数十台 Tomcat 版本进行升级的时候,你需要怎么做?
  • 当你需要针对每一个不同的 Web 服务分配不用的内存时,你需要怎么做?
  • 当你需要启动多台服务器时,你需要怎么做?

当然,好像上面的都不是很重要,注意,划重点,多实例部署最大作用就是最大化利用服务器资源。

说干就干,现在就开始干?

别着急别着急,先看一下官方文档怎么建议的。他们说可不建议你复制一个又一份的全部 Tomcat 目录进行多实例的部署,说安照下图可以实现更优雅的 Tomcat 单机多实例部署:

部署结构

上图中的 CATALINA_HOME 指Tomcat安装路径,CATALINA_BASE 指实例所在位置。
CATALINA_HOME 路径下只需要包含 binlib 目录,而 CATALINA_BASE 只存放 conf、webapps、logs 等这些文件,这样部署的好处在于升级方便,配置及安装文件间互不影响,在不影响 Tomcat 实例的前提下,替换掉 CATALINA_HOME 中的安装文件。

流程清楚了,接下来才是真正的撸起袖子加油干了。

快来实践一下吧

你看到了这里肯定已经安装了 Tomcat 了,我现在演示用的是最新的 8.5.11 版本。

1.复制出两个 Tomcat 实例
在 Tomcat 安装路径的同一级目录下,新建两个tomcat-1、tomcat-2文件夹,先把安装路径下的 conf、webapps、temp、logs、work 这五个文件移动到tomcat-1实例中:
mv conf

命令:

mkdir tomcat-1 tomcat-2
cd apache-tomcat-8.5.11
mv conf/ webapps/ temp/ logs/ work/ -t ../tomcat-1

接着把tomcat-1下的这几个文件再复制到tomcat-2中,直接命令:

cp tomcat-1/* tomcat-2

2.新建 Tomcat 启动、停止脚本
依然是在 Tomcat 安装路径的同一级目录下,新建两个tomcat-shell文件夹,用于存放启动和停止脚本,同时赋予文件全部权限。
tomcat-shell

命令:

cd tomcat-shell/
vim start_tomcat.sh
vim stop_tomcat.sh
chmod 777 start_tomcat.sh stop_tomcat.sh

tomcat-start.sh:
tomcat-start.sh

#!/bin/bash

export CATALINA_HOME=/software/apache-tomcat-8.5.11
export CATALINA_BASE=${1%/}

echo $CATALINA_BASE

TOMCAT_ID=`ps aux |grep "java"|grep "Dcatalina.base=$CATALINA_BASE "|grep -v "grep"|awk '{ print $2}'`


if [ -n "$TOMCAT_ID" ] ; then
echo "tomcat(${TOMCAT_ITOMCAT_ID}) still running now , please shutdown it firest";
    exit 2;
fi

TOMCAT_START_LOG=`$CATALINA_HOME/bin/startup.sh`


if [ "$?" = "0" ]; then
    echo "$0 ${1%/} start succeed"
else
    echo "$0 ${1%/} start failed"
    echo $TOMCAT_START_LOG
fi

tomcat-stop.sh:
tomcat-stop.sh

#!/bin/bash

export CATALINA_HOME=/software/apache-tomcat-8.5.11
export CATALINA_BASE=${1%/}

echo $CATALINA_BASE

TOMCAT_ID=`ps aux |grep "java"|grep "[D]catalina.base=$CATALINA_BASE "|awk '{ print $2}'`

if [ -n "$TOMCAT_ID" ] ; then
TOMCAT_STOP_LOG=`$CATALINA_HOME/bin/shutdown.sh`
else
    echo "Tomcat instance not found : ${1%/}"
    exit

fi


if [ "$?" = "0" ]; then
    echo "$0 ${1%/} stop succeed"
else
    echo "$0 ${1%/} stop failed"
    echo $TOMCAT_STOP_LOG
fi

这两个就是简单的脚本,其中传入了要启动的 Tomcat 实例所在的路径,当然,你也可以写一个重启的脚本,其实就是先停止再启动,还可以加入不同的 JVM 参数配置等等操作。
到这里,其实全部基础工作已经做好了。接下来我们看一眼整个多实例的目录结构:

ls-all

3.配置 server.xml 端口
你知道的,同一个服务器部署不同 Tomcat 要设置不同的端口,不然会报端口冲突,所以我们只需要修改conf/server.xml中的其中前三个端口就行了。但它有四个分别是:

  • Server Port:该端口用于监听关闭tomcat的shutdown命令,默认为8005
  • Connector Port:该端口用于监听HTTP的请求,默认为8080
  • AJP Port:该端口用于监听AJP( Apache JServ Protocol )协议上的请求,通常用于整合Apache Server等其他HTTP服务器,默认为8009
  • Redirect Port:重定向端口,出现在Connector配置中,如果该Connector仅支持非SSL的普通http请求,那么该端口会把 https 的请求转发到这个Redirect Port指定的端口,默认为8443;

我这里把 tomcat-2 实例的 Connector Port 改为了 8081 ,并分别在 tomcat-1、tomcat-2webapps/ROOT 目录下放入了一个页面文件,内容如下:

<html>
<title>Tomcat-1</title>
<body>
    Hello Mafly! from Tomcat-1.
</body>
</html>

4.启动
直接通过执行我们刚写的脚本,传入某一个 Tomcat 实例路径即可来启动对应的 Tomcat。
start-tomcat

命令:

/software/tomcat-shell/start_tomcat.sh /software/tomcat-1
/software/tomcat-shell/start_tomcat.sh /software/tomcat-2

去浏览器看一眼:
Hello Mafly

哈哈,可以了。接下来,停止或者重启什么的都一样,你可以根据需要来在单个服务器上创建更多的 Tomcat 实例,一切都看你喜欢。

总结一下

这两天简单翻了一下 《Tomcat 权威指南》这本书,对于我们日常使用的 Tomcat 有了更详细的了解,当然我在这里并没有详细写配置、部署管理工具、安全管理和集群什么的,我还了解不够透彻,只是简单把 Tomcat 单机多实例比较优雅的部署方式玩了一下,希望对你有用。

昨天晚上看 Linux 和 Git 的发起者 Linus 这位大神的传记,真是令人感到上帝的不公平,怎么能设计出这样的天才人类,但更令人兴奋的是,我们目前绝大多数人都被设计成了几乎无差别,还远远轮不到拼智商的地步。

新年上班第一天,我的 IDE 挂了

新的一年又开始了

你年前的总结还记得么?你新年的计划做好了么?反正我都没做。
上班第一天大家都在晒着开工红包,看着一个比一个刷到的红包多,庆幸自己幸好没结婚;开心的聊着过年又被七大姑八大姨爷爷奶奶爸爸妈妈催婚,然后自己没有女朋友;说着同学朋友聚会大家都开着车去的,自己骑着一个自行车;他家孩子已经一岁多了,她又去了一次境外游,他买了房子并涨价四千块。而你呢,好像还是和去年没什么两样…

第一天我就开始写代码了

当和大家一起刷完领导和同事的开工红包后,我就马不停蹄的打开电脑准备写代码啦!但万万没想到,当我点开 Eclipse 的时候,她给我弹出一个对话框,我以为是要祝我新年快乐、鸡年大吉吧呢!谁知道…

eclipse_erroe.png

Error: Registry key 'Software\JavaSoft\Java Runtime Environment'\CurrentVersion' has value '1.7', but '1.8' is required.
明显感受到了来自 IDE 深深的新年祝福,新年第一天打开电脑第一件事就是修复 IDE,也是够了。不过,从对话框上的文字,可以得出是 JDK 版本冲突了,这时候我想起来,在过年前放假回家的前一天,我在这台电脑上又装了 Java 1.8 版本的 JDK 来调试其他的一个项目。CMD 打开命令行窗口,敲击 java -version 命令,果然还是这句话:

cmd_error.png

Error: Registry key 'Software\JavaSoft\Java Runtime Environment'\CurrentVersion'

has value '1.7', but '1.8' is required.
Error: could not find java.dll
Error: Could not find Java SE Runtime Environment.

翻译成人话:出错啦!你告诉我 1.7 ,却给我 1.8 ,找不到!找不到!!

既然 IDE 挂了,那就不写代码了

怎么可能,今天还有一个小功能要上线呢。首先考虑是环境变量配置出问题,检查也没错,都是之前配置的 1.7 版本;接着检查注册表都没有什么异常,CurrentVersion也都是之前的 1.7 版本;我想不出来还是什么问题了,就去 Google 了一下,找到了 SO 上的一个问题:Registry key Error: Java version has value ‘1.8’, but ‘1.7’ is required 哈哈,和我一样,这就好办了。再接着了解到,虽然我们一直配置的环境变量中的Path\JAVA_HOME等这些东西,但当我们在命令行窗口执行java -version或打开 Eclipse 的时候,执行的并不是环境变量配置下bin目录的文件,而是C:\Windows\System32下的文件。

了解原因以后,那再根据提示就会发现:可能我的C:\Windows\System32下的文件是 1.8 版本的,不是 1.7 的。这是因为我先安装的 JDK 版本是 1.7 的,这时候C:\Windows\System32下的文件肯定只有 1.7 版本的,但当我又安装了 1.8 版本的 JDK 之后,系统可能把 C:\Windows\System32 下的 1.7 版本文件覆盖掉了!掉了!!掉了!!!那我就替换呗。

肯定有人会问:既然知道是多个不同 JDK 版本,卸载年前安装的 1.8 版本不就行了?那可不行,1.8 还有用呢,所以,需要保留 JDK 1.7 和 1.8 两个版本,现在要做的就是让两个版本共存且不再冲突

替换。两个不同版本 JDK 共存

Windows 下多个 JDK 版本共存首要条件是得仅有一个版本为主要的,其他不同的版本 JDK 调用只需要在 Tomcat 的bin/catalina.bat中配置JAVA_HOME即可了。选定自己需要的 JDK 版本后,就要把环境变量的JAVA_HOME、注册表等这些关于 JDK 版本的配置改为你需要的,我这里需要 1.7 版本,就拿 1.7 版本做演示。

1.安装 1.7 和 1.8 JDK,配置 1.7 环境变量(已安装,略)。
2.查看或修改注册表
找到HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft节点,查看Java Runtime EnvironmentJava Development kit中的CurrentVersion值是不是 1.7 ,不是就改为 1.7 。如图:

regedit.png

3.替换 java.exe/javaw.exe/javaws.exe
找到环境变量JAVA_HOME中配置路径下的bin目录中的 java.exe/javaw.exe/javaws.exe 这三个文件,用这三个替换C:\Windows\System32C:\Windows\SysWOW64中的文件。如图:

jdk_bin.png

当然,还有C:\ProgramData\Oracle\Java\javapath下的这三个文件快捷方式,也要替换一下。

4.完成
命令行窗口再次执行java -version,看到下面画面就是成功啦!Eclipse 也能打开了,我写完了代码。

cmd_success.png

总结一下

新年开工第一天竟然是以修 IDE 开始的,打死我也想不到,但我们作为开发者不就是整天在做修补工作么,修完这个 Bug ,还有下一个 Bug 在等着,我认为这才是这个职业的魅力所在,你永远不知道接下来等着你的是什么,在没来临的那一刻。

好了,年也过完了,新的年又开始了,你也许去年混的不怎么样,但你放心,你今年会依然混的不怎么样,哈哈,开个玩笑,不过在我有限的认知中,我认为我们所从事的这个职业是现阶段社会上的职业中幸福的了(因为我特么没干过其他职业)。当然,我知道你们又要跳槽了,祝大家找工作的找到好工作,不找工作的好好加班写代码,未来都是大家的。