记一次线上 tomcat 内存告警问题排查

最近几天线上服务器内存一直报警,物理内存持续超过 90%, 导致监控一直报警。

线上服务器 tomcat 启动参数:

-Xms3072m -Xmx3072m -XX:PermSize=256m -XX:MaxPermSize=256m -Xmn1024m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSCompactAtFullCollection -XX:+HeapDumpOnOutOfMemoryError

服务器配置:
双核 4GB 内存

通过本地和开发环境模拟测试:

当 tomcat 启动之后内存并没有占用到 3GB 的内存,只有 1GB 左右。然后通过一段测试代码将内存耗光 (代码如下):

  HashMap<Object,Object> map = new HashMap<>();
  Integer i= 1;
  flag1 = flag;
  for(;;) {
    i++;
    if(flag1 == 1) {
      map.put(i, i+1);
    }else {
      break;
    }
  }

然后系统一直会执行 Full GC,直到抛出 OutOfMemoryError 为止。但是如果在系统开始 Full GC 之后马上终止程序,

这个时候 JVM 的堆内存使用会释放至正常范围,但是通过系统命令查看 JVM 的内存占用发现内存一直没有释放。

未分类

因为对于操作系统,请求内存的系统调用会占用大量的 cpu 时间,所以频繁的请求、释放内存将会导致性能的严重下降。所以对于 jvm 最好的方式就是尽量多占用内存作为 heap, 少释放甚至不释放空闲的 heap 给操作系统以减少消耗在内存请求、释放操作上的 cpu 时间。

所以导致 JVM 内存一直占用过高,但是通过打印堆栈的使用情况来看,堆内存的使用率并不是很高,再加上系统的占用以及其他应用对内存的占用,所以会导致监控平台内存告警。

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 3221225472 (3072.0MB)
   NewSize          = 1073741824 (1024.0MB)
   MaxNewSize       = 1073741824 (1024.0MB)
   OldSize          = 5439488 (5.1875MB)
   NewRatio         = 2
   SurvivorRatio    = 8
   PermSize         = 268435456 (256.0MB)
   MaxPermSize      = 268435456 (256.0MB)
   G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 966393856 (921.625MB)
   used     = 57484824 (54.821800231933594MB)
   free     = 908909032 (866.8031997680664MB)
   5.94838467184957% used
Eden Space:
   capacity = 859045888 (819.25MB)
   used     = 50165232 (47.84129333496094MB)
   free     = 808880656 (771.4087066650391MB)
   5.839645204145369% used
From Space:
   capacity = 107347968 (102.375MB)
   used     = 7319592 (6.980506896972656MB)
   free     = 100028376 (95.39449310302734MB)
   6.818565955528846% used
To Space:
   capacity = 107347968 (102.375MB)
   used     = 0 (0.0MB)
   free     = 107347968 (102.375MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 2147483648 (2048.0MB)
   used     = 276183376 (263.3889923095703MB)
   free     = 1871300272 (1784.6110076904297MB)
   12.860790640115738% used
Perm Generation:
   capacity = 268435456 (256.0MB)
   used     = 85506216 (81.54508209228516MB)
   free     = 182929240 (174.45491790771484MB)
   31.85354769229889% used

34698 interned Strings occupying 3779016 bytes.

最终的解决方案通过降低内存分配解决。

tomcat配置https自签名证书(keytool生成)

生成keystore

keytool -genkeypair -alias "server" -keyalg "RSA" -validity "365" -keystore "/app/webapp/tomcat/https/server.keystore"
[webapp@machina https]$ pwd
/app/webapp/tomcat/https
[webapp@machina https]$ keytool -genkeypair -alias "server" -keyalg "RSA" -validity "365" -keystore "/app/webapp/tomcat/https/server.keystore"
Enter keystore password:  
Re-enter new password: 
What is your first and last name?
  [Unknown]:  10.13.22.102
What is the name of your organizational unit?
  [Unknown]:  ai
What is the name of your organization?
  [Unknown]:  ai
What is the name of your City or Locality?
  [Unknown]:  gz
What is the name of your State or Province?
  [Unknown]:  gd
What is the two-letter country code for this unit?
  [Unknown]:  cn
Is CN=10.13.22.102, OU=ai, O=ai, L=gz, ST=gd, C=cn correct?
  [no]:  yes

Enter key password for <server>
        (RETURN if same as keystore password):  
Re-enter new password: 

Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore /app/webapp/tomcat/https/server.keystore -destkeystore /app/webapp/tomcat/https/server.keystore -deststoretype pkcs12".
[webapp@machina https]$ 

修改配置server.xml

[webapp@machina conf]$ pwd
/app/webapp/tomcat/apache-tomcat-7.0.88/conf
[webapp@machina conf]$ vi server.xml
    <!--
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" />
    -->

改为:

    <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" 
               keystoreFile="/app/webapp/tomcat/https/server.keystore" keystorePass="123456"/>

保存:
:wq

修改https的tomcat里的默认端口8443(也可不改,用默认的)。
这里修改为18003。共修改三处。另外两处是注释里的,可不修改。

    <Connector port="18002" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <Connector port="18002" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="18003" />

    <Connector port="18003" protocol="org.apache.coyote.http11.Http11Protocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS"
               keystoreFile="/app/webapp/tomcat/https/server.keystore" keystorePass="123456"/>

    <Connector port="8009" protocol="AJP/1.3" redirectPort="18003" />

修改tomcat的web.xml,强制http跳转到https

[webapp@machina conf]$ pwd
/app/webapp/tomcat/apache-tomcat-7.0.88/conf
[webapp@machina conf]$ vi web.xml

后面加上这样一段:

    <login-config>    
        <!-- Authorization setting for SSL -->    
        <auth-method>CLIENT-CERT</auth-method>    
        <realm-name>Client Cert Users-only Area</realm-name>    
    </login-config>    
    <security-constraint>    
        <!-- Authorization setting for SSL -->    
        <web-resource-collection >    
            <web-resource-name >SSL</web-resource-name>    
            <url-pattern>/*</url-pattern>    
        </web-resource-collection>    
        <user-data-constraint>    
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>    
        </user-data-constraint>    
    </security-constraint>

重启tomcat

[webapp@machina bin]$ pwd
/app/webapp/tomcat/apache-tomcat-7.0.88/bin
[webapp@machina bin]$ sh shutdown.sh
Using CATALINA_BASE:   /app/webapp/tomcat/apache-tomcat-7.0.88
Using CATALINA_HOME:   /app/webapp/tomcat/apache-tomcat-7.0.88
Using CATALINA_TMPDIR: /app/webapp/tomcat/apache-tomcat-7.0.88/temp
Using JRE_HOME:        /opt/jdk1.8.0_151
Using CLASSPATH:       /app/webapp/tomcat/apache-tomcat-7.0.88/bin/bootstrap.jar:/app/webapp/tomcat/apache-tomcat-7.0.88/bin/tomcat-juli.jar
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=256m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
[webapp@machina bin]$ sh startup.sh
Using CATALINA_BASE:   /app/webapp/tomcat/apache-tomcat-7.0.88
Using CATALINA_HOME:   /app/webapp/tomcat/apache-tomcat-7.0.88
Using CATALINA_TMPDIR: /app/webapp/tomcat/apache-tomcat-7.0.88/temp
Using JRE_HOME:        /opt/jdk1.8.0_151
Using CLASSPATH:       /app/webapp/tomcat/apache-tomcat-7.0.88/bin/bootstrap.jar:/app/webapp/tomcat/apache-tomcat-7.0.88/bin/tomcat-juli.jar
Tomcat started.

访问

http://10.13.22.102:18002/ops/app

自动跳转:

https://10.13.22.102:18003/ops/app

tomcat的安装方法

安装tomcat

1) 切换至java目录

cd /usr/java

2) 解压压缩包至/usr/java目录

tar -zxvf  apache-tomcat-7.0.65.tar.gz

3) 切换至tomcat的bin目录

cd /usr/java/apache-tomcat-7.0.65/bin

4) 启动tomcat

./startup.sh

测试Tomcat

打开浏览器输入输入http://localhost:8080/,看到以下页面表示Tomcat安装后,启动成功。

未分类

使用Jenkins的任务自动跑脚本后发现,tomcat服务刚启动就被杀死

在Jenkins的使用中,遇到过的一个场景是:使用python自动执行tomcat bin目录下的startup.bat开启批处理,服务开启成功后就随着python脚本执行结束该服务就被杀死,开始以为是python脚本的问题,但是直接执行是没有问题的;之后一直查运行环境差异,发现也不是这个原因;到后来才怀疑到Jenkins任务结束时候自动关掉了所有的子进程。通过以下shell脚本片段解决了问题:

1.第一种方案: #临时改变BUILD_ID值,使得Jenkins不会找到并结束掉python脚本启动的后台进程
OLD_BUILD_ID=$BUILD_ID
echo $OLD_BUILD_ID
BUILD_ID=dontKillMe
./run.sh restart
#改回原来的BUILD_ID值
BUILD_ID=$OLD_BUILD_ID
echo $BUILD_ID

问题的根本在于是Jenkins使用processTreeKiller杀掉了所有子进程,而且这是Jenkins的默认行为。其实回头来看这个问题,就发现Jenkins的做法非常合理。当一次build异常结束,或被人终止时,必然需要结束所有这次build启动的子进程。下面的link提供了更多细节,以及解决方法。https://wiki.jenkins-ci.org/display/JENKINS/ProcessTreeKiller

2.第二种方案:

使用jenkins的批处理或者ant 启动tomcat失败。查了一下原因说是 jenkins在脚本执行结束后,就认为任务结束了,但是脚本启动的相关子程序仍然在运行。由于jenkins认为任务已经结束了,就结束了该构建相关的子进程。

解决办法:增加一个环境变量。

BUILD_ID=pleaseDontKillMe and it works like a charm。

可以添加在执行任务的节点设置中添加该变量。

解决详细步骤:

前置说明:我是通过slave节点来启动tomcat 的,所以再slave节点配置上述环境变量。

未分类

未分类

CentOS下安装与配置JDK和Tomcat

下面以CentOS7环境,安装JDK8和Tomcat8为例,演示安装与配置过程。

一、下载JDK

从Oracle官网找到最新的JDK版本,当前最新版为Java SE Development Kit 8u92,复制下载链接,如下图所示:

未分类

shell> wget --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie;" http://download.oracle.com/otn-pub/java/jdk/8u92-b14/jdk-8u92-linux-x64.tar.gz

二、下载Tomcat

进入Tomcat官网,找到tomcat8的下载地址,如下图所示:

未分类

shell> wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.0.36/bin/apache-tomcat-8.0.36.tar.gz

三、解压JDK和Tomcat

shell> tar -zxvf jdk-8u92-linux-x64.tar.gz -C /opt
shell> tar -zxvf apache-tomcat-8.0.35.tar.gz -C /opt

未分类

四、配置JDK环境变量

vim /etc/profile.d/java.sh
# 在java.sh中加入如下内容:
JAVA_HOME=/opt/jdk1.8.0_92
PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME PATH

保存并退出,执行sources /etc/profile,使环境变量生效

五、启动tomcat

shell> /opt/apache-tomcat-8.0.35/bin/startup.sh

Zabbix利用JMX监控多实例Tomcat运行状态

自使用Zabbix监控系统以来,一直想用JMX来监控Tomcat,但是一直都没配置成功,总有一些问题,监控端的报错又很抽象,搜索网上大都是复制粘贴之产物,或者是缺斤短两之网文,但是一直都没放弃,至今终于配置成功,并且成功获取数据,形成图形,现在把自己解决问题的过程和配置的一些心得整理成文.

环境介绍:

  • Centos 6.5
  • Zabbix 2.2.15
  • Tomcat 7.0.68

监控JMX配置步骤

1.在zabbix服务器上安装配置zabbix-java-gateway,并且配置相关参数。

2.配置tomcat服务器,JMX服务相关参数,上传依赖包

3.zabbix web端添加监控

4.启动tomcat服务,查看zabbix web端监控运行状态,排错.

5.自定义图形和监控项

一、Zabbix-java-gateway配置

1.安装zabbix-java-gateway

yum -y install zabbix-java-gateway

2.配置zabbix-java-gateway

grep "^[A-Z]" /etc/zabbix/zabbix_java_gateway.conf
LISTEN_IP="0.0.0.0"        #监听本机所有ip
LISTEN_PORT=10052            #在10052端口提供服务
PID_FILE="/var/run/zabbix/zabbix_java.pid"
START_POLLERS=5

3.启动服务

service zabbix-java-gateway start

4.配置zabbix-server

grep "^[A-Z]" /etc/zabbix/zabbix_server.conf|grep Java
JavaGateway=127.0.0.1     #JavaGateway所在服务器的IP
JavaGatewayPort=10052     #JavaGateway的默认端口
StartJavaPollers=5         #JVM进行监控轮询实例数,默认是0

5.重启zabbix-server

service zabbix-server restart

6.查看监听端口

未分类

二、Tomcat配置

1.配置catalina.sh

vim /apache-tomcat-7.0.68/bin/catalina.sh
CATALINA_OPTS="$CATALINA_OPTS  -Dfile.encoding=utf-8
-Dcom.sun.management.jmxremote      #开启远程
-Dcom.sun.management.jmxremote.authenticate=false     #免密认证
# -Dcom.sun.management.jmxremote.port=12345       #这里不需要这行,一会儿说明原因 
-Dcom.sun.management.jmxremote.ssl=false 
-Djava.rmi.server.hostname=192.168.66.22"        #要监控的tomcat主机ip

2.配置server.xml

vim /m.aaa.com/conf/server.xml    (注意,这里是实例里面的配置文件)

在任意位置添加如下文字,意思是远程连接端口是12345,获取数据端口是123456,如果有多实例,全部都要加这行文字,端口数字不同即可

<Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener"  rmiRegistryPortPlatform="12345" rmiServerPortPlatform="12346"/>

3.开放指定端口

iptables -I INPUT -p tcp -m multiport –dports 12345,12346 -j ACCEPT

4.下载支持的jar包

链接:http://down.51cto.com/data/2448735

里面包含两个jar包,catalina-jmx-remote.jar和cmdline-jmxclient-0.10.3.jar 还有一个监控模板

catalina-jmx-remote.jar存放位置:/apache-tomcat-7.0.68/lib/

cmdline-jmxclient-0.10.3.jar 这个jar是用来测试连通情况的,zabbix和tomcat服务器上都要放一个

三、Zabbix web监控配置

1.添加JMX介面,这里添加的端口号是远程连接端口号

未分类

2.添加监控模板

未分类

3.监控项添加完后,JMX图标显示红色,因为tomcat服务还没有起

4.多实例监控,进入主机-项目进行配置,在server.xml中指定的什么端口,这里就选什么端口

未分类

四、启动tomcat服务,查看zabbix web端监控运行状态,排错

在zabbix服务器端尝试获取数据,显示如下数据连接正确

java -jar cmdline-jmxclient-0.10.3.jar - 192.168.66.22:12345 java.lang:type=Memory NonHeapMemoryUsage

未分类

在tomcat服务器端显示使用12346进行连接

未分类

如果看者严格按照上面的配置操作,这时zabbix服务web端JMX图标已经显示绿色,如果还是红色,请参考下面的提示排错

**1. java.rmi.ConnectException: Connection refused to host: 192.168.66.22; nested exception is: **

java.net.ConnectException: Connecti

错误原因:只在catalina.sh中配置了一个远程端口

还记得上面我在/apache-tomcat-7.0.68/bin/catalina.sh有一行注释掉了么,在这个文件中不能指定端口号,否则就会报如上错误

2. java.io.IOException: Failed to retrieve RMIServer stub: javax.naming.ServiceUnavailableException [Root exception is java.rmi.Con

错误原因:没有配置远程端口

光删除掉上面的端口号还不够,还需要在/m.aaa.com/conf/server.xml中添加相应的端口配置

3.java.lang.ClassNotFoundException: org.apache.catalina.mbeans.JmxRemoteLifecycleListener

错误原因:这是tomcat报错,缺少支持的jar包catalina-jmx-remote.jar

请往上翻,找51cto的下载链接,下载相关jar包

4. Catalina:type=ThreadPool,name=http-8080

未分类

错误原因:支持的name里没有http-8080

查看支持哪些类型和调用

java -jar cmdline-jmxclient-0.10.3.jar – 192.168.66.22:12345

上面报错的解决方法是:

1.java -jar cmdline-jmxclient-0.10.3.jar – 192.168.66.22:12347|grep Thread

未分类

2.配置模板

未分类

3.点击进入修改键值

未分类

4.改完后是这样的

未分类

5.目标是全绿

未分类

Nginx+Tomcat 部署负载均衡集群

Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。在Java的Web架构中,通常使用Tomcat和Nginx进行配合,Nginx作为反向代理服务器,可以对后台的Tomcat服务器负载均衡,也可以让Nginx处理静态页面的请求、Tomcat处理JSP页面请求达到动静分离的目的。

系统环境:

未分类

软件包百度下载: https://pan.baidu.com/share/init?surl=3fbIn0EuEcSVplRk4tRnAg&third=15 密码:6b7e

网站拓扑架构图:

未分类

开始部署

一、Tomcat服务器1、Tomcat服务器2

1.安装jdk并设置java环境

#安装jdk软件包
rpm -ivh jdk-10.0.1_linux-x64_bin.rpm

#添加jdk的环境变量,新建java.sh并写入以下内容
vim /etc/profile.d/java.sh

    export JAVA_HOME=/usr/java/jdk-10.0.1
    export PATH=$JAVA_HOME/bin:$PATH

#加载环境变量
source /etc/profile.d/java.sh

2.查看jdk版本信息

java -version

如下图表示jdk已经安装成功了

未分类

3.解压并安装tomcat

#解压缩软件包
tar zxvf apache-tomcat-8.5.11.tar.gz

#移动tomcat目录
mv apache-tomcat-8.5.11 /usr/local/tomcat8

4.启动tomcat服务

#启动服务
/usr/local/tomcat8/bin/startup.sh
  1. 默认tomcat运行在8080端口,检查服务是否成功启动
netstat -tunlp | grep 8080

未分类

6.创建站点目录

mkdir -p /web/webapp

7.为站点新建首页文件index.jsp

vim /web/webapp/index.jsp
#以为下index.jsp内容
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" %>
<html>
<head>
<title>hello</title>
</head>
<body>
<% out.println("<h1>This is tomcat 1 web.</h1>"); %>
</body>
</html>

8.编辑tomcat主配置文件,以支持新站点:

vim /usr/local/tomcat8/conf/server.xml
#在<Host>标签内,新增第二行记录
<Context docBase="/web/webapp" path="" reloadable="false"></Context>

未分类

9.重启tomcat服务

#关闭服务
/usr/local/tomcat8/bin/shutdown.sh

#开启服务
/usr/local/tomcat8/bin/startup.sh

#关闭安全设置
setenforce 0

#关闭防火墙
systemctl stop firewalld.service

Tomcat服务器1 和 Tomcat服务器2 安装及配置等一样,只是测试网页内容有差异,具体网页内容显示如下图

Tomcat服务器1:

未分类

Tomcat服务器2:

未分类

二、Nginx服务器

1.安装依赖包(需要连接网络或者通过系统镜像ISO文件安装)

yum -y install pcre-devel zlib-devel gcc gcc-c++ make

2.创建管理用户nginx

useradd -M -s /sbin/nologin nginx

3.解压nginx,并进入解压后nginx目录

tar xzvf nginx-1.6.0.tar.gz -C /opt
cd /opt/nginx-1.6.0/

4.配置

./configure 
--prefix=/usr/local/nginx 
--user=nginx 
--group=nginx 
--with-http_stub_status_module //开启stub_status状态统计模块//

5.编译及安装

make && make install

6.nginx连接至系统命令区,方便命令使用

ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/

7.创建nginx管理脚本

vi /etc/init.d/nginx
#!/bin/bash
#chkconfig: - 99 20
#description: Nginx Service Control Script
PROG="/usr/local/nginx/sbin/nginx"
PIDF="/usr/local/nginx/logs/nginx.pid"
case "$1" in
start)
$PROG
;;
stop)
kill -s QUIT $(cat $PIDF)
;;
restart)
$0 stop
$0 start
;;
reload)
kill -s HUP $(cat $PIDF)
;;
*)
echo "Usage: $0 {start|stop|restart|reload}"
exit 1
esac
exit 0

8.为nginx赋予执行权限,并加入系统服务管理

chmod +x /etc/init.d/nginx
chkconfig --add nginx

三、配置nginx负载均衡集群

  1. 修改nginx配置文件
vim /usr/local/nginx/conf/nginx.conf
#http{}标签内添加以下命令
upstream tomcat_server {
server 192.168.100.6:8080 weight=1;
server 192.168.100.7:8080 weight=1;
}
#在location / {}标签内添加
location / {
root html;
index index.html index.htm;
proxy_pass http://tomcat_server; #通过proxy_pass方法进行代理至tomcat_server的服务器组,其中http://不能省略
}

未分类

2.检查nginx配置

nginx -t

3.重启nginx服务

killall -1 nginx
#关闭selinux
setenforce 0
#关闭防火墙
systemctl stop firewalld.service

测试

客户机访问Nginx服务器IP地址:http://192.168.100.25/,通过不断的刷新浏览器测试,可以看到哦由于权重相同,页面在两个tomcat站点反复切换,这样说明负载均衡集群搭建成功了。

未分类

未分类

Tomcat单机多实例部署及管理

未分类

单台机器部署多个 Tomcat, 每个Tomcat部署独立服务,Tomcat之间启停互不影响

不要问我为什么有这个需求, 复制粘贴就是干

0x00 单机多实例概述

未分类

大概的目录结构:

  • CATALINA_HOME 为Tomcat应用程序运行程序及所需依赖
  • CATALINA_BASE 即我们即将部署的程序

手动更改如下工作目录:

这是我自己个儿整的初始目录结构 download

war_apps                #手动创建用来存放下载好的war包文件
tomcat                  #CATALINA_HOME
├── bin
├── INIT_APPS_FILE      #CATALINA_BASE
│   ├── conf
│   ├── logs
│   ├── temp
│   ├── webapps
│   └── work
└── lib

然后呢正常的思路就是配置每个APP的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;

应用太多你难道要一个个手动改? no no no! 上脚本

0x01 自动部署管理配置

需要把应用war包传到可下载位置,来用于应用分发,我这里是直接传到了阿里云的oss桶里

1.添加tomcat更新启停控制脚本

需要安装unzip yum -y install unzip

manage.sh需要放在这里,也可以自定义改代码

war_apps                #手动创建用来存放下载好的war包文件
tomcat                  #CATALINA_HOME
├── bin
├── manage.sh
├── INIT_APPS_FILE      #CATALINA_BASE
│   ├── conf
│   ├── logs
│   ├── temp
│   ├── webapps
│   └── work
└── lib

蓝色块需要根据实际情况自定义, 应用名称, 和war包下载地址不需要写,后面的python总控制台会自动分配

manage.sh

#!/bin/sh
# Author: Kionf
# description: 启动tomcat多实例.
# PATH=/opt/op/java/jdk1.8.0_172/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
# 应用名称
app=
# war包下载地址
war_url=
soft_dir="/opt/op"
download_war_file="${soft_dir}/war_apps/${app}.war"
export CATALINA_BASE="${soft_dir}/tomcat/$app"
export CATALINA_HOME="${soft_dir}/tomcat"
export JVM_OPTIONS="-Xms528m -Xmx812m -Xmn328m"
check(){
    PID=`ps aux|grep java|grep -w ${CATALINA_BASE}|awk '{print $2}'`
    if [ -n "$PID" ];then
        echo -e "33[94m $app is running PID:$PID"
        running=`netstat -ntlp|grep $PID|grep 127.0.0.1`
        if [ -n "$running" ];then
            echo -e "33[92m $app is provide services33[0m "
        else
            echo -e "33[93m $app is running but not provide services33[0m"
        fi
        return 0
    else
        echo -e "33[91m $app is dead33[0m "
        return 1
    fi
}
start() {
    check
    if [ $? -eq 1 ];then
        echo -e "33[94m Start $app 33[0m"
        $CATALINA_HOME/bin/startup.sh >/dev/null 2>&1
    fi
}
stop() {
    check
    if [ $? -eq 0 ];then
        echo -e "33[94m Stop $app33[0m"
        $CATALINA_HOME/bin/shutdown.sh >/dev/null 2>&1
        kill -9 $PID
    fi
}
update() {
    echo "下载文件"
    wget ${war_url} -O ${download_war_file} > /dev/null 2>&1
    if [ $? -eq 0 ];then
        cd ${CATALINA_BASE}/webapps/*/; unzip -q -o ${download_war_file} >/dev/null 2>&1
    fi
}
log() {
    tailf ${CATALINA_BASE}/logs/catalina.out
}
if [ $# != "0" ];then
    case "$1" in
        start)
            start
            ;;
        stop)
            stop
            ;;
        restart)
            stop
            start 
            ;;
        status)
            check
            ;;
        upgrade)
            stop
            update
            start
            ;;
        log)
            log
            ;;
        *)
            echo $"Usage: $0 {start|stop|restart|status|upgrade|log}"
            exit 1
            ;;
    esac
else
    start
    log
fi

2.添加总控制台脚本

我就放在了/usr/local/bin下, chmod +x /usr/local/bin/tomcat_manager 蓝色部分需要根据自己需求更改 (支持python2)

/usr/local/bin/tomcat_manager

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2018/5/23 15:18
# @Author  : Kionf
# @FileName: tomcat_manager.py
#
#   初始配置并管理单例tomcat多应用
#
import os
import subprocess
import sys
import shutil
# PATH = os.getcwd()
PATH = "/opt/op/tomcat"         #Tomcat目录
shell_script = os.path.join(PATH, "manage.sh")          #更新启停管理脚本
INIT_FILES = os.path.join(PATH, "INIT_APPS_FILE")
bucket = "https://oss.aliyuncs.com/tomcat_app/"         # webapp下载地址
config_data = {
    # 应用名: [ServerPort, ConnectPort, AJPPort, RedirectPort, ]
    'Application': ['8201', '8101', '8301', '8401', 'BaseApplication.war'],
    'BaseNotify_Service': ['8202', '8102', '8302', '8402', 'BaseNotify_Service.war'],
    'BaseUserCenter_Service': ['8203', '8103', '8303', '8403', 'BaseUserCenter_Service.war'],
}
def customize_print(msg, stat=0):
    if stat == 1:
        print("33[91m [ERROR]:  %s 33[0m" % msg)
    elif stat == 0:
        print("33[92m [INFO]:  %s 33[0m" % msg)
    elif stat == 2:
        print("33[93m [DEBUG]:  %s 33[0m" % msg)
class TomcatAppsManage:
    def __init__(self, webapp):
        self.name = webapp
        self.app_config = config_data[self.name]
        self.manage_shell_file = os.path.join(PATH, self.name, self.name)
        self.server_port, self.conn_port, self.ajp_port, self.redirect_port, self.app_war_name = self.app_config
        self.app_download_url = bucket + self.app_war_name
        self.config_file = os.path.join(PATH, self.name, 'conf/server.xml')
        self.webapp_dir = os.path.join(PATH, self.name)
    def create_app(self):
        """
        创建app
        :return:
        """
        if not os.path.exists(self.webapp_dir):
            customize_print("创建APP: %s" % self.name)
            shutil.copytree(INIT_FILES, self.webapp_dir)
            os.mkdir(os.path.join(self.webapp_dir, "webapps", self.name.lower()))
    def config_app_port(self):
        customize_print("正在修改APP:%s 配置" % self.name)
        change_port = {
            'ServerPort': "sed -i s'#Server port="[0-9]*"#Server port="" + self.server_port + ""#'g " + self.config_file,
            'ConnPort': "sed -i s'#Connector port="[0-9]*" protocol="HTTP/1.1"#Connector port="" + self.conn_port + "" protocol="HTTP/1.1"#'g " + self.config_file,
            'RedirectPort': "sed -i s'#redirectPort="[0-9]*"#redirectPort="" + self.redirect_port + ""#'g " + self.config_file,
            'AjpPort': "sed -i s'#Connector port="[0-9]*" protocol="AJP/1.3"#Connector port="" + self.ajp_port + "" protocol="AJP/1.3"#'g " + self.config_file,
        }
        for port in change_port.keys():
            # customize_print("修改 %s 端口" % port)
            os.system(change_port[port])
    def config_app_manage_shell(self):
        customize_print("%s 添加管理脚本" % self.name)
        copy_shell_script = 'cp -f ' + shell_script + ' ' + self.manage_shell_file
        os.system(copy_shell_script)
        config_script_app_name = "sed -i 's/app=/app="" + self.name + ""/' " + self.manage_shell_file
        os.system(config_script_app_name)
        config_script_war_url = "sed -i 's#war_url=#war_url="" + self.app_download_url + ""#' " + self.manage_shell_file
        os.system(config_script_war_url)
    def status_app(self):
        """
        :return: 0提供服务,1停止,2未提供服务
        """
        try:
            result = subprocess.check_output(['sh', self.manage_shell_file, 'status'])
        except subprocess.CalledProcessError as e:
            result = e.output
        if 'run' in result:
            if 'is provide services' in result:
                customize_print("应用 %s 成功启动并提供服务" % self.name)
                return 0
            elif 'but' in result:
                customize_print("应用 %s 进程存在但未提供服务" % self.name, 2)
                return 2
        else:
            customize_print("应用 %s 以停止" % self.name, 1)
            return 1
    def manage(self, operate):
        os.system('sh %s %s' % (self.manage_shell_file, operate))

    def init(self):
        self.create_app()
        self.config_app_port()
        self.config_app_manage_shell()
        self.manage("upgrade")
        self.manage("stop")
    def restart(self):
        self.manage("stop")
        self.manage("start")
    def start(self):
        self.manage("start")
    def stop(self):
        self.manage("stop")
    def log(self):
        self.manage("log")
    def upgrade(self):
        self.lock_config_file()
        self.manage("upgrade")
    def lock_config_file(self):
        cmd = 'find ' + self.webapp_dir + ' -name db*properties -o -name config_base_*|xargs chattr +i >/dev/null 2>&1'
        customize_print("锁配置文件", 2)
        os.system(cmd)
    def unlock_config_file(self):
        cmd = 'find ' + self.webapp_dir + ' -name db*properties -o -name config_base_*|xargs chattr -i >/dev/null 2>&1'
        customize_print("解锁配置文件", 2)
        os.system(cmd)
def dash_board(apps, operate):
    """
    主管理程序,调用
    :param operate: 应用操作
    :param apps: apps 为list
    """
    for app in apps:
        app_obj = TomcatAppsManage(app)
        main_dict = {
            "init": app_obj.init,
            "shell": app_obj.config_app_manage_shell,
            "status": app_obj.status_app,
            "start": app_obj.start,
            "stop": app_obj.stop,
            "restart": app_obj.restart,
            "upgrade": app_obj.upgrade,
            "log": app_obj.log,
            "lock": app_obj.lock_config_file,
            "unlock": app_obj.unlock_config_file,
        }
        try:
            main_dict[operate]()
        except KeyError as e:
            customize_print(help_msg)
help_msg = """
使用方法:
    1 log
    all status
    管理应用编号 操作
操作:
    lock        锁配置文件
    unlock      解锁配置文件
    init        配置tomcat监听端口
    shell       配置webapp控制脚本
    status,start,restart, log,upgrade,stop 应用操作
"""
def main():
    app_list = []
    for index, app_name in enumerate(config_data, 1):
        print "33[94m %s:  %s 33[0m" % (index, app_name)
        app_list.append(app_name)
    choice = raw_input("输入要管理的服务:  ")
    try:
        app_index = choice.split()[0]
        operate = choice.split()[1]
        if app_index.isdigit():
            app_index = int(app_index)
            if len(app_list) >= app_index > 0:
                app_name = app_list[app_index - 1]
                dash_board(app_name.split(), operate)
        elif app_index == "all":
            dash_board(app_list, operate)
    except ValueError and IndexError:
        customize_print("参数输入错误", 1)
        customize_print(help_msg)
if __name__ == '__main__':
    try:
        dash_board(sys.argv[1].split(), sys.argv[2])
    except IndexError:
        try:
            while True:
                main()
        except KeyboardInterrupt:
            customize_print("Bye!")

0x02 开始初始化部署

执行tomcat_manager

未分类

all init 自动初始化部署所有项目, 其他具体使用方法见帮助信息

Tomcat 部署项目的三种方法

1、下载 Tomcat 服务器

①、官网下载地址:http://tomcat.apache.org/

②、tomcat 8.0 64位百度云下载地址:http://pan.baidu.com/s/1slbKPsx 密码:ewui

③、tomcat 8.0 32位百度云下载地址:http://pan.baidu.com/s/1o8G28rS 密码:k11n

2、启动并部署 Tomcat 服务器

①、解压 tomcat 安装包到一个非中文目录下
②、配置环境变量。JAVA_HOME(指向 JDK 安装的根目录)
③、双击 apache-tomcat-6.0.16bin 目录下的 startup.bat,启动服务器(如果一闪而过,那就是没有配置 JAVA_HOME 的环境变量)
④、在浏览器中输入 http://localhost:8080

注意:Tomcat 启动不了的时候注意配置 JAVA_HOME:C:Program FilesJavajdk1.6.0_43这是安装 JDK的根目录

3、Tomcat 的目录结构

未分类

4、部署项目的第一种方法(项目直接放入 webapps 目录中)

1、将编写并编译好的web项目(注意要是编译好的,如果是 eclipse,可以将项目打成 war 包放入),放入到 webapps 中

未分类

2、启动tomcat服务器(双击 apache-tomcat-6.0.16bin 目录下的 startup.bat,启动服务器)

未分类

3、在浏览器输入:http://localhost:8080/项目名/访问的文件名

未分类   

5、部署项目的第二种方法(修改 conf/server.xml 文件 )

①、打开tomcat下conf/server.xml,在 标签之间输入项目配置信息

<Context path="/WebProject" docBase="D:/WebProject" reloadable="true" />

path:浏览器访问时的路径名

docBase:web项目的WebRoot所在的路径,注意是WebRoot的路径,不是项目的路径。其实也就是编译后的项目

reloadble:设定项目有改动时,tomcat是否重新加载该项目

②、双击 startup.bat,启动 tomcat 服务器,然后在浏览器输入访问的项目名称路径

未分类  

注意:如果你配置的 path=”/xx”,那么访问的时候就是这样:

未分类

6、部署项目的第三种方法(apache-tomcat-7.0.52confCatalinalocalhost )

①、进入到 apache-tomcat-7.0.52confCatalinalocalhost 目录,新建一个 项目名.xml 文件

未分类

②、在 那个新建的 xml 文件中,增加下面配置语句(和上面的是一样的,但是不需要 path 配置,加上也没什么用)

<Context  docBase="D:/WebProject" reloadable="true" />

未分类

③、在浏览器输入路径:localhost:8080/xml文件名/访问的文件名

未分类

总结:

①、第一种方法比较普通,但是我们需要将编译好的项目重新 copy 到 webapps 目录下,多出了两步操作

②、第二种方法直接在 server.xml 文件中配置,但是从 tomcat5.0版本开始后,server.xml 文件作为 tomcat 启动的主要配置文件,一旦 tomcat 启动后,便不会再读取这个文件,因此无法再 tomcat 服务启动后发布 web 项目

③、第三种方法是最好的,每个项目分开配置,tomcat 将以confCatalinalocalhost 目录下的 xml 文件的文件名作为 web 应用的上下文路径,而不再理会 中配置的 path 路径,因此在配置的时候,可以不写 path。

通常我们使用第三种方法

tomcat与jvm的关系分析

首先,我们来看几个概念:

1. 什么是jvm

我们从操作系统的层面来理解,jvm其实就是操作系统中的一个进程。既然是一个进程,那么我们很容易的可以通过任务管理器来查看。假设此时我们启动myeclipse(myeclipse其实就是用java语言编写的一个软件,他的运行必然会启动一个jvm,我们可以把myeclipse理解成我们自己写的一个简单的java版的helloworld程序)。查看任务管理器的截图如下:

未分类

2. 什么是tomcat

tomcat其实是一个用java语言开发的免费开源的web服务器(因为是java语言开发,这就是为什么使用tomcat前要配置好jdk,因为jdk里面有jvm,而运行java应用需要jvm)。此时再次查看任务管理器会发现多了一个javaw.exe

看了两者之间的概念之后,相信我们都清楚了两者之间的关系。

现在还有一个问题:

同一个tomcat下的java ee项目使用的是不是同一个jvm?答案是是的。(使用的都是启动tomcat的jvm)这个可以通过启动不同的web应用来自己判断。

如果运行的是普通的java se程序,使用的是不是同一个jvm呢?答案是否。这个可以自己运行程序判断。(可以写一个很简单的while死循环,便于查看)。